test-prof 0.4.9 → 0.5.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +63 -20
  3. data/README.md +10 -57
  4. data/assets/tagprof.demo.html +447 -0
  5. data/assets/tagprof.template.html +447 -0
  6. data/lib/minitest/event_prof_formatter.rb +18 -16
  7. data/lib/test_prof.rb +2 -1
  8. data/lib/test_prof/any_fixture.rb +80 -4
  9. data/lib/test_prof/any_fixture/dsl.rb +18 -0
  10. data/lib/test_prof/event_prof.rb +10 -108
  11. data/lib/test_prof/event_prof/custom_events.rb +30 -0
  12. data/lib/test_prof/event_prof/custom_events/factory_create.rb +1 -1
  13. data/lib/test_prof/event_prof/custom_events/sidekiq_inline.rb +1 -1
  14. data/lib/test_prof/event_prof/custom_events/sidekiq_jobs.rb +1 -1
  15. data/lib/test_prof/event_prof/minitest.rb +6 -0
  16. data/lib/test_prof/event_prof/profiler.rb +129 -0
  17. data/lib/test_prof/event_prof/rspec.rb +20 -11
  18. data/lib/test_prof/factory_all_stub.rb +32 -0
  19. data/lib/test_prof/factory_all_stub/factory_bot_patch.rb +13 -0
  20. data/lib/test_prof/factory_doctor/rspec.rb +3 -2
  21. data/lib/test_prof/factory_prof/printers/flamegraph.rb +9 -13
  22. data/lib/test_prof/recipes/active_record_shared_connection.rb +55 -0
  23. data/lib/test_prof/recipes/logging.rb +37 -0
  24. data/lib/test_prof/recipes/rspec/any_fixture.rb +4 -1
  25. data/lib/test_prof/recipes/rspec/factory_all_stub.rb +10 -0
  26. data/lib/test_prof/rspec_dissect/rspec.rb +4 -2
  27. data/lib/test_prof/rspec_stamp/rspec.rb +3 -2
  28. data/lib/test_prof/tag_prof.rb +4 -0
  29. data/lib/test_prof/tag_prof/printers/html.rb +24 -0
  30. data/lib/test_prof/tag_prof/printers/simple.rb +82 -0
  31. data/lib/test_prof/tag_prof/result.rb +38 -0
  32. data/lib/test_prof/tag_prof/rspec.rb +43 -40
  33. data/lib/test_prof/utils/html_builder.rb +21 -0
  34. data/lib/test_prof/version.rb +1 -1
  35. metadata +20 -24
  36. data/assets/logo.svg +0 -1
  37. data/assets/testprof.png +0 -0
  38. data/guides/.rubocop.yml +0 -1
  39. data/guides/any_fixture.md +0 -114
  40. data/guides/before_all.md +0 -98
  41. data/guides/event_prof.md +0 -177
  42. data/guides/factory_default.md +0 -111
  43. data/guides/factory_doctor.md +0 -119
  44. data/guides/factory_prof.md +0 -86
  45. data/guides/let_it_be.md +0 -97
  46. data/guides/rspec_dissect.md +0 -60
  47. data/guides/rspec_stamp.md +0 -52
  48. data/guides/rubocop.md +0 -48
  49. data/guides/ruby_prof.md +0 -63
  50. data/guides/stack_prof.md +0 -47
  51. data/guides/tag_prof.md +0 -51
  52. data/guides/tests_sampling.md +0 -24
