test-prof 0.2.1 → 0.3.0

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -1
  3. data/README.md +12 -2
  4. data/guides/event_prof.md +12 -0
  5. data/guides/factory_doctor.md +10 -0
  6. data/guides/let_it_be.md +96 -0
  7. data/guides/rspec_dissect.md +58 -0
  8. data/guides/ruby_prof.md +5 -5
  9. data/guides/stack_prof.md +3 -3
  10. data/lib/test_prof/cops/rspec/aggregate_failures.rb +2 -2
  11. data/lib/test_prof/event_prof/custom_events/factory_create.rb +5 -1
  12. data/lib/test_prof/event_prof/custom_events/sidekiq_inline.rb +5 -1
  13. data/lib/test_prof/event_prof/custom_events/sidekiq_jobs.rb +5 -1
  14. data/lib/test_prof/event_prof/rspec.rb +44 -5
  15. data/lib/test_prof/event_prof.rb +38 -25
  16. data/lib/test_prof/ext/array_bsearch_index.rb +15 -0
  17. data/lib/test_prof/ext/float_duration.rb +0 -1
  18. data/lib/test_prof/ext/string_strip_heredoc.rb +15 -0
  19. data/lib/test_prof/ext/string_truncate.rb +19 -0
  20. data/lib/test_prof/factory_doctor/rspec.rb +38 -2
  21. data/lib/test_prof/factory_doctor.rb +12 -4
  22. data/lib/test_prof/factory_prof/printers/simple.rb +4 -1
  23. data/lib/test_prof/recipes/rspec/any_fixture.rb +1 -1
  24. data/lib/test_prof/recipes/rspec/before_all.rb +8 -0
  25. data/lib/test_prof/recipes/rspec/let_it_be.rb +80 -0
  26. data/lib/test_prof/rspec_dissect/rspec.rb +157 -0
  27. data/lib/test_prof/rspec_dissect.rb +112 -0
  28. data/lib/test_prof/rspec_stamp/parser.rb +58 -7
  29. data/lib/test_prof/rspec_stamp/rspec.rb +10 -34
  30. data/lib/test_prof/rspec_stamp.rb +55 -6
  31. data/lib/test_prof/ruby_prof.rb +19 -4
  32. data/lib/test_prof/stack_prof.rb +19 -5
  33. data/lib/test_prof/tag_prof/rspec.rb +5 -3
  34. data/lib/test_prof/utils/sized_ordered_set.rb +65 -0
  35. data/lib/test_prof/utils.rb +18 -0
  36. data/lib/test_prof/version.rb +1 -1
  37. data/lib/test_prof.rb +15 -2
  38. metadata +15 -75
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2b61bd0dd4011eb8d77c50a343ba8e582393bd68
4
- data.tar.gz: 3a233a89a57d261382dc253cb87156655a7356c6
3
+ metadata.gz: eb103d86ccc7a513de8b30ae8ddf536c9bbc6255
4
+ data.tar.gz: f0fe1c206faab5761b4419ccbf8745f4060d3e82
5
5
  SHA512:
6
- metadata.gz: 335d1151f7fcf286f9bf0ba01fcadcbe09c042e9712d069c4c85a3acc5e28f548add3580292fbed6ec8d89da796330b81ac66809fd55ab952fef5a20caf4e200
7
- data.tar.gz: b2fb7e978bb9247c9d53e082682bb5fae34d0f7179784295457077100abff1365481b564fa4bb62e1bb24f4a3edf9d8fff0ef6be199ddf188ee99813ee369573
6
+ metadata.gz: f6a4881cfc954438fa4184c2cd9be1784d10bfcd6d92931ea5132c8422aa08866b77e60ec91066ddd6afb620c524e99b4584818d158f396176f9649ef3a0e038
7
+ data.tar.gz: a699bd6e308b370aa4e53447025551a5b2f30249eb893356d30238eaed4dc135bb69335c780ea9d0b306ce569bca29117494ab91b6a70bbb7a22f714a65f827f
data/CHANGELOG.md CHANGED
@@ -1,6 +1,64 @@
1
1
  # Change log
