test-prof 0.5.0 → 0.6.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +54 -0
- data/README.md +5 -2
- data/lib/test_prof.rb +17 -1
- data/lib/test_prof/before_all.rb +35 -0
- data/lib/test_prof/before_all/adapters/active_record.rb +25 -0
- data/lib/test_prof/cops/rspec/aggregate_failures.rb +1 -6
- data/lib/test_prof/event_prof.rb +16 -1
- data/lib/test_prof/event_prof/instrumentations/active_support.rb +10 -4
- data/lib/test_prof/event_prof/monitor.rb +30 -0
- data/lib/test_prof/recipes/minitest/before_all.rb +78 -0
- data/lib/test_prof/recipes/rspec/any_fixture.rb +3 -8
- data/lib/test_prof/recipes/rspec/before_all.rb +18 -19
- data/lib/test_prof/rspec_dissect.rb +50 -18
- data/lib/test_prof/rspec_dissect/collectors/base.rb +72 -0
- data/lib/test_prof/rspec_dissect/collectors/before.rb +19 -0
- data/lib/test_prof/rspec_dissect/collectors/let.rb +39 -0
- data/lib/test_prof/rspec_dissect/rspec.rb +22 -48
- data/lib/test_prof/stack_prof.rb +20 -7
- data/lib/test_prof/stack_prof/rspec.rb +7 -0
- data/lib/test_prof/version.rb +1 -1
- metadata +13 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb97ad09442483992d4d0618362de046dae7a5ed4e17f909a8bcbbffe9fc827f
|
4
|
+
data.tar.gz: d477bbede59c0b94f21181e830bef6f346c1abb6d6fb1c683a582ad237ec9c2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d0f049f6e75ed08c3474f58cdbca386ca3c7418223abd51a3e9c7d3d8ab207c68527133b2901de725531044a778de48b8dbf3eab1243bdc16e60f53ed9e2ea80
|
7
|
+
data.tar.gz: e97987b38878cdf5b29328204fa39d6e364ddbc84fff8bfe322449b02ec2bc4203417f35cc2d9fb82f79dc88c99e4241a1f5979ea11987175d5c806a8ec56965
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,60 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
- Add `EventProf.monitor` to instrument arbitrary methods. ([@palkan][])
|
6
|
+
|
7
|
+
Add custom instrumetation easily:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class Work
|
11
|
+
def do
|
12
|
+
# ...
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Instrument Work#do calls with "my.work" event
|
17
|
+
TestProf::EventProf.monitor(Work, "my.work", :do)
|
18
|
+
```
|
19
|
+
|
20
|
+
- Adapterize `before_all`. ([@palkan][])
|
21
|
+
|
22
|
+
Now it's possible to write your own adapter for `before_all` to manage
|
23
|
+
transactions.
|
24
|
+
|
25
|
+
- Add `before_all` for Minitest. ([@palkan][])
|
26
|
+
|
27
|
+
- Refactor `:with_clean_fixture` to clean data once per group. ([@palkan][])
|
28
|
+
|
29
|
+
- Show top `let` declarations per example group in RSpecDissect profiler. ([@palkan][])
|
30
|
+
|
31
|
+
The output now includes the following information:
|
32
|
+
|
33
|
+
```
|
34
|
+
Top 5 slowest suites (by `let` time):
|
35
|
+
|
36
|
+
FunnelsController (./spec/controllers/funnels_controller_spec.rb:3) – 00:38.532 of 00:43.649 (133)
|
37
|
+
↳ user – 3
|
38
|
+
↳ funnel – 2
|
39
|
+
ApplicantsController (./spec/controllers/applicants_controller_spec.rb:3) – 00:33.252 of 00:41.407 (222)
|
40
|
+
↳ user – 10
|
41
|
+
↳ funnel – 5
|
42
|
+
```
|
43
|
+
|
44
|
+
Enabled by default. Disable it with:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
TestProf::RSpecDissect.configure do |config|
|
48
|
+
config.let_stats_enabled = false
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
- Add ability to run only `let` or `before` profiler with RSpecDissect. ([@palkan][])
|
53
|
+
|
54
|
+
- [Fix [#75](https://github.com/palkan/test-prof/issues/75)] Fix `RSpec/Aggregate` failures with non-regular examples. ([@palkan][])
|
55
|
+
|
56
|
+
Do not take into account `xit`, `pending`, `its`, etc. examples,
|
57
|
+
only consider regular `it`, `specify`, `scenario`, `example`.
|
58
|
+
|
5
59
|
## 0.5.0 (2018-04-25)
|
6
60
|
|
7
61
|
### Features
|
data/README.md
CHANGED
@@ -41,10 +41,12 @@ Supported Ruby versions:
|
|
41
41
|
|
42
42
|
## Resources
|
43
43
|
|
44
|
-
- RailsClub, Moscow, 2017, "Faster Tests" talk [[video](https://www.youtube.com/watch?v=8S7oHjEiVzs) (RU), [slides](https://speakerdeck.com/palkan/railsclub-moscow-2017-faster-tests)]
|
45
|
-
|
46
44
|
- [TestProf: a good doctor for slow Ruby tests](https://evilmartians.com/chronicles/testprof-a-good-doctor-for-slow-ruby-tests)
|
47
45
|
|
46
|
+
- [TestProf II: Factory therapy for your Ruby tests](https://evilmartians.com/chronicles/testprof-2-factory-therapy-for-your-ruby-tests-rspec-minitest)
|
47
|
+
|
48
|
+
- RailsClub, Moscow, 2017, "Faster Tests" talk [[video](https://www.youtube.com/watch?v=8S7oHjEiVzs) (RU), [slides](https://speakerdeck.com/palkan/railsclub-moscow-2017-faster-tests)]
|
49
|
+
|
48
50
|
- RubyConfBy, 2017, "Run Test Run" talk [[video](https://www.youtube.com/watch?v=q52n4p0wkIs), [slides](https://speakerdeck.com/palkan/rubyconfby-minsk-2017-run-test-run)]
|
49
51
|
|
50
52
|
- [Tips to improve speed of your test suite](https://medium.com/appaloosa-store-engineering/tips-to-improve-speed-of-your-test-suite-8418b485205c) by [Benoit Tigeot](https://github.com/benoittgt)
|
@@ -69,6 +71,7 @@ Check out our [docs][].
|
|
69
71
|
|
70
72
|
Have an idea? [Propose](https://github.com/palkan/test-prof/issues/new) a feature request!
|
71
73
|
|
74
|
+
Already using TestProf? [Share your story!](https://github.com/palkan/test-prof/issues/73)
|
72
75
|
|
73
76
|
## License
|
74
77
|
|
data/lib/test_prof.rb
CHANGED
@@ -60,7 +60,13 @@ module TestProf
|
|
60
60
|
# Contains workaround for applications using Spring.
|
61
61
|
def activate(env_var, val = nil)
|
62
62
|
if defined?(::Spring::Application)
|
63
|
-
|
63
|
+
notify_spring_detected
|
64
|
+
::Spring.after_fork do
|
65
|
+
activate!(env_var, val) do
|
66
|
+
notify_spring_activate env_var
|
67
|
+
yield
|
68
|
+
end
|
69
|
+
end
|
64
70
|
else
|
65
71
|
activate!(env_var, val) { yield }
|
66
72
|
end
|
@@ -98,6 +104,16 @@ module TestProf
|
|
98
104
|
timestamps = "-#{now.to_i}"
|
99
105
|
"#{path.sub(/\.\w+$/, '')}#{timestamps}#{::File.extname(path)}"
|
100
106
|
end
|
107
|
+
|
108
|
+
def notify_spring_detected
|
109
|
+
return if instance_variable_defined?(:@spring_notified)
|
110
|
+
log :info, "Spring detected"
|
111
|
+
@spring_notified = true
|
112
|
+
end
|
113
|
+
|
114
|
+
def notify_spring_activate(env_var)
|
115
|
+
log :info, "Activating #{env_var} with `Spring.after_fork`"
|
116
|
+
end
|
101
117
|
end
|
102
118
|
|
103
119
|
# TestProf configuration
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TestProf
|
4
|
+
# `before_all` helper configiration
|
5
|
+
module BeforeAll
|
6
|
+
class AdapterMissing < StandardError # :nodoc:
|
7
|
+
MSG = "Please, provide an adapter for `before_all` " \
|
8
|
+
"through `TestProf::BeforeAll.adapter = MyAdapter`".freeze
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
super(MSG)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
attr_accessor :adapter
|
17
|
+
|
18
|
+
def begin_transaction
|
19
|
+
raise AdapterMissing if adapter.nil?
|
20
|
+
adapter.begin_transaction
|
21
|
+
end
|
22
|
+
|
23
|
+
def rollback_transaction
|
24
|
+
raise AdapterMissing if adapter.nil?
|
25
|
+
adapter.rollback_transaction
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
if defined?(::ActiveRecord::Base)
|
32
|
+
require "test_prof/before_all/adapters/active_record"
|
33
|
+
|
34
|
+
TestProf::BeforeAll.adapter = TestProf::BeforeAll::Adapters::ActiveRecord
|
35
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
if ::ActiveRecord::VERSION::MAJOR < 4
|
4
|
+
require "test_prof/ext/active_record_3"
|
5
|
+
using TestProf::ActiveRecord3Transactions
|
6
|
+
end
|
7
|
+
|
8
|
+
module TestProf
|
9
|
+
module BeforeAll
|
10
|
+
module Adapters
|
11
|
+
# ActiveRecord adapter for `before_all`
|
12
|
+
module ActiveRecord
|
13
|
+
class << self
|
14
|
+
def begin_transaction
|
15
|
+
::ActiveRecord::Base.connection.begin_transaction(joinable: false)
|
16
|
+
end
|
17
|
+
|
18
|
+
def rollback_transaction
|
19
|
+
::ActiveRecord::Base.connection.rollback_transaction
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -27,15 +27,10 @@ module RuboCop
|
|
27
27
|
# From https://github.com/backus/rubocop-rspec/blob/master/lib/rubocop/rspec/language.rb
|
28
28
|
GROUP_BLOCKS = %i[
|
29
29
|
describe context feature example_group
|
30
|
-
xdescribe xcontext xfeature
|
31
|
-
fdescribe fcontext ffeature
|
32
30
|
].freeze
|
33
31
|
|
34
32
|
EXAMPLE_BLOCKS = %i[
|
35
|
-
it specify example scenario
|
36
|
-
fit fspecify fexample fscenario focus
|
37
|
-
xit xspecify xexample xscenario ski
|
38
|
-
pending
|
33
|
+
it specify example scenario
|
39
34
|
].freeze
|
40
35
|
|
41
36
|
class << self
|
data/lib/test_prof/event_prof.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require "test_prof/rspec_stamp"
|
4
4
|
require "test_prof/event_prof/profiler"
|
5
5
|
require "test_prof/event_prof/instrumentations/active_support"
|
6
|
+
require "test_prof/event_prof/monitor"
|
6
7
|
require "test_prof/utils/sized_ordered_set"
|
7
8
|
|
8
9
|
module TestProf
|
@@ -77,12 +78,26 @@ module TestProf
|
|
77
78
|
def build(event = config.event)
|
78
79
|
ProfilersGroup.new(
|
79
80
|
event: event,
|
80
|
-
instrumenter:
|
81
|
+
instrumenter: instrumenter,
|
81
82
|
rank_by: config.rank_by,
|
82
83
|
top_count: config.top_count,
|
83
84
|
per_example: config.per_example?
|
84
85
|
)
|
85
86
|
end
|
87
|
+
|
88
|
+
def instrumenter
|
89
|
+
@instrumenter ||= config.resolve_instrumenter
|
90
|
+
end
|
91
|
+
|
92
|
+
# Instrument specified module methods.
|
93
|
+
# Wraps them with `instrumenter.instrument(event) { ... }`.
|
94
|
+
#
|
95
|
+
# Use it to profile arbitrary methods:
|
96
|
+
#
|
97
|
+
# TestProf::EventProf.monitor(MyModule, "my_module.call", :call)
|
98
|
+
def monitor(mod, event, *mids)
|
99
|
+
Monitor.call(mod, event, *mids)
|
100
|
+
end
|
86
101
|
end
|
87
102
|
end
|
88
103
|
end
|
@@ -4,11 +4,17 @@ module TestProf::EventProf
|
|
4
4
|
module Instrumentations
|
5
5
|
# Wrapper over ActiveSupport::Notifications
|
6
6
|
module ActiveSupport
|
7
|
-
|
8
|
-
|
7
|
+
class << self
|
8
|
+
def subscribe(event)
|
9
|
+
raise ArgumentError, 'Block is required!' unless block_given?
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
::ActiveSupport::Notifications.subscribe(event) do |_event, start, finish, *_args|
|
12
|
+
yield (finish - start)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def instrument(event)
|
17
|
+
::ActiveSupport::Notifications.instrument(event) { yield }
|
12
18
|
end
|
13
19
|
end
|
14
20
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_prof/ext/string_strip_heredoc"
|
4
|
+
|
5
|
+
module TestProf
|
6
|
+
module EventProf
|
7
|
+
# Wrap methods with instrumentation
|
8
|
+
module Monitor
|
9
|
+
using StringStripHeredoc
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def call(mod, event, *mids)
|
13
|
+
patch = Module.new do
|
14
|
+
mids.each do |mid|
|
15
|
+
module_eval <<-SRC.strip_heredoc, __FILE__, __LINE__
|
16
|
+
def #{mid}(*)
|
17
|
+
TestProf::EventProf.instrumenter.instrument(
|
18
|
+
'#{event}'
|
19
|
+
) { super }
|
20
|
+
end
|
21
|
+
SRC
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
mod.prepend(patch)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_prof/before_all"
|
4
|
+
|
5
|
+
module TestProf
|
6
|
+
module BeforeAll
|
7
|
+
# Add before_all hook to Minitest: wrap all examples into a transaction and
|
8
|
+
# store instance variables
|
9
|
+
module Minitest # :nodoc: all
|
10
|
+
class Executor
|
11
|
+
attr_reader :active
|
12
|
+
|
13
|
+
alias active? active
|
14
|
+
|
15
|
+
def initialize(&block)
|
16
|
+
@block = block
|
17
|
+
end
|
18
|
+
|
19
|
+
def activate!(test_class)
|
20
|
+
return if active?
|
21
|
+
@active = true
|
22
|
+
@examples_left = test_class.runnable_methods.size
|
23
|
+
BeforeAll.begin_transaction
|
24
|
+
capture!
|
25
|
+
end
|
26
|
+
|
27
|
+
def try_deactivate!
|
28
|
+
@examples_left -= 1
|
29
|
+
return unless @examples_left.zero?
|
30
|
+
|
31
|
+
@active = false
|
32
|
+
BeforeAll.rollback_transaction
|
33
|
+
end
|
34
|
+
|
35
|
+
def capture!
|
36
|
+
instance_eval(&@block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def restore_to(test_object)
|
40
|
+
instance_variables.each do |ivar|
|
41
|
+
next if ivar == :@block
|
42
|
+
test_object.instance_variable_set(
|
43
|
+
ivar,
|
44
|
+
instance_variable_get(ivar)
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class << self
|
51
|
+
def included(base)
|
52
|
+
base.extend ClassMethods
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module ClassMethods
|
57
|
+
attr_accessor :before_all_executor
|
58
|
+
|
59
|
+
def before_all
|
60
|
+
self.before_all_executor = Executor.new(&Proc.new)
|
61
|
+
|
62
|
+
prepend(Module.new do
|
63
|
+
def setup
|
64
|
+
self.class.before_all_executor.activate!(self.class)
|
65
|
+
self.class.before_all_executor.restore_to(self)
|
66
|
+
super
|
67
|
+
end
|
68
|
+
|
69
|
+
def teardown
|
70
|
+
super
|
71
|
+
self.class.before_all_executor.try_deactivate!
|
72
|
+
end
|
73
|
+
end)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -1,19 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "test_prof/any_fixture"
|
4
|
+
require "test_prof/recipes/rspec/before_all"
|
4
5
|
|
5
6
|
RSpec.shared_context "any_fixture:clean", with_clean_fixture: true do
|
6
|
-
|
7
|
-
raise("Cannot use clean context without a transaction!") unless
|
8
|
-
open_transaction?
|
7
|
+
extend TestProf::BeforeAll::RSpec
|
9
8
|
|
9
|
+
before_all do
|
10
10
|
TestProf::AnyFixture.clean
|
11
11
|
end
|
12
|
-
|
13
|
-
def open_transaction?
|
14
|
-
pool = ActiveRecord::Base.connection_pool
|
15
|
-
pool.active_connection? && pool.connection.open_transactions > 0
|
16
|
-
end
|
17
12
|
end
|
18
13
|
|
19
14
|
RSpec.configure do |config|
|
@@ -1,36 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require "test_prof/ext/active_record_3"
|
5
|
-
using TestProf::ActiveRecord3Transactions
|
6
|
-
end
|
3
|
+
require "test_prof/before_all"
|
7
4
|
|
8
5
|
module TestProf
|
9
|
-
# Helper to wrap the whole example group into a transaction
|
10
6
|
module BeforeAll
|
11
|
-
|
12
|
-
|
7
|
+
# Helper to wrap the whole example group into a transaction
|
8
|
+
module RSpec
|
9
|
+
def before_all(&block)
|
10
|
+
raise ArgumentError, "Block is required!" unless block_given?
|
13
11
|
|
14
|
-
|
12
|
+
return before(:all, &block) if within_before_all?
|
15
13
|
|
16
|
-
|
14
|
+
@__before_all_activated__ = true
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
16
|
+
before(:all) do
|
17
|
+
BeforeAll.begin_transaction
|
18
|
+
instance_eval(&block)
|
19
|
+
end
|
22
20
|
|
23
|
-
|
24
|
-
|
21
|
+
after(:all) do
|
22
|
+
BeforeAll.rollback_transaction
|
23
|
+
end
|
25
24
|
end
|
26
|
-
end
|
27
25
|
|
28
|
-
|
29
|
-
|
26
|
+
def within_before_all?
|
27
|
+
instance_variable_defined?(:@__before_all_activated__)
|
28
|
+
end
|
30
29
|
end
|
31
30
|
end
|
32
31
|
end
|
33
32
|
|
34
33
|
RSpec.configure do |config|
|
35
|
-
config.extend TestProf::BeforeAll
|
34
|
+
config.extend TestProf::BeforeAll::RSpec
|
36
35
|
end
|
@@ -14,18 +14,18 @@ module TestProf
|
|
14
14
|
end
|
15
15
|
|
16
16
|
module MemoizedInstrumentation # :nodoc:
|
17
|
-
def fetch_or_store(*)
|
17
|
+
def fetch_or_store(id, *)
|
18
18
|
res = nil
|
19
|
-
Thread.current[:
|
20
|
-
Thread.current[:
|
19
|
+
Thread.current[:_rspec_dissect_let_depth] ||= 0
|
20
|
+
Thread.current[:_rspec_dissect_let_depth] += 1
|
21
21
|
begin
|
22
|
-
res = if Thread.current[:
|
23
|
-
RSpecDissect.track(:
|
22
|
+
res = if Thread.current[:_rspec_dissect_let_depth] == 1
|
23
|
+
RSpecDissect.track(:let, id) { super }
|
24
24
|
else
|
25
25
|
super
|
26
26
|
end
|
27
27
|
ensure
|
28
|
-
Thread.current[:
|
28
|
+
Thread.current[:_rspec_dissect_let_depth] -= 1
|
29
29
|
end
|
30
30
|
res
|
31
31
|
end
|
@@ -33,21 +33,44 @@ module TestProf
|
|
33
33
|
|
34
34
|
# RSpecDisect configuration
|
35
35
|
class Configuration
|
36
|
-
|
36
|
+
MODES = %w[all let before].freeze
|
37
|
+
|
38
|
+
attr_accessor :top_count, :let_stats_enabled,
|
39
|
+
:let_top_count
|
40
|
+
|
41
|
+
alias let_stats_enabled? let_stats_enabled
|
42
|
+
|
43
|
+
attr_reader :mode
|
37
44
|
|
38
45
|
def initialize
|
46
|
+
@let_stats_enabled = true
|
47
|
+
@let_top_count = (ENV['RD_PROF_LET_TOP'] || 3).to_i
|
39
48
|
@top_count = (ENV['RD_PROF_TOP'] || 5).to_i
|
40
49
|
@stamp = ENV['RD_PROF_STAMP']
|
50
|
+
@mode = ENV['RD_PROF'] == '1' ? 'all' : ENV['RD_PROF']
|
51
|
+
|
52
|
+
unless MODES.include?(mode)
|
53
|
+
raise "Unknown RSpecDissect mode: #{mode};" \
|
54
|
+
"available modes: #{MODES.join(', ')}"
|
55
|
+
end
|
41
56
|
|
42
57
|
RSpecStamp.config.tags = @stamp if stamp?
|
43
58
|
end
|
44
59
|
|
60
|
+
def let?
|
61
|
+
mode == "all" || mode == "let"
|
62
|
+
end
|
63
|
+
|
64
|
+
def before?
|
65
|
+
mode == "all" || mode == "before"
|
66
|
+
end
|
67
|
+
|
45
68
|
def stamp?
|
46
69
|
!@stamp.nil?
|
47
70
|
end
|
48
71
|
end
|
49
72
|
|
50
|
-
METRICS = %w[before
|
73
|
+
METRICS = %w[before let].freeze
|
51
74
|
|
52
75
|
class << self
|
53
76
|
include Logging
|
@@ -76,22 +99,27 @@ module TestProf
|
|
76
99
|
|
77
100
|
reset!
|
78
101
|
|
102
|
+
if config.let? && !memoization_available?
|
103
|
+
log :warn, "RSpecDissect: `let` profiling is not supported (requires RSpec >= 3.3.0)\n"
|
104
|
+
end
|
105
|
+
|
79
106
|
log :info, "RSpecDissect enabled"
|
80
107
|
end
|
81
108
|
|
82
|
-
def track(type)
|
109
|
+
def track(type, meta = nil)
|
83
110
|
start = TestProf.now
|
84
111
|
res = yield
|
85
112
|
delta = (TestProf.now - start)
|
86
113
|
type = type.to_s
|
87
|
-
@data[type] += delta
|
114
|
+
@data[type][:time] += delta
|
115
|
+
@data[type][:meta] << meta unless meta.nil?
|
88
116
|
@data["total_#{type}"] += delta
|
89
117
|
res
|
90
118
|
end
|
91
119
|
|
92
120
|
def reset!
|
93
121
|
METRICS.each do |type|
|
94
|
-
@data[type.to_s] = 0.0
|
122
|
+
@data[type.to_s] = { time: 0.0, meta: [] }
|
95
123
|
end
|
96
124
|
end
|
97
125
|
|
@@ -100,19 +128,23 @@ module TestProf
|
|
100
128
|
defined?(::RSpec::Core::MemoizedHelpers::ThreadsafeMemoized)
|
101
129
|
end
|
102
130
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
end
|
131
|
+
def time_for(key)
|
132
|
+
@data[key.to_s][:time]
|
133
|
+
end
|
107
134
|
|
108
|
-
|
109
|
-
|
110
|
-
|
135
|
+
def meta_for(key)
|
136
|
+
@data[key.to_s][:meta]
|
137
|
+
end
|
138
|
+
|
139
|
+
def total_time_for(key)
|
140
|
+
@data["total_#{key}"]
|
111
141
|
end
|
112
142
|
end
|
113
143
|
end
|
114
144
|
end
|
115
145
|
|
146
|
+
require "test_prof/rspec_dissect/collectors/let"
|
147
|
+
require "test_prof/rspec_dissect/collectors/before"
|
116
148
|
require "test_prof/rspec_dissect/rspec" if defined?(RSpec::Core)
|
117
149
|
|
118
150
|
TestProf.activate('RD_PROF') do
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_prof/utils/sized_ordered_set"
|
4
|
+
require "test_prof/ext/float_duration"
|
5
|
+
require "test_prof/ext/string_truncate"
|
6
|
+
require "test_prof/ext/string_strip_heredoc"
|
7
|
+
|
8
|
+
module TestProf # :nodoc: all
|
9
|
+
using FloatDuration
|
10
|
+
using StringTruncate
|
11
|
+
using StringStripHeredoc
|
12
|
+
|
13
|
+
module RSpecDissect
|
14
|
+
module Collectors
|
15
|
+
class Base
|
16
|
+
attr_reader :results, :name, :top_count
|
17
|
+
|
18
|
+
def initialize(name:, top_count:)
|
19
|
+
@name = name
|
20
|
+
@top_count = top_count
|
21
|
+
@results = Utils::SizedOrderedSet.new(
|
22
|
+
top_count, sort_by: name
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def populate!(data)
|
27
|
+
data[name] = RSpecDissect.time_for(name)
|
28
|
+
end
|
29
|
+
|
30
|
+
def <<(data)
|
31
|
+
results << data
|
32
|
+
end
|
33
|
+
|
34
|
+
def total_time
|
35
|
+
RSpecDissect.total_time_for(name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def total_time_message
|
39
|
+
"\nTotal `#{print_name}` time: #{total_time.duration}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def print_name
|
43
|
+
name
|
44
|
+
end
|
45
|
+
|
46
|
+
def print_result_header
|
47
|
+
<<-MSG.strip_heredoc
|
48
|
+
|
49
|
+
Top #{top_count} slowest suites (by `#{print_name}` time):
|
50
|
+
|
51
|
+
MSG
|
52
|
+
end
|
53
|
+
|
54
|
+
def print_group_result(group)
|
55
|
+
<<-GROUP.strip_heredoc
|
56
|
+
#{group[:desc].truncate} (#{group[:loc]}) – #{group[name].duration} of #{group[:total].duration} (#{group[:count]})
|
57
|
+
GROUP
|
58
|
+
end
|
59
|
+
|
60
|
+
def print_results
|
61
|
+
msgs = [print_result_header]
|
62
|
+
|
63
|
+
results.each do |group|
|
64
|
+
msgs << print_group_result(group)
|
65
|
+
end
|
66
|
+
|
67
|
+
msgs.join
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./base"
|
4
|
+
|
5
|
+
module TestProf
|
6
|
+
module RSpecDissect
|
7
|
+
module Collectors # :nodoc: all
|
8
|
+
class Before < Base
|
9
|
+
def initialize(params)
|
10
|
+
super(name: :before, **params)
|
11
|
+
end
|
12
|
+
|
13
|
+
def print_name
|
14
|
+
"before(:each)"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./base"
|
4
|
+
|
5
|
+
module TestProf
|
6
|
+
module RSpecDissect
|
7
|
+
module Collectors # :nodoc: all
|
8
|
+
class Let < Base
|
9
|
+
def initialize(params)
|
10
|
+
super(name: :let, **params)
|
11
|
+
end
|
12
|
+
|
13
|
+
def populate!(data)
|
14
|
+
super
|
15
|
+
data[:let_calls] = RSpecDissect.meta_for(name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def print_results
|
19
|
+
return unless RSpecDissect.memoization_available?
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def print_group_result(group)
|
24
|
+
return super unless RSpecDissect.config.let_stats_enabled?
|
25
|
+
msgs = [super]
|
26
|
+
group[:let_calls]
|
27
|
+
.group_by(&:itself)
|
28
|
+
.map { |id, calls| [id, -calls.size] }
|
29
|
+
.sort_by(&:last)
|
30
|
+
.take(RSpecDissect.config.let_top_count)
|
31
|
+
.each do |(id, size)|
|
32
|
+
msgs << " ↳ #{id} – #{-size}\n"
|
33
|
+
end
|
34
|
+
msgs.join
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,17 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "test_prof/ext/float_duration"
|
4
|
-
require "test_prof/ext/string_truncate"
|
5
|
-
require "test_prof/utils/sized_ordered_set"
|
6
4
|
require "test_prof/ext/string_strip_heredoc"
|
7
5
|
|
8
6
|
module TestProf
|
9
7
|
module RSpecDissect
|
10
|
-
# rubocop:disable Metrics/ClassLength
|
11
8
|
class Listener # :nodoc:
|
12
9
|
include Logging
|
13
10
|
using FloatDuration
|
14
|
-
using StringTruncate
|
15
11
|
using StringStripHeredoc
|
16
12
|
|
17
13
|
NOTIFICATIONS = %i[
|
@@ -21,12 +17,16 @@ module TestProf
|
|
21
17
|
].freeze
|
22
18
|
|
23
19
|
def initialize
|
24
|
-
@
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
20
|
+
@collectors = []
|
21
|
+
|
22
|
+
if RSpecDissect.config.let?
|
23
|
+
collectors << Collectors::Let.new(top_count: RSpecDissect.config.top_count)
|
24
|
+
end
|
25
|
+
|
26
|
+
if RSpecDissect.config.before?
|
27
|
+
collectors << Collectors::Before.new(top_count: RSpecDissect.config.top_count)
|
28
|
+
end
|
29
|
+
|
30
30
|
@examples_count = 0
|
31
31
|
@examples_time = 0.0
|
32
32
|
@total_examples_time = 0.0
|
@@ -46,13 +46,11 @@ module TestProf
|
|
46
46
|
data = {}
|
47
47
|
data[:total] = @examples_time
|
48
48
|
data[:count] = @examples_count
|
49
|
-
data[:before] = RSpecDissect.before_time
|
50
|
-
data[:memo] = RSpecDissect.memo_time
|
51
49
|
data[:desc] = notification.group.top_level_description
|
52
50
|
data[:loc] = notification.group.metadata[:location]
|
53
51
|
|
54
|
-
|
55
|
-
|
52
|
+
collectors.each { |c| c.populate!(data) }
|
53
|
+
collectors.each { |c| c << data }
|
56
54
|
|
57
55
|
@total_examples_time += @examples_time
|
58
56
|
@examples_count = 0
|
@@ -69,43 +67,16 @@ module TestProf
|
|
69
67
|
RSpecDissect report
|
70
68
|
|
71
69
|
Total time: #{@total_examples_time.duration}
|
72
|
-
Total `before(:each)` time: #{RSpecDissect.total_before_time.duration}
|
73
|
-
MSG
|
74
|
-
|
75
|
-
msgs <<
|
76
|
-
if RSpecDissect.memoization_available?
|
77
|
-
"Total `let` time: #{RSpecDissect.total_memo_time.duration}\n\n"
|
78
|
-
else
|
79
|
-
"Total `let` time: NOT SUPPORTED (requires RSpec >= 3.3.0)\n\n"
|
80
|
-
end
|
81
|
-
|
82
|
-
msgs <<
|
83
|
-
<<-MSG.strip_heredoc
|
84
|
-
Top #{top_count} slowest suites (by `before(:each)` time):
|
85
|
-
|
86
70
|
MSG
|
87
71
|
|
88
|
-
|
89
|
-
msgs <<
|
90
|
-
<<-GROUP.strip_heredoc
|
91
|
-
#{group[:desc].truncate} (#{group[:loc]}) – #{group[:before].duration} of #{group[:total].duration} (#{group[:count]})
|
92
|
-
GROUP
|
72
|
+
collectors.each do |c|
|
73
|
+
msgs << c.total_time_message
|
93
74
|
end
|
94
75
|
|
95
|
-
|
96
|
-
msgs <<
|
97
|
-
<<-MSG.strip_heredoc
|
76
|
+
msgs << "\n"
|
98
77
|
|
99
|
-
|
100
|
-
|
101
|
-
MSG
|
102
|
-
|
103
|
-
@memo_results.each do |group|
|
104
|
-
msgs <<
|
105
|
-
<<-GROUP.strip_heredoc
|
106
|
-
#{group[:desc].truncate} (#{group[:loc]}) – #{group[:memo].duration} of #{group[:total].duration} (#{group[:count]})
|
107
|
-
GROUP
|
108
|
-
end
|
78
|
+
collectors.each do |c|
|
79
|
+
msgs << c.print_results
|
109
80
|
end
|
110
81
|
|
111
82
|
log :info, msgs.join
|
@@ -118,7 +89,9 @@ module TestProf
|
|
118
89
|
|
119
90
|
examples = Hash.new { |h, k| h[k] = [] }
|
120
91
|
|
121
|
-
(
|
92
|
+
all_results = collectors.inject([]) { |acc, c| acc + c.results.to_a }
|
93
|
+
|
94
|
+
all_results
|
122
95
|
.map { |obj| obj[:loc] }.each do |location|
|
123
96
|
file, line = location.split(":")
|
124
97
|
examples[file] << line.to_i
|
@@ -146,11 +119,12 @@ module TestProf
|
|
146
119
|
|
147
120
|
private
|
148
121
|
|
122
|
+
attr_reader :collectors
|
123
|
+
|
149
124
|
def top_count
|
150
125
|
RSpecDissect.config.top_count
|
151
126
|
end
|
152
127
|
end
|
153
|
-
# rubocop:enable Metrics/ClassLength
|
154
128
|
end
|
155
129
|
end
|
156
130
|
|
data/lib/test_prof/stack_prof.rb
CHANGED
@@ -24,11 +24,25 @@ module TestProf
|
|
24
24
|
module StackProf
|
25
25
|
# StackProf configuration
|
26
26
|
class Configuration
|
27
|
-
attr_accessor :mode, :interval, :raw
|
27
|
+
attr_accessor :mode, :interval, :raw, :target
|
28
28
|
|
29
29
|
def initialize
|
30
30
|
@mode = ENV.fetch('TEST_STACK_PROF_MODE', :wall).to_sym
|
31
|
-
@
|
31
|
+
@target = ENV['TEST_STACK_PROF'] == 'boot' ? :boot : :suite
|
32
|
+
@raw = ENV['TEST_STACK_PROF'] == 'raw' || ENV['TEST_STACK_PROF_RAW'] == '1'
|
33
|
+
@raw = true if boot?
|
34
|
+
end
|
35
|
+
|
36
|
+
def raw?
|
37
|
+
@raw == true
|
38
|
+
end
|
39
|
+
|
40
|
+
def boot?
|
41
|
+
target == :boot
|
42
|
+
end
|
43
|
+
|
44
|
+
def suite?
|
45
|
+
target == :suite
|
32
46
|
end
|
33
47
|
end
|
34
48
|
|
@@ -45,17 +59,16 @@ module TestProf
|
|
45
59
|
end
|
46
60
|
|
47
61
|
# Run StackProf and automatically dump
|
48
|
-
# a report when the process exits.
|
49
|
-
#
|
50
|
-
# Use this method to profile the whole run.
|
62
|
+
# a report when the process exits or when the application is booted.
|
51
63
|
def run
|
52
64
|
return unless profile
|
53
65
|
|
54
66
|
@locked = true
|
55
67
|
|
56
|
-
log :info, "StackProf enabled"
|
68
|
+
log :info, "StackProf enabled#{config.raw? ? ' (raw)' : ''}: " \
|
69
|
+
"mode – #{config.mode}, target – #{config.target}"
|
57
70
|
|
58
|
-
at_exit { dump("total") }
|
71
|
+
at_exit { dump("total") } if config.suite?
|
59
72
|
end
|
60
73
|
|
61
74
|
def profile(name = nil)
|
@@ -11,3 +11,10 @@ RSpec.shared_context "stackprof", sprof: true do
|
|
11
11
|
TestProf::StackProf.dump ex.full_description.parameterize
|
12
12
|
end
|
13
13
|
end
|
14
|
+
|
15
|
+
# Handle boot profiling
|
16
|
+
RSpec.configure do |config|
|
17
|
+
config.append_before(:suite) do
|
18
|
+
TestProf::StackProf.dump("boot") if TestProf::StackProf.config.boot?
|
19
|
+
end
|
20
|
+
end
|
data/lib/test_prof/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: test-prof
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0.pre1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vladimir Dementyev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-06-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -72,14 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: 0.
|
75
|
+
version: 0.57.0
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: 0.
|
82
|
+
version: 0.57.0
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: rubocop-md
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -125,6 +125,8 @@ files:
|
|
125
125
|
- lib/test_prof.rb
|
126
126
|
- lib/test_prof/any_fixture.rb
|
127
127
|
- lib/test_prof/any_fixture/dsl.rb
|
128
|
+
- lib/test_prof/before_all.rb
|
129
|
+
- lib/test_prof/before_all/adapters/active_record.rb
|
128
130
|
- lib/test_prof/cops/rspec/aggregate_failures.rb
|
129
131
|
- lib/test_prof/event_prof.rb
|
130
132
|
- lib/test_prof/event_prof/custom_events.rb
|
@@ -133,6 +135,7 @@ files:
|
|
133
135
|
- lib/test_prof/event_prof/custom_events/sidekiq_jobs.rb
|
134
136
|
- lib/test_prof/event_prof/instrumentations/active_support.rb
|
135
137
|
- lib/test_prof/event_prof/minitest.rb
|
138
|
+
- lib/test_prof/event_prof/monitor.rb
|
136
139
|
- lib/test_prof/event_prof/profiler.rb
|
137
140
|
- lib/test_prof/event_prof/rspec.rb
|
138
141
|
- lib/test_prof/ext/active_record_3.rb
|
@@ -161,6 +164,7 @@ files:
|
|
161
164
|
- lib/test_prof/recipes/active_record_one_love.rb
|
162
165
|
- lib/test_prof/recipes/active_record_shared_connection.rb
|
163
166
|
- lib/test_prof/recipes/logging.rb
|
167
|
+
- lib/test_prof/recipes/minitest/before_all.rb
|
164
168
|
- lib/test_prof/recipes/minitest/sample.rb
|
165
169
|
- lib/test_prof/recipes/rspec/any_fixture.rb
|
166
170
|
- lib/test_prof/recipes/rspec/before_all.rb
|
@@ -169,6 +173,9 @@ files:
|
|
169
173
|
- lib/test_prof/recipes/rspec/let_it_be.rb
|
170
174
|
- lib/test_prof/recipes/rspec/sample.rb
|
171
175
|
- lib/test_prof/rspec_dissect.rb
|
176
|
+
- lib/test_prof/rspec_dissect/collectors/base.rb
|
177
|
+
- lib/test_prof/rspec_dissect/collectors/before.rb
|
178
|
+
- lib/test_prof/rspec_dissect/collectors/let.rb
|
172
179
|
- lib/test_prof/rspec_dissect/rspec.rb
|
173
180
|
- lib/test_prof/rspec_stamp.rb
|
174
181
|
- lib/test_prof/rspec_stamp/parser.rb
|
@@ -202,9 +209,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
202
209
|
version: 2.2.0
|
203
210
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
204
211
|
requirements:
|
205
|
-
- - "
|
212
|
+
- - ">"
|
206
213
|
- !ruby/object:Gem::Version
|
207
|
-
version:
|
214
|
+
version: 1.3.1
|
208
215
|
requirements: []
|
209
216
|
rubyforge_project:
|
210
217
|
rubygems_version: 2.7.6
|