test-prof 0.3.0 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eb103d86ccc7a513de8b30ae8ddf536c9bbc6255
4
- data.tar.gz: f0fe1c206faab5761b4419ccbf8745f4060d3e82
3
+ metadata.gz: d45463f21e439e42506d700ace1dcc6202869ee8
4
+ data.tar.gz: 1f6cd4e4a1161cfacda3f6f95371408131e4e49f
5
5
  SHA512:
6
- metadata.gz: f6a4881cfc954438fa4184c2cd9be1784d10bfcd6d92931ea5132c8422aa08866b77e60ec91066ddd6afb620c524e99b4584818d158f396176f9649ef3a0e038
7
- data.tar.gz: a699bd6e308b370aa4e53447025551a5b2f30249eb893356d30238eaed4dc135bb69335c780ea9d0b306ce569bca29117494ab91b6a70bbb7a22f714a65f827f
6
+ metadata.gz: 816ca6fed6eb159671eb0563294daae38ec832e7a6530eec7c6cefe16bf66d96d7014c5d3a110a3c207f0cbaa49425cab335fc41b5d7be557e2ad07aee5753ae
7
+ data.tar.gz: a22cd5e4f92f5e47918132d5646a53593a756cfb27fb88f00152d8e58ca6ec8f0479893f99698cd50f9c4ee26da79ba04351498552224b745a4b0f9376a17179
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Change log
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Features:
6
+
7
+ - [#29](https://github.com/palkan/test-prof/pull/29) EventProf Minitest integration. ([@IDolgirev][])
8
+
9
+ It is possible now to use Event Prof with Minitest
10
+
11
+ - [#30](https://github.com/palkan/test-prof/pull/30) Fabrication support for FactoryProf. ([@Shkrt][])
12
+
13
+ FactoryProf now also accounts objects created by Fabrication gem (in addition to FactoryGirl)
14
+
3
15
  ## 0.3.0
4
16
 
5
17
  ### Features:
@@ -20,7 +32,7 @@ After running the command above the top 5 slowest example groups would be marked
20
32
 
21
33
  - [#14](https://github.com/palkan/test-prof/pull/14) RSpecDissect profiler. ([@palkan][])
22
34
 
23
- RSpecDissect tracks how much time do you spend in `before` hooks
35
+ RSpecDissect tracks how much time do you spend in `before` hooks
24
36
  and memoization helpers (i.e. `let`) in your tests.
25
37
 
26
38
  - [#13](https://github.com/palkan/test-prof/pull/13) RSpec `let_it_be` method. ([@palkan][])
@@ -88,4 +100,7 @@ Ensure output dir exists in `#artifact_path` method.
88
100
 
89
101
  [@palkan]: https://github.com/palkan
90
102
  [@marshall-lee]: https://github.com/marshall-lee
91
- [@danielwestendorf]: https://github.com/danielwestendorf
103
+ [@danielwestendorf]: https://github.com/danielwestendorf
104
+ [@Shkrt]: https://github.com/Shkrt
105
+ [@IDolgirev]: https://github.com/IDolgirev
106
+
data/README.md CHANGED
@@ -22,6 +22,8 @@ TestProf toolbox aims to help you identify bottlenecks in your test suite. It co
22
22
 
23
23
  Of course, we have some [solutions](#tips-and-tricks-or-recipes) for common performance issues too, bundled into the gem.
24
24
 
25
+ [![](https://s3-us-west-1.amazonaws.com/vladem/test-prof/TestProf.png)](https://coggle.it/diagram/Wa7tW-87jAAB0ExN)
26
+
25
27
  See [Table of Contents](#table-of-contents) for more.
26
28
 
27
29
  Supported Ruby versions:
@@ -112,8 +114,6 @@ Or TODO list:
112
114
 
113
115
  - Better Minitest integration (PRs welcome!)
114
116
 
115
- - Other data generation library support (e.g [Fabricator](http://fabricationgem.org/)). _Does anyone use something except from FactoryGirl?_
116
-
117
117
  - Improve FactoryDoctor
118
118
 
119
119
  - Add more Rubocop cops (e.g. `CreateListLimit`)
@@ -58,3 +58,58 @@ require "test_prof/recipes/rspec/any_fixture"
58
58
  ```
59
59
 
60
60
  Now you can use `TestProf::AnyFixture` in your tests.
61
+
62
+ ## Caveats
63
+
64
+ `AnyFixture` cleans tables in the reverse order as compared to the order they were populated. That
65
+ means when you register a fixture which references a not-yet-registered table, a
66
+ foreign-key violation error *might* occur (if any). An example is worth more than 1000
67
+ words:
68
+
69
+ ```ruby
70
+ class Author < ApplicationRecord
71
+ has_many :articles
72
+ end
73
+
74
+ class Article < ApplicationRecord
75
+ belongs_to :author
76
+ end
77
+ ```
78
+
79
+ And the shared contexts:
80
+
81
+ ```ruby
82
+ RSpec.shared_context "author" do
83
+ before(:all) do
84
+ @author = TestProf::AnyFixture.register(:author) do
85
+ FactoryGirl.create(:account)
86
+ end
87
+ end
88
+
89
+ let(:author) { @author }
90
+ end
91
+
92
+ RSpec.shared_context "article" do
93
+ before(:all) do
94
+ # outside of AnyFixture, we don't know about its dependent tables
95
+ author = FactoryGirl.create(:author)
96
+
97
+ @article = TestProf::AnyFixture.register(:article) do
98
+ FactoryGirl.create(:article, author: author)
99
+ end
100
+ end
101
+
102
+ let(:article) { @article }
103
+ end
104
+ ```
105
+
106
+ Then in some example:
107
+
108
+ ```ruby
109
+ # This one adds only the 'articles' table to the list of affected tables
110
+ include_context "article"
111
+ # And this one adds the 'authors' table
112
+ include_context "author"
113
+ ```
114
+
115
+ Now we have the following affected tables list: `["articles", "authors"]`. At the end of the suite, the "authors" table is cleaned first which leads to a foreign-key violation error.
data/guides/event_prof.md CHANGED
@@ -30,15 +30,53 @@ fails (./spec/shared_examples/controllers/invalid_examples.rb:3) – 00:00.007 (
30
30
 
31
31
  ## Instructions
32
32
 
33
- Currently, EventProf supports only ActiveSupport::Notifications and RSpec.
33
+ Currently, EventProf supports only ActiveSupport::Notifications
34
34
 
35
- To activate EventProf use `EVENT_PROF` environment variable set to event name:
35
+ To activate EventProf with:
36
+
37
+ ### Rspec
38
+
39
+ Use `EVENT_PROF` environment variable set to event name:
36
40
 
37
41
  ```sh
38
42
  # Collect SQL queries stats for every suite and example
39
43
  EVENT_PROF='sql.active_record' rspec ...
40
44
  ```
41
45
 
46
+ ### Minitest
47
+
48
+ Use `EVENT_PROF` environment variable set to event name:
49
+
50
+ ```sh
51
+ # Collect SQL queries stats for every suite and example
52
+ EVENT_PROF='sql.active_record' rake test
53
+ ```
54
+ or use CLI options as well:
55
+
56
+ ```sh
57
+ # Run a specific file using CLI option
58
+ ruby test/my_super_test.rb --event-prof=sql.active_record
59
+
60
+ # Show the list of possible options:
61
+ ruby test/my_super_test.rb --help
62
+ ```
63
+
64
+ ### Using with Minitest::Reporters
65
+
66
+ If you're using `Minitest::Reporters` in your project you have to explicitly declare it
67
+ in your test helper file:
68
+
69
+ ```sh
70
+ require 'minitest/reporters'
71
+ Minitest::Reporters.use! [YOUR_FAVORITE_REPORTERS]
72
+ ```
73
+ #### NOTICE
74
+ When you have `minitest-reporters` installed as a gem but not declared in your `Gemfile`
75
+ make sure to always prepend your test run command with `bundle exec` (but we sure that you always do it).
76
+ Otherwise, you'll get an error caused by Minitest plugin system, which scans all the entries in the
77
+ `$LOAD_PATH` for any `minitest/*_plugin.rb`, thus initialization of `minitest-reporters` plugin which is
78
+ available in that case doesn't happens correctly.
79
+
42
80
  See [Rails guides](http://guides.rubyonrails.org/active_support_instrumentation.html)
43
81
  for the list of available events if you're using Rails.
44
82
 
@@ -16,11 +16,12 @@ Example output:
16
16
 
17
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
18
 
19
- **NOTE**: FactoryProf only tracks the usage of `create` strategy.
19
+ **NOTE**: FactoryProf only tracks the database-persisted factories. In case of FactoryFirl these are the factories
20
+ provided by using `create` strategy. In case of Fabrication - objects that created using `create` method.
20
21
 
21
22
  ## Instructions
22
23
 
23
- FactoryProf can only be used with FactoryGirl.
24
+ FactoryProf can be used with FactoryGirl or Fabrication - application can be bundled with both gems at the same time.
24
25
 
25
26
  To activate FactoryProf use `FPROF` environment variable:
26
27
 
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_prof/event_prof/minitest'
4
+
5
+ module Minitest # :nodoc:
6
+ def self.plugin_event_prof_options(opts, options)
7
+ opts.on "--event-prof=EVENT", "Collects metrics for specified EVENT" do |event|
8
+ options[:event] = event
9
+ end
10
+ opts.on "--event-prof-rank-by=RANK_BY", "Defines RANK_BY parameter for results" do |rank|
11
+ options[:rank_by] = rank.to_sym
12
+ end
13
+ opts.on "--event-prof-top-count=N", "Limits results with N groups/examples" do |count|
14
+ options[:top_count] = count.to_i
15
+ end
16
+ opts.on "--event-prof-per-example", TrueClass, "Includes examples metrics to results" do |flag|
17
+ options[:per_example] = flag
18
+ end
19
+ end
20
+
21
+ def self.plugin_event_prof_init(options)
22
+ options[:event] = ENV['EVENT_PROF'] if ENV['EVENT_PROF']
23
+ options[:rank_by] = ENV['EVENT_PROF_RANK'].to_sym if ENV['EVENT_PROF_RANK']
24
+ options[:top_count] = ENV['EVENT_PROF_TOP'].to_i if ENV['EVENT_PROF_TOP']
25
+ options[:per_example] = true if ENV['EVENT_PROF_EXAMPLES']
26
+
27
+ return unless options[:event]
28
+ reporter << EventProfReporter.new(options[:io], options)
29
+ end
30
+ end
@@ -186,5 +186,5 @@ module TestProf
186
186
  end
187
187
 
188
188
  require "test_prof/event_prof/rspec" if defined?(RSpec::Core)
189
- require "test_prof/event_prof/minitest" if defined?(Minitest::Reporters)
189
+ require "test_prof/event_prof/minitest" if defined?(Minitest)
190
190
  require "test_prof/event_prof/custom_events"
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_prof/ext/float_duration"
4
+ require "test_prof/ext/string_truncate"
5
+ require "test_prof/ext/string_strip_heredoc"
6
+
7
+ module TestProf
8
+ module EventProf
9
+ class MinitestFormatter # :nodoc:
10
+ using FloatDuration
11
+ using StringTruncate
12
+ using StringStripHeredoc
13
+
14
+ def initialize(profiler)
15
+ @profiler = profiler
16
+ @results = []
17
+ end
18
+
19
+ def prepare_results
20
+ total_results
21
+ by_groups
22
+ by_examples
23
+ @results.join
24
+ end
25
+
26
+ private
27
+
28
+ def total_results
29
+ @results <<
30
+ <<-MSG.strip_heredoc
31
+ EventProf results for #{@profiler.event}
32
+
33
+ Total time: #{@profiler.total_time.duration}
34
+ Total events: #{@profiler.total_count}
35
+
36
+ Top #{@profiler.top_count} slowest suites (by #{@profiler.rank_by}):
37
+
38
+ MSG
39
+ end
40
+
41
+ def by_groups
42
+ @profiler.results[:groups].each do |group|
43
+ description = group[:id][:name]
44
+ location = group[:id][:location]
45
+
46
+ @results <<
47
+ <<-GROUP.strip_heredoc
48
+ #{description.truncate} (#{location}) – #{group[:time].duration} (#{group[:count]} / #{group[:examples]})
49
+ GROUP
50
+ end
51
+ end
52
+
53
+ def by_examples
54
+ return unless @profiler.results[:examples]
55
+ @results << "\nTop #{@profiler.top_count} slowest tests (by #{@profiler.rank_by}):\n\n"
56
+
57
+ @profiler.results[:examples].each do |example|
58
+ description = example[:id][:name]
59
+ location = example[:id][:location]
60
+
61
+ @results <<
62
+ <<-GROUP.strip_heredoc
63
+ #{description.truncate} (#{location}) – #{example[:time].duration} (#{example[:count]})
64
+ GROUP
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,3 +1,90 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # TODO: write Minitest reporter
3
+ require 'test_prof/logging'
4
+ require 'test_prof/event_prof/formatters/minitest'
5
+
6
+ module Minitest
7
+ class EventProfReporter < AbstractReporter # :nodoc:
8
+ include TestProf::Logging
9
+
10
+ attr_accessor :io
11
+
12
+ def initialize(io = $stdout, options = {})
13
+ @io = io
14
+ @profiler = configure_profiler(options)
15
+ @formatter = TestProf::EventProf::MinitestFormatter.new(@profiler)
16
+ @current_group = nil
17
+ @current_example = nil
18
+ inject_to_minitest_reporters if defined? Minitest::Reporters
19
+ end
20
+
21
+ def start; end
22
+
23
+ def prerecord(group, example)
24
+ change_current_group(group, example) unless @current_group
25
+ track_current_example(group, example)
26
+ end
27
+
28
+ def before_test(test)
29
+ prerecord(test.class, test.name)
30
+ end
31
+
32
+ def record(*)
33
+ @profiler.example_finished(@current_example)
34
+ end
35
+
36
+ def after_test(*); end
37
+
38
+ def report
39
+ @profiler.group_finished(@current_group)
40
+ result = @formatter.prepare_results
41
+ puts "\n"
42
+ log :info, result
43
+ end
44
+
45
+ private
46
+
47
+ def track_current_example(group, example)
48
+ unless @current_group[:name] == group.name
49
+ @profiler.group_finished(@current_group)
50
+ change_current_group(group, example)
51
+ end
52
+
53
+ @current_example = {
54
+ name: example.gsub(/^test_(?:\d+_)?/, ''),
55
+ location: File.expand_path(location(group, example).join(':')).gsub(Dir.getwd, '.')
56
+ }
57
+
58
+ @profiler.example_started(@current_example)
59
+ end
60
+
61
+ def change_current_group(group, example)
62
+ @current_group = {
63
+ name: group.name,
64
+ location: File.expand_path(location(group, example).first).gsub(Dir.getwd, '.')
65
+ }
66
+
67
+ @profiler.group_started(@current_group)
68
+ end
69
+
70
+ def location(group, example)
71
+ suite = group.public_instance_methods.select { |mtd| mtd.to_s.match /^test_/ }
72
+ name = suite.find { |mtd| mtd.to_s == example }
73
+ group.instance_method(name).source_location
74
+ end
75
+
76
+ def configure_profiler(options)
77
+ TestProf::EventProf.configure do |config|
78
+ config.event = options[:event]
79
+ config.rank_by = options[:rank_by] if options[:rank_by]
80
+ config.top_count = options[:top_count] if options[:top_count]
81
+ config.per_example = options[:per_example] if options[:per_example]
82
+ end
83
+ TestProf::EventProf.build
84
+ end
85
+
86
+ def inject_to_minitest_reporters
87
+ Minitest::Reporters.reporters << self if Minitest::Reporters.reporters
88
+ end
89
+ end
90
+ end
@@ -1,13 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "test_prof/factory_prof/factory_girl_patch"
4
3
  require "test_prof/factory_prof/printers/simple"
5
4
  require "test_prof/factory_prof/printers/flamegraph"
5
+ require "test_prof/factory_prof/factory_builders/factory_girl"
6
+ require "test_prof/factory_prof/factory_builders/fabrication"
6
7
 
7
8
  module TestProf
8
9
  # FactoryProf collects "factory stacks" that can be used to build
9
10
  # flamegraphs or detect most popular factories
10
11
  module FactoryProf
12
+ FACTORY_BUILDERS = [FactoryBuilders::FactoryGirl,
13
+ FactoryBuilders::Fabrication].freeze
14
+
11
15
  # FactoryProf configuration
12
16
  class Configuration
13
17
  attr_accessor :mode
@@ -69,9 +73,7 @@ module TestProf
69
73
 
70
74
  log :info, "FactoryProf enabled (#{config.mode} mode)"
71
75
 
72
- # Monkey-patch FactoryGirl
73
- ::FactoryGirl::FactoryRunner.prepend(FactoryGirlPatch) if
74
- defined?(::FactoryGirl)
76
+ FACTORY_BUILDERS.each(&:patch)
75
77
  end
76
78
 
77
79
  # Inits FactoryProf and setups at exit hook,
@@ -99,8 +101,8 @@ module TestProf
99
101
  Result.new(@stacks, @stats)
100
102
  end
101
103
 
102
- def track(strategy, factory)
103
- return yield if !running? || (strategy != :create)
104
+ def track(factory)
105
+ return yield unless running?
104
106
  begin
105
107
  @depth += 1
106
108
  @current_stack << factory if config.flamegraph?
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TestProf
4
+ module FactoryProf
5
+ # Wrap #run method with FactoryProf tracking
6
+ module FabricationPatch
7
+ def create(name, overrides = {})
8
+ FactoryBuilders::Fabrication.track(name) { super }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_prof/factory_prof/fabrication_patch"
4
+
5
+ module TestProf
6
+ module FactoryProf
7
+ module FactoryBuilders
8
+ # implementation of #patch and #track methods
9
+ # to provide unified interface for all factory-building gems
10
+ class Fabrication
11
+ # Monkey-patch Fabrication
12
+ def self.patch
13
+ TestProf.require 'fabrication', ""
14
+ ::Fabricate.singleton_class.prepend(FabricationPatch) if
15
+ defined?(::Fabrication)
16
+ end
17
+
18
+ def self.track(factory, &block)
19
+ FactoryProf.track(factory, &block)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_prof/factory_prof/factory_girl_patch"
4
+
5
+ module TestProf
6
+ module FactoryProf
7
+ module FactoryBuilders
8
+ # implementation of #patch and #track methods
9
+ # to provide unified interface for all factory-building gems
10
+ class FactoryGirl
11
+ # Monkey-patch FactoryGirl
12
+ def self.patch
13
+ ::FactoryGirl::FactoryRunner.prepend(FactoryGirlPatch) if
14
+ defined?(::FactoryGirl)
15
+ end
16
+
17
+ def self.track(strategy, factory, &block)
18
+ return yield unless strategy == :create
19
+ FactoryProf.track(factory, &block)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -5,7 +5,7 @@ module TestProf
5
5
  # Wrap #run method with FactoryProf tracking
6
6
  module FactoryGirlPatch
7
7
  def run(strategy = @strategy)
8
- FactoryProf.track(strategy, @name) { super }
8
+ FactoryBuilders::FactoryGirl.track(strategy, @name) { super }
9
9
  end
10
10
  end
11
11
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TestProf
4
- VERSION = "0.3.0".freeze
4
+ VERSION = "0.4.0".freeze
5
5
  end
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.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-21 00:00:00.000000000 Z
11
+ date: 2017-10-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -81,7 +81,7 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0.50'
83
83
  description: "\n Ruby applications tests profiling tools.\n\n Contains tools
84
- to anylyze factories usage, integrate with Ruby profilers,\n profile your examples
84
+ to analyze factories usage, integrate with Ruby profilers,\n profile your examples
85
85
  using ActiveSupport notifications (if any) and\n statically analyze your code
86
86
  with custom Rubocop cops.\n "
87
87
  email:
@@ -116,6 +116,7 @@ files:
116
116
  - guides/stack_prof.md
117
117
  - guides/tag_prof.md
118
118
  - guides/tests_sampling.md
119
+ - lib/minitest/event_prof_plugin.rb
119
120
  - lib/test-prof.rb
120
121
  - lib/test_prof.rb
121
122
  - lib/test_prof/any_fixture.rb
@@ -125,6 +126,7 @@ files:
125
126
  - lib/test_prof/event_prof/custom_events/factory_create.rb
126
127
  - lib/test_prof/event_prof/custom_events/sidekiq_inline.rb
127
128
  - lib/test_prof/event_prof/custom_events/sidekiq_jobs.rb
129
+ - lib/test_prof/event_prof/formatters/minitest.rb
128
130
  - lib/test_prof/event_prof/instrumentations/active_support.rb
129
131
  - lib/test_prof/event_prof/minitest.rb
130
132
  - lib/test_prof/event_prof/rspec.rb
@@ -139,6 +141,9 @@ files:
139
141
  - lib/test_prof/factory_doctor/minitest.rb
140
142
  - lib/test_prof/factory_doctor/rspec.rb
141
143
  - lib/test_prof/factory_prof.rb
144
+ - lib/test_prof/factory_prof/fabrication_patch.rb
145
+ - lib/test_prof/factory_prof/factory_builders/fabrication.rb
146
+ - lib/test_prof/factory_prof/factory_builders/factory_girl.rb
142
147
  - lib/test_prof/factory_prof/factory_girl_patch.rb
143
148
  - lib/test_prof/factory_prof/printers/flamegraph.rb
144
149
  - lib/test_prof/factory_prof/printers/simple.rb
@@ -184,7 +189,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
184
189
  version: '0'
185
190
  requirements: []
186
191
  rubyforge_project:
187
- rubygems_version: 2.6.11
192
+ rubygems_version: 2.6.13
188
193
  signing_key:
189
194
  specification_version: 4
190
195
  summary: Ruby applications tests profiling tools