2
2
 
3
- ## master
3
+ ## 0.3.0
4
+
5
+ ### Features:
6
+
7
+ - Combine RSpecStamp with FactoryDoctor. ([@palkan][])
8
+
9
+ Automatically mark _bad_ examples with custom tags.
10
+
11
+ - [#17](https://github.com/palkan/test-prof/pull/17) Combine RSpecStamp with EventProf and RSpecDissect. ([@palkan][])
12
+
13
+ It is possible now to automatically mark _slow_ examples and groups with custom tags. For example:
14
+
15
+ ```sh
16
+ EVENT_PROF="sql.active_record" EVENT_PROF_STAMP="slow:sql" rspec ...
17
+ ```
18
+
19
+ After running the command above the top 5 slowest example groups would be marked with `slow: :sql` tag.
20
+
21
+ - [#14](https://github.com/palkan/test-prof/pull/14) RSpecDissect profiler. ([@palkan][])
22
+
23
+ RSpecDissect tracks how much time do you spend in `before` hooks
24
+ and memoization helpers (i.e. `let`) in your tests.
25
+
26
+ - [#13](https://github.com/palkan/test-prof/pull/13) RSpec `let_it_be` method. ([@palkan][])
27
+
28
+ Just like `let`, but persist the result for the whole group (i.e. `let` + `before_all`).
29
+
30
+ ### Improvements:
31
+
32
+ - Add ability to specify RubyProf report through `TEST_RUBY_PROF` env variable. ([@palkan][])
33
+
34
+ - Add ability to specify StackProf raw mode through `TEST_STACK_PROF` env variable. ([@palkan][])
35
+
36
+ ### Changes
37
+
38
+ - Use RubyProf `FlatPrinter` by default (was `CallStackPrinter`). ([@palkan][])
39
+
40
+ ## 0.2.5
41
+
42
+ - [#16](https://github.com/palkan/test-prof/pull/16) Support Ruby >= 2.2.0 (was >= 2.3.0). ([@palkan][])
43
+
44
+ ## 0.2.4
45
+
46
+ - EventProf: Fix regression bug with examples profiling. ([@palkan][])
47
+
48
+ There was a bug when an event occurs before the example has started (e.g. in `before(:context)` hook).
49
+
50
+ ## 0.2.3
51
+
52
+ - Minor improvements. ([@palkan][])
53
+
54
+ ## 0.2.2
55
+
56
+ - Fix time calculation when Time class is monkey-patched. ([@palkan][])
57
+
58
+ Add `TestProf.now` method which is just a copy of original `Time.now` and
59
+ use it everywhere.
60
+
61
+ Fixes [#10](https://github.com/palkan/test-prof/issues/10).
4
62
 
5
63
  ## 0.2.1
6
64
 
data/README.md CHANGED
@@ -10,7 +10,7 @@ Simply speaking, slow tests waste your time making you less productive.
10
10
 
11
11
  TestProf toolbox aims to help you identify bottlenecks in your test suite. It contains:
12
12
 
13
- - Plug'n'Play integrations for general Ruby profilers ([`ruby-prof`](https://github.com/ruby-prof), [`stackprof`](https://github.com/tmm1/stackprof))
13
+ - Plug'n'Play integrations for general Ruby profilers ([`ruby-prof`](https://github.com/ruby-prof/ruby-prof), [`stackprof`](https://github.com/tmm1/stackprof))
14
14
 
15
15
  - Factories usage analyzers and profilers
16
16
 
@@ -20,10 +20,16 @@ TestProf toolbox aims to help you identify bottlenecks in your test suite. It co
20
20
 
21
21
  - etc.
22
22
 
23
- Of course, we have some [solutions](#tips-and-tricks) for common performance issues too, bundled into the gem.
23
+ Of course, we have some [solutions](#tips-and-tricks-or-recipes) for common performance issues too, bundled into the gem.
24
24
 
25
25
  See [Table of Contents](#table-of-contents) for more.
26
26
 
27
+ Supported Ruby versions:
28
+
29
+ - Ruby (MRI) >= 2.2.0
30
+
31
+ - JRuby >= 9.1.0.0
32
+
27
33
  <a href="https://evilmartians.com/">
28
34
  <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
29
35
 
@@ -63,6 +69,8 @@ Checkout our guides for each specific tool:
63
69
 
64
70
  - [Factory Profiler](https://github.com/palkan/test-prof/tree/master/guides/factory_prof.md)
65
71
 
72
+ - [RSpecDissect Profiler](https://github.com/palkan/test-prof/tree/master/guides/rspec_dissect.md)
73
+
66
74
  - [Rubocop Cops](https://github.com/palkan/test-prof/tree/master/guides/rubocop.md)
67
75
 
68
76
  ## Tips and Tricks (or _Recipes_)
@@ -71,6 +79,8 @@ We also want to share some small code tricks which can help you to improve your
71
79
 
72
80
  - [`before_all` Hook](https://github.com/palkan/test-prof/tree/master/guides/before_all.md)
73
81
 
82
+ - [`let_it_be` Helper](https://github.com/palkan/test-prof/tree/master/guides/let_it_be.md)
83
+
74
84
  - [AnyFixture](https://github.com/palkan/test-prof/tree/master/guides/any_fixture.md)
75
85
 
76
86
  - [FactoryDefault](https://github.com/palkan/test-prof/tree/master/guides/factory_default.md)
data/guides/event_prof.md CHANGED
@@ -55,6 +55,8 @@ TestProf::EventProf.configure do |config|
55
55
  end
56
56
  ```
57
57
 
58
+ Or provide the `EVENT_PROF_EXAMPLES=1` env variable.
59
+
58
60
  Another useful configuration parameter – `rank_by`. It's responsible for sorting stats –
59
61
  either by the time spent in the event or by the number of occurrences:
60
62
 
@@ -64,6 +66,16 @@ EVENT_PROF_RANK=count EVENT_PROF='instantiation.active_record' be rspec
64
66
 
65
67
  See [event_prof.rb](https://github.com/palkan/test-prof/tree/master/lib/test_prof/event_prof.rb) for all available configuration options and their usage.
66
68
 
69
+ ## Using with RSpecStamp
70
+
71
+ EventProf can be used with [RSpec Stamp](https://github.com/palkan/test-prof/tree/master/guides/rspec_stamp.md) to automatically mark _slow_ examples with custom tags. For example:
72
+
73
+ ```sh
74
+ EVENT_PROF="sql.active_record" EVENT_PROF_STAMP="slow:sql" rspec ...
75
+ ```
76
+
77
+ After running the command above the slowest example groups (and examples if configured) would be marked with the `slow: :sql` tag.
78
+
67
79
  ## Custom Instrumentation
68
80
 
69
81
  To use EventProf with your instrumentation engine just complete the two following steps:
@@ -62,3 +62,13 @@ To activate FactoryDoctor use `FDOC` environment variable:
62
62
  ```sh
63
63
  FDOC=1 rspec ...
64
64
  ```
65
+
66
+ ## Using with RSpecStamp
67
+
68
+ FactoryDoctor can be used with [RSpec Stamp](https://github.com/palkan/test-prof/tree/master/guides/rspec_stamp.md) to automatically mark _bad_ examples with custom tags. For example:
69
+
70
+ ```sh
71
+ FDOC=1 FDOC_STAMP="fdoc:consider" rspec ...
72
+ ```
73
+
74
+ After running the command above all _potentially_ bad examples would be marked with the `fdoc: :consider` tag.
@@ -0,0 +1,96 @@
1
+ # Let It Be!
2
+
3
+ Let's bring a little bit of magic and introduce a new way to setup a _shared_ test data.
4
+
5
+ Suppose you have the following setup:
6
+
7
+ ```ruby
8
+ describe BeatleWeightedSearchQuery do
9
+ let!(:paul) { create(:beatle, name: 'Paul') }
10
+ let!(:ringo) { create(:beatle, name: 'Ringo') }
11
+ let!(:george) { create(:beatle, name: 'George') }
12
+ let!(:john) { create(:beatle, name: 'John') }
13
+
14
+ specify { expect(subject.call('joh')).to contain_exactly(john) }
15
+
16
+ # and more examples here
17
+ end
18
+ ```
19
+
20
+ We don't need to re-create the Fab Four for every example, do we?
21
+
22
+ We already have [`before_all`](https://github.com/palkan/test-prof/tree/master/guides/before_all.md) to solve the problem of _repeatable_ data:
23
+
24
+ ```ruby
25
+ describe BeatleWeightedSearchQuery do
26
+ before_all do
27
+ @paul = create(:beatle, name: 'Paul')
28
+ ...
29
+ end
30
+
31
+ specify { expect(subject.call('joh')).to contain_exactly(@john) }
32
+
33
+ ...
34
+ end
35
+ ```
36
+
37
+ That technique works pretty good but requires us to use instance variables and define everything at once. Thus it's not easy to refactor existing tests which use `let/let!` instead.
38
+
39
+ With `let_it_be` you can do the following:
40
+
41
+ ```ruby
42
+ describe BeatleWeightedSearchQuery do
43
+ let_it_be(:paul) { create(:beatle, name: 'Paul') }
44
+ let_it_be(:ringo) { create(:beatle, name: 'Ringo') }
45
+ let_it_be(:george) { create(:beatle, name: 'George') }
46
+ let_it_be(:john) { create(:beatle, name: 'John') }
47
+
48
+ specify { expect(subject.call('joh')).to contain_exactly(john) }
49
+
50
+ # and more examples here
51
+ end
52
+ ```
53
+
54
+ That's it! Just replace `let!` with `let_it_be`. That's equal to the `before_all` approach but requires less refactoring.
55
+
56
+
57
+ ## Instructions
58
+
59
+ In your `spec_helper.rb`:
60
+
61
+ ```ruby
62
+ require "test_prof/recipes/rspec/let_it_be"
63
+ ```
64
+
65
+ In your tests:
66
+
67
+ ```ruby
68
+ describe MySuperDryService do
69
+ let_it_be(:user) { create(:user) }
70
+
71
+ ...
72
+ end
73
+ ```
74
+
75
+ ## Caveats
76
+
77
+ If you modify objects generated within a `let_it_be` block in your examples, you maybe have to re-initiate them.
78
+ We have a built-in support for that:
79
+
80
+
81
+ ```ruby
82
+ # Use reload: true option to reload user object (assuming it's an instance of ActiveRecord)
83
+ # for every example
84
+ let_it_be(:user, reload: true) { create(:user) }
85
+
86
+ # it is almost equal to
87
+ before_all { @user = create(:user) }
88
+ let(:user) { @user.reload }
89
+
90
+ # You can also specify refind: true option to hard-reload the record
91
+ let_it_be(:user, refind: true) { create(:user) }
92
+
93
+ # it is almost equal to
94
+ before_all { @user = create(:user) }
95
+ let(:user) { User.find(@user.id) }
96
+ ```
@@ -0,0 +1,58 @@
1
+ # RSpecDissect
2
+
3
+ Do you know how much time you spend in `before` hooks? Or memoization helpers such as `let`? Usually, the most of the whole test suite time.
4
+
5
+ _RSpecDissect_ provides this kind of information and also shows you the worst example groups. The main purpose of RSpecDissect is to identify these slow groups and refactor them using [`before_all`](https://github.com/palkan/test-prof/tree/master/guides/before_all.md) or [`let_it_be`](https://github.com/palkan/test-prof/tree/master/guides/let_it_be.md) recipes.
6
+
7
+ Example output:
8
+
9
+ ```sh
10
+ [TEST PROF INFO] RSpecDissect enabled
11
+
12
+ Total time: 25:14.870
13
+ Total `before(:each)` time: 14:36.482
14
+ Total `let` time: 19:20.259
15
+
16
+ Top 5 slowest suites (by `before(:each)` time):
17
+
18
+ Webhooks::DispatchTransition (./spec/services/webhooks/dispatch_transition_spec.rb:3) – 00:29.895 of 00:33.706 (327)
19
+ FunnelsController (./spec/controllers/funnels_controller_spec.rb:3) – 00:22.117 of 00:43.649 (133)
20
+ ApplicantsController (./spec/controllers/applicants_controller_spec.rb:3) – 00:21.220 of 00:41.407 (222)
21
+ BookedSlotsController (./spec/controllers/booked_slots_controller_spec.rb:3) – 00:15.729 of 00:27.893 (50)
22
+ Analytics::Wor...rsion::Summary (./spec/services/analytics/workflow_conversion/summary_spec.rb:3) – 00:15.383 of 00:15.914 (12)
23
+
24
+
25
+ Top 5 slowest suites (by `let` time):
26
+
27
+ FunnelsController (./spec/controllers/funnels_controller_spec.rb:3) – 00:38.532 of 00:43.649 (133)
28
+ ApplicantsController (./spec/controllers/applicants_controller_spec.rb:3) – 00:33.252 of 00:41.407 (222)
29
+ Webhooks::DispatchTransition (./spec/services/webhooks/dispatch_transition_spec.rb:3) – 00:30.320 of 00:33.706 (327)
30
+ BookedSlotsController (./spec/controllers/booked_slots_controller_spec.rb:3) – 00:25.710 of 00:27.893 (50)
31
+ AvailableSlotsController (./spec/controllers/available_slots_controller_spec.rb:3) – 00:18.481 of 00:23.366 (85)
32
+ ```
33
+
34
+ ## Instructions
35
+
36
+ RSpecDissect can only be used with RSpec (which is clear from the name).
37
+
38
+ To activate RSpecDissect use `RD_PROF` environment variable:
39
+
40
+ ```sh
41
+ RD_PROF=1 rspec ...
42
+ ```
43
+
44
+ You can also specify the number of top slow groups through `RD_PROF_TOP` variable:
45
+
46
+ ```sh
47
+ RD_PROF=1 RD_PROF_TOP=10 rspec ...
48
+ ```
49
+
50
+ ## Using with RSpecStamp
51
+
52
+ RSpecDissect can be used with [RSpec Stamp](https://github.com/palkan/test-prof/tree/master/guides/rspec_stamp.md) to automatically mark _slow_ examples with custom tags. For example:
53
+
54
+ ```sh
55
+ RD_PROF=1 RD_PROF_STAMP="slow" rspec ...
56
+ ```
57
+
58
+ After running the command above the slowest example groups would be marked with the `:slow` tag.
data/guides/ruby_prof.md CHANGED
@@ -9,7 +9,7 @@ Install `ruby-prof` gem (>= 0.16):
9
9
  ```ruby
10
10
  # Gemfile
11
11
  group :development, :test do
12
- gem 'ruby-prof', require: false
12
+ gem 'ruby-prof', '>= 0.16.0', require: false
13
13
  end
14
14
  ```
15
15
 
@@ -42,21 +42,21 @@ end
42
42
 
43
43
  The most useful configuration option is `printer` – it allows you to specify a RubyProf [printer](https://github.com/ruby-prof/ruby-prof#printers).
44
44
 
45
- You can specify a printer through environment variable `TEST_RUBY_PROF_PRINTER`:
45
+ You can specify a printer through environment variable `TEST_RUBY_PROF`:
46
46
 
47
47
  ```sh
48
- TEST_RUBY_PROF_PRINTER=flat bundle exec rake test
48
+ TEST_RUBY_PROF=call_stack bundle exec rake test
49
49
  ```
50
50
 
51
51
  Or in your code:
52
52
 
53
53
  ```ruby
54
54
  TestProf::RubyProf.configure do |config|
55
- config.printer = :flat
55
+ config.printer = :call_stack
56
56
  end
57
57
  ```
58
58
 
59
- By default, we use `CallStackPrinter`.
59
+ By default, we use `FlatPrinter`.
60
60
 
61
61
  Also, you can specify RubyProf mode (`wall`, `cpu`, etc) through `TEST_RUBY_PROF_MODE` env variable.
62
62
 
data/guides/stack_prof.md CHANGED
@@ -4,12 +4,12 @@
4
4
 
5
5
  ## Instructions
6
6
 
7
- Install 'stackprof' gem (>= 0.2.7):
7
+ Install 'stackprof' gem (>= 0.2.9):
8
8
 
9
9
  ```ruby
10
10
  # Gemfile
11
11
  group :development, :test do
12
- gem 'stackprof', require: false
12
+ gem 'stackprof', '>= 0.2.9', require: false
13
13
  end
14
14
  ```
15
15
 
@@ -42,6 +42,6 @@ end
42
42
 
43
43
  You can change StackProf mode (which is `wall` by default) through `TEST_STACK_PROF_MODE` env variable.
44
44
 
45
- If you want to generate flame graphs you should collect _raw_ data. Turn _raw_ collection on by passing `TEST_STACK_PROF_RAW=1`.
45
+ If you want to generate flame graphs you should collect _raw_ data. Turn _raw_ collection on by passing `TEST_STACK_PROF=raw`.
46
46
 
47
47
  See [stack_prof.rb](https://github.com/palkan/test-prof/tree/master/lib/test_prof/stack_prof.rb) for all available configuration options and their usage.
@@ -39,7 +39,7 @@ module RuboCop
39
39
 
40
40
  def on_block(node)
41
41
  method, _args, body = *node
42
- return unless body&.begin_type?
42
+ return unless body && body.begin_type?
43
43
 
44
44
  _receiver, method_name, _object = *method
45
45
  return unless GROUP_BLOCKS.include?(method_name)
@@ -109,7 +109,7 @@ module RuboCop
109
109
  end
110
110
 
111
111
  def oneliner?(node)
112
- node&.block_type? &&
112
+ node && node.block_type? &&
113
113
  (node.source.lines.size == 1) &&
114
114
  example_node?(node)
115
115
  end
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "test_prof/ext/string_strip_heredoc"
4
+
5
+ using TestProf::StringStripHeredoc
6
+
3
7
  module TestProf::EventProf::CustomEvents
4
8
  module FactoryCreate # :nodoc: all
5
9
  module RunnerPatch
@@ -42,7 +46,7 @@ end
42
46
  TestProf.activate('EVENT_PROF', 'factory.create') do
43
47
  if TestProf.require(
44
48
  'factory_girl',
45
- <<~MSG
49
+ <<-MSG.strip_heredoc
46
50
  Failed to load FactoryGirl.
47
51
 
48
52
  Make sure that "factory_girl" gem is in your Gemfile.
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "test_prof/ext/string_strip_heredoc"
4
+
5
+ using TestProf::StringStripHeredoc
6
+
3
7
  module TestProf::EventProf::CustomEvents
4
8
  module SidekiqInline # :nodoc: all
5
9
  module ClientPatch
@@ -39,7 +43,7 @@ end
39
43
  TestProf.activate('EVENT_PROF', 'sidekiq.inline') do
40
44
  if TestProf.require(
41
45
  'sidekiq/testing',
42
- <<~MSG
46
+ <<-MSG.strip_heredoc
43
47
  Failed to load Sidekiq.
44
48
 
45
49
  Make sure that "sidekiq" gem is in your Gemfile.
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "test_prof/ext/string_strip_heredoc"
4
+
5
+ using TestProf::StringStripHeredoc
6
+
3
7
  module TestProf::EventProf::CustomEvents
4
8
  module SidekiqJobs # :nodoc: all
5
9
  module ClientPatch
@@ -26,7 +30,7 @@ end
26
30
  TestProf.activate('EVENT_PROF', 'sidekiq.jobs') do
27
31
  if TestProf.require(
28
32
  'sidekiq/testing',
29
- <<~MSG
33
+ <<-MSG.strip_heredoc
30
34
  Failed to load Sidekiq.
31
35
 
32
36
  Make sure that "sidekiq" gem is in your Gemfile.
@@ -1,12 +1,16 @@
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/ext/string_strip_heredoc"
4
6
 
5
7
  module TestProf
6
8
  module EventProf
7
9
  class RSpecListener # :nodoc:
8
10
  include Logging
9
11
  using FloatDuration
12
+ using StringTruncate
13
+ using StringStripHeredoc
10
14
 
11
15
  NOTIFICATIONS = %i[
12
16
  example_group_started
@@ -43,7 +47,7 @@ module TestProf
43
47
  msgs = []
44
48
 
45
49
  msgs <<
46
- <<~MSG
50
+ <<-MSG.strip_heredoc
47
51
  EventProf results for #{@profiler.event}
48
52
 
49
53
  Total time: #{@profiler.total_time.duration}
@@ -58,8 +62,8 @@ module TestProf
58
62
  location = group[:id].metadata[:location]
59
63
 
60
64
  msgs <<
61
- <<~GROUP
62
- #{description} (#{location}) – #{group[:time].duration} (#{group[:count]} / #{group[:examples]})
65
+ <<-GROUP.strip_heredoc
66
+ #{description.truncate} (#{location}) – #{group[:time].duration} (#{group[:count]} / #{group[:examples]})
63
67
  GROUP
64
68
  end
65
69
 
@@ -70,13 +74,48 @@ module TestProf
70
74
  description = example[:id].description
71
75
  location = example[:id].metadata[:location]
72
76
  msgs <<
73
- <<~GROUP
74
- #{description} (#{location}) – #{example[:time].duration} (#{example[:count]})
77
+ <<-GROUP.strip_heredoc
78
+ #{description.truncate} (#{location}) – #{example[:time].duration} (#{example[:count]})
75
79
  GROUP
76
80
  end
77
81
  end
78
82
 
79
83
  log :info, msgs.join
84
+
85
+ stamp! if EventProf.config.stamp?
86
+ end
87
+
88
+ def stamp!
89
+ result = @profiler.results
90
+
91
+ stamper = RSpecStamp::Stamper.new
92
+
93
+ examples = Hash.new { |h, k| h[k] = [] }
94
+
95
+ (result[:groups].to_a + result.fetch(:examples, []).to_a)
96
+ .map { |obj| obj[:id].metadata[:location] }.each do |location|
97
+ file, line = location.split(":")
98
+ examples[file] << line.to_i
99
+ end
100
+
101
+ examples.each do |file, lines|
102
+ stamper.stamp_file(file, lines.uniq)
103
+ end
104
+
105
+ msgs = []
106
+
107
+ msgs <<
108
+ <<-MSG.strip_heredoc
109
+ RSpec Stamp results
110
+
111
+ Total patches: #{stamper.total}
112
+ Total files: #{examples.keys.size}
113
+
114
+ Failed patches: #{stamper.failed}
115
+ Ignored files: #{stamper.ignored}
116
+ MSG
117
+
118
+ log :info, msgs.join
80
119
  end
81
120
  end
82
121
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "test_prof/rspec_stamp"
3
4
  require "test_prof/event_prof/instrumentations/active_support"
5
+ require "test_prof/utils/sized_ordered_set"
4
6
 
5
7
  module TestProf
6
8
  # EventProf profiles your tests and suites against custom events,
@@ -19,6 +21,8 @@ module TestProf
19
21
  # TestProf::EventProf.configure do |config|
20
22
  # config.per_example = true
21
23
  # end
24
+ #
25
+ # Or provide the EVENT_PROF_EXAMPLES=1 env variable.
22
26
  module EventProf
23
27
  # EventProf configuration
24
28
  class Configuration
@@ -34,8 +38,15 @@ module TestProf
34
38
  @event = ENV['EVENT_PROF']
35
39
  @instrumenter = :active_support
36
40
  @top_count = (ENV['EVENT_PROF_TOP'] || 5).to_i
37
- @per_example = false
41
+ @per_example = ENV['EVENT_PROF_EXAMPLES'] == '1'
38
42
  @rank_by = (ENV['EVENT_PROF_RANK'] || :time).to_sym
43
+ @stamp = ENV['EVENT_PROF_STAMP']
44
+
45
+ RSpecStamp.config.tags = @stamp if stamp?
46
+ end
47
+
48
+ def stamp?
49
+ !@stamp.nil?
39
50
  end
40
51
 
41
52
  def per_example?
@@ -73,7 +84,7 @@ module TestProf
73
84
  class Profiler # :nodoc:
74
85
  include TestProf::Logging
75
86
 
76
- attr_reader :event, :top_count, :rank_by, :total_count, :total_time
87
+ attr_reader :event, :total_count, :total_time
77
88
 
78
89
  def initialize(event:, instrumenter:)
79
90
  @event = event
@@ -82,13 +93,16 @@ module TestProf
82
93
 
83
94
  instrumenter.subscribe(event) { |time| track(time) }
84
95
 
85
- @groups = Hash.new { |h, k| h[k] = { id: k } }
86
- @examples = Hash.new { |h, k| h[k] = { id: k } }
96
+ @groups = Utils::SizedOrderedSet.new(
97
+ top_count, sort_by: rank_by
98
+ )
99
+
100
+ @examples = Utils::SizedOrderedSet.new(
101
+ top_count, sort_by: rank_by
102
+ )
87
103
 
88
104
  @total_count = 0
89
105
  @total_time = 0.0
90
-
91
- reset!
92
106
  end
93
107
 
94
108
  def track(time)
@@ -99,41 +113,47 @@ module TestProf
99
113
  @time += time
100
114
  @count += 1
101
115
 
102
- @example_time += time if config.per_example?
103
- @example_count += 1 if config.per_example?
116
+ return if @current_example.nil?
117
+
118
+ @example_time += time
119
+ @example_count += 1
104
120
  end
105
121
 
106
122
  def group_started(id)
107
- reset!
123
+ reset_group!
108
124
  @current_group = id
109
125
  end
110
126
 
111
127
  def group_finished(id)
112
- @groups[id][:time] = @time
113
- @groups[id][:count] = @count
114
- @groups[id][:examples] = @total_examples
128
+ data = { id: id, time: @time, count: @count, examples: @total_examples }
129
+
130
+ @groups << data unless data[rank_by].zero?
131
+
115
132
  @current_group = nil
116
133
  end
117
134
 
118
- def example_started(_id)
135
+ def example_started(id)
136
+ return unless config.per_example?
119
137
  reset_example!
138
+ @current_example = id
120
139
  end
121
140
 
122
141
  def example_finished(id)
123
142
  @total_examples += 1
124
143
  return unless config.per_example?
125
144
 
126
- @examples[id][:time] = @example_time
127
- @examples[id][:count] = @example_count
145
+ data = { id: id, time: @example_time, count: @example_count }
146
+ @examples << data unless data[rank_by].zero?
147
+ @current_example = nil
128
148
  end
129
149
 
130
150
  def results
131
151
  {
132
- groups: fetch_top(@groups.values)
152
+ groups: @groups.to_a
133
153
  }.tap do |data|
134
154
  next unless config.per_example?
135
155
 
136
- data[:examples] = fetch_top(@examples.values)
156
+ data[:examples] = @examples.to_a
137
157
  end
138
158
  end
139
159
 
@@ -147,21 +167,14 @@ module TestProf
147
167
 
148
168
  private
149
169
 
150
- def fetch_top(arr)
151
- arr.reject { |el| el[rank_by].zero? }
152
- .sort_by { |el| -el[rank_by] }
153
- .take(top_count)
154
- end
155
-
156
170
  def config
157
171
  EventProf.config
158
172
  end
159
173
 
160
- def reset!
174
+ def reset_group!
161
175
  @time = 0.0
162
176
  @count = 0
163
177
  @total_examples = 0
164
- reset_example!
165
178
  end
166
179
 
167
180
  def reset_example!