test-prof 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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