@@ -1,86 +0,0 @@
1
- # FactoryProf
2
-
3
- FactoryProf tracks your factories usage statistics, i.e. how often each factory has been used.
4
-
5
- Example output:
6
-
7
- ```sh
8
- [TEST PROF INFO] Factories usage
9
-
10
- total top-level name
11
-
12
- 8 4 user
13
- 5 3 post
14
- ```
15
-
16
-
17
- It shows both the total number of the factory runs and the number of _top-level_ runs, i.e. not during another factory invocation (e.g. when using associations.)
18
-
19
- **NOTE**: FactoryProf only tracks the database-persisted factories. In case of FactoryGirl/FactoryBot these are the factories
20
- provided by using `create` strategy. In case of Fabrication - objects that created using `create` method.
21
-
22
- ## Instructions
23
-
24
- FactoryProf can be used with FactoryGirl/FactoryBot or Fabrication - application can be bundled with both gems at the same time.
25
-
26
- To activate FactoryProf use `FPROF` environment variable:
27
-
28
- ```sh
29
- # Simple profiler
30
- FPROF=1 rspec
31
-
32
- # or
33
- FPROF=1 bundle exec rake test
34
- ```
35
-
36
- ## Factory Flamegraph
37
-
38
- The most useful feature of FactoryProf is the _FactoryFlame_ report. That's the special interpretation of Brendan Gregg's [flame graphs](http://www.brendangregg.com/flamegraphs.html) which allows you to identify _factory cascades_.
39
-
40
- To generate FactoryFlame report set `FPROF` environment variable to `flamegraph`:
41
-
42
- ```sh
43
- FPROF=flamegraph rspec
44
-
45
- # or
46
- FPROF=flamegraph bundle exec rake test
47
- ```
48
-
49
- That's how a report looks like:
50
-
51
- ![](https://s3-us-west-1.amazonaws.com/vladem/test-prof/factory-flame.gif)
52
-
53
- How to read this?
54
-
55
- Every column represents a _factory stack_ or _cascade_, that is a sequence of recursive `#create` method calls. Consider an example:
56
-
57
- ```ruby
58
- factory :comment do
59
- answer
60
- author
61
- end
62
-
63
- factory :answer do
64
- question
65
- author
66
- end
67
-
68
- factory :question do
69
- author
70
- end
71
-
72
- create(:comment) #=> creates 5 records
73
-
74
- # And the corresponding stack is:
75
- # [:comment, :answer, :question, :author, :author, :author]
76
- ```
77
-
78
- The wider column the more often this stack appears.
79
-
80
- The `root` cell shows the total number of `create` calls.
81
-
82
- ## Acknowledgements
83
-
84
- - Thanks to [Martin Spier](https://github.com/spiermar) for [d3-flame-graph](https://github.com/spiermar/d3-flame-graph)
85
-
86
- - Thanks to [Sam Saffron](https://github.com/SamSaffron) for his [flame graphs implementation](https://github.com/SamSaffron/flamegraph).
@@ -1,97 +0,0 @@
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('john')).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('john')).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
- **NOTE**: requires RSpec >= 3.2.0.
57
-
58
- ## Instructions
59
-
60
- In your `spec_helper.rb`:
61
-
62
- ```ruby
63
- require 'test_prof/recipes/rspec/let_it_be'
64
- ```
65
-
66
- In your tests:
67
-
68
- ```ruby
69
- describe MySuperDryService do
70
- let_it_be(:user) { create(:user) }
71
-
72
- # ...
73
- end
74
- ```
75
-
76
- ## Caveats
77
-
78
- If you modify objects generated within a `let_it_be` block in your examples, you maybe have to re-initiate them.
79
- We have a built-in support for that:
80
-
81
-
82
- ```ruby
83
- # Use reload: true option to reload user object (assuming it's an instance of ActiveRecord)
84
- # for every example
85
- let_it_be(:user, reload: true) { create(:user) }
86
-
87
- # it is almost equal to
88
- before_all { @user = create(:user) }
89
- let(:user) { @user.reload }
90
-
91
- # You can also specify refind: true option to hard-reload the record
92
- let_it_be(:user, refind: true) { create(:user) }
93
-
94
- # it is almost equal to
95
- before_all { @user = create(:user) }
96
- let(:user) { User.find(@user.id) }
97
- ```
@@ -1,60 +0,0 @@
1
- # RSpecDissect
2
-
3
- Do you know how much time you spend in `before` hooks? Or in 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
- **NOTE**: Tracking `let` time only supported in RSpec >= 3.3.0.
35
-
36
- ## Instructions
37
-
38
- RSpecDissect can only be used with RSpec (which is clear from the name).
39
-
40
- To activate RSpecDissect use `RD_PROF` environment variable:
41
-
42
- ```sh
43
- RD_PROF=1 rspec ...
44
- ```
45
-
46
- You can also specify the number of top slow groups through `RD_PROF_TOP` variable:
47
-
48
- ```sh
49
- RD_PROF=1 RD_PROF_TOP=10 rspec ...
50
- ```
51
-
52
- ## Using with RSpecStamp
53
-
54
- 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:
55
-
56
- ```sh
57
- RD_PROF=1 RD_PROF_STAMP="slow" rspec ...
58
- ```
59
-
60
- After running the command above the slowest example groups would be marked with the `:slow` tag.
@@ -1,52 +0,0 @@
1
- # RSpecStamp
2
-
3
- RSpecStamp is a tool to automatically _tag_ failed examples with custom tags.
4
-
5
- It _literally_ adds tags to your examples (i.e. rewrites them).
6
-
7
- The main purpose of RSpecStamp is to make testing codebase refactoring easy. Changing global configuration may cause a lot of failures. You can patch failing spec by adding a shared context. And here comes RSpecStamp.
8
-
9
- ## Example Use Case: Sidekiq Inline
10
-
11
- Using `Sidekiq::Testing.inline!` may be considered a _bad practice_ (see [here](https://github.com/mperham/sidekiq/issues/3495)) due to its negative performance impact. But it's still widely used.
12
-
13
- How to migrate from `inline!` to `fake!`?
14
-
15
- Step 0. Make sure that all your tests pass.
16
-
17
- Step 1. Create a shared context to conditionally turn on `inline!` mode:
18
-
19
- ```ruby
20
- shared_context 'sidekiq:inline', sidekiq: :inline do
21
- around(:each) { |ex| Sidekiq::Testing.inline!(&ex) }
22
- end
23
- ```
24
-
25
- Step 2. Turn on `fake!` mode globally.
26
-
27
- Step 3. Run `RSTAMP=sidekiq:inline rspec`.
28
-
29
- The output of the command above contains information about the _stamping_ process:
30
-
31
- - How many files have been affected?
32
-
33
- - How many patches were made?
34
-
35
- - How many patches failed?
36
-
37
- - How many files have been ignored?
38
-
39
- Now all (or almost all) failing specs are tagged with `sidekiq: :inline`. Run the whole suite again and check it there are any failures left.
40
-
41
- There is also a `dry-run` mode (activated by `RSTAMP_DRY_RUN=1` env variable) which prints out patches instead of re-writing files.
42
-
43
- ## Configuration
44
-
45
- By default, RSpecStamp ignores examples located in `spec/support` directory (typical place to put shared examples in).
46
- You can add more _ignore_ patterns:
47
-
48
- ```ruby
49
- TestProf::RSpecStamp.configure do |config|
50
- config.ignore_files << %r{spec/my_directory}
51
- end
52
- ```
@@ -1,48 +0,0 @@
1
- # Custom RuboCop Cops
2
-
3
- TestProf comes with the [RuboCop](https://github.com/bbatsov/rubocop) cops that help you write more performant tests.
4
-
5
- To enable them:
6
-
7
- - Require `test_prof/rubocop` in your RuboCop configuration:
8
-
9
- ```yml
10
- # .rubocop.yml
11
- require:
12
- - 'test_prof/rubocop'
13
- ```
14
-
15
- - Enable cops:
16
-
17
- ```yml
18
- RSpec/AggregateFailures:
19
- Enabled: true
20
- Include:
21
- - 'spec/**/*.rb'
22
- ```
23
-
24
- ## RSpec/AggregateFailures
25
-
26
- This cop encourages you to use one of the greatest features of the recent RSpec – aggregating failures within an example.
27
-
28
- Instead of writing one example per assertion, you can group _independent_ assertions together, thus running all setup hooks only once. That can dramatically increase your performance (by reducing the total number of examples).
29
-
30
- Consider an example:
31
-
32
- ```ruby
33
- # bad
34
- it { is_expected.to be_success }
35
- it { is_expected.to have_header('X-TOTAL-PAGES', 10) }
36
- it { is_expected.to have_header('X-NEXT-PAGE', 2) }
37
-
38
- # good
39
- it 'returns the second page', :aggregate_failures do
40
- is_expected.to be_success
41
- is_expected.to have_header('X-TOTAL-PAGES', 10)
42
- is_expected.to have_header('X-NEXT-PAGE', 2)
43
- end
44
- ```
45
-
46
- This cop supports auto-correct feature, so you can automatically refactor you legacy tests!
47
-
48
- **NOTE**: auto-correction may break your tests (especially the ones using block-matchers, such as `change`).
@@ -1,63 +0,0 @@
1
- # Profiling with RubyProf
2
-
3
- Easily integrate the power of [ruby-prof](https://github.com/ruby-prof/ruby-prof) into your test suite.
4
-
5
- ## Instructions
6
-
7
- Install `ruby-prof` gem (>= 0.16):
8
-
9
- ```ruby
10
- # Gemfile
11
- group :development, :test do
12
- gem 'ruby-prof', '>= 0.16.0', require: false
13
- end
14
- ```
15
-
16
- RubyProf profiler has two modes: `global` and `per-example`.
17
-
18
- You can activate the global profiling using the environment variable `TEST_RUBY_PROF`:
19
-
20
- ```sh
21
- TEST_RUBY_PROF=1 bundle exec rake test
22
-
23
- # or for RSpec
24
- TEST_RUBY_PROF=1 rspec ...
25
- ```
26
-
27
- Or in your code:
28
-
29
- ```ruby
30
- TestProf::RubyProf.run
31
- ```
32
-
33
- TestProf provides a built-in shared context for RSpec to profile examples individually:
34
-
35
- ```ruby
36
- it 'is doing heavy stuff', :rprof do
37
- # ...
38
- end
39
- ```
40
-
41
- ### Configuration
42
-
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
-
45
- You can specify a printer through environment variable `TEST_RUBY_PROF`:
46
-
47
- ```sh
48
- TEST_RUBY_PROF=call_stack bundle exec rake test
49
- ```
50
-
51
- Or in your code:
52
-
53
- ```ruby
54
- TestProf::RubyProf.configure do |config|
55
- config.printer = :call_stack
56
- end
57
- ```
58
-
59
- By default, we use `FlatPrinter`.
60
-
61
- Also, you can specify RubyProf mode (`wall`, `cpu`, etc) through `TEST_RUBY_PROF_MODE` env variable.
62
-
63
- See [ruby_prof.rb](https://github.com/palkan/test-prof/tree/master/lib/test_prof/ruby_prof.rb) for all available configuration options and their usage.
@@ -1,47 +0,0 @@
1
- # Profiling with StackProf
2
-
3
- [StackProf](https://github.com/tmm1/stackprof) is a sampling call-stack profiler for ruby.
4
-
5
- ## Instructions
6
-
7
- Install `stackprof` gem (>= 0.2.9):
8
-
9
- ```ruby
10
- # Gemfile
11
- group :development, :test do
12
- gem 'stackprof', '>= 0.2.9', require: false
13
- end
14
- ```
15
-
16
- StackProf profiler has 2 modes: `global` and `per-example`.
17
-
18
- You can activate global profiling using the environment variable `TEST_STACK_PROF`:
19
-
20
- ```sh
21
- TEST_STACK_PROF=1 bundle exec rake test
22
-
23
- # or for RSpec
24
- TEST_STACK_PROF=1 rspec ...
25
- ```
26
-
27
- Or in your code:
28
-
29
- ```ruby
30
- TestProf::StackProf.run
31
- ```
32
-
33
- TestProf provides a built-in shared context for RSpec to profile examples individually:
34
-
35
- ```ruby
36
- it 'is doing heavy stuff', :sprof do
37
- # ...
38
- end
39
- ```
40
-
41
- ### Configuration
42
-
43
- You can change StackProf mode (which is `wall` by default) through `TEST_STACK_PROF_MODE` env variable.
44
-
45
- If you want to generate flame graphs you should collect _raw_ data. Turn _raw_ collection on by passing `TEST_STACK_PROF=raw`.
46
-
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.