test-prof 0.5.0 → 0.6.0.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|