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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc2f073e4331955ed514e20849bb5dadf28d4f5934254ae3d30ca2c60db90ff0
4
- data.tar.gz: d07ba0a976611946258de85284b697d0ccf7adbdeaae0363efebd868156100e4
3
+ metadata.gz: cb97ad09442483992d4d0618362de046dae7a5ed4e17f909a8bcbbffe9fc827f
4
+ data.tar.gz: d477bbede59c0b94f21181e830bef6f346c1abb6d6fb1c683a582ad237ec9c2a
5
5
  SHA512:
6
- metadata.gz: c942589dbd72c954f54a4cb874d91f98a2c9a64c604fb82b32476bcf2ea9b82fbfa72222e32b0cfec52fc1b335b2c9e69d99567be21e91b8084005a94ed9963a
7
- data.tar.gz: e90718e07df7b566c657c9192805a4a1f523196578699f7cbc93f3cf25ddf09634bcb0bda35e5e18a9f1501708e583748df4575b721c20886605ca1c863efff6
6
+ metadata.gz: d0f049f6e75ed08c3474f58cdbca386ca3c7418223abd51a3e9c7d3d8ab207c68527133b2901de725531044a778de48b8dbf3eab1243bdc16e60f53ed9e2ea80
7
+ data.tar.gz: e97987b38878cdf5b29328204fa39d6e364ddbc84fff8bfe322449b02ec2bc4203417f35cc2d9fb82f79dc88c99e4241a1f5979ea11987175d5c806a8ec56965
@@ -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
 
@@ -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
- ::Spring.after_fork { activate!(env_var, val) { yield } }
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 its
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
@@ -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: config.resolve_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
- def self.subscribe(event)
8
- raise ArgumentError, 'Block is required!' unless block_given?
7
+ class << self
8
+ def subscribe(event)
9
+ raise ArgumentError, 'Block is required!' unless block_given?
9
10
 
10
- ::ActiveSupport::Notifications.subscribe(event) do |_event, start, finish, *_args|
11
- yield (finish - start)
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
- before do
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
- if ActiveRecord::VERSION::MAJOR < 4
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
- def before_all(&block)
12
- raise ArgumentError, "Block is required!" unless block_given?
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
- return before(:all, &block) if within_before_all?
12
+ return before(:all, &block) if within_before_all?
15
13
 
16
- @__before_all_activated__ = true
14
+ @__before_all_activated__ = true
17
15
 
18
- before(:all) do
19
- ActiveRecord::Base.connection.begin_transaction(joinable: false)
20
- instance_eval(&block)
21
- end
16
+ before(:all) do
17
+ BeforeAll.begin_transaction
18
+ instance_eval(&block)
19
+ end
22
20
 
23
- after(:all) do
24
- ActiveRecord::Base.connection.rollback_transaction
21
+ after(:all) do
22
+ BeforeAll.rollback_transaction
23
+ end
25
24
  end
26
- end
27
25
 
28
- def within_before_all?
29
- instance_variable_defined?(:@__before_all_activated__)
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[:_rspec_dissect_memo_depth] ||= 0
20
- Thread.current[:_rspec_dissect_memo_depth] += 1
19
+ Thread.current[:_rspec_dissect_let_depth] ||= 0
20
+ Thread.current[:_rspec_dissect_let_depth] += 1
21
21
  begin
22
- res = if Thread.current[:_rspec_dissect_memo_depth] == 1
23
- RSpecDissect.track(:memo) { super }
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[:_rspec_dissect_memo_depth] -= 1
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
- attr_accessor :top_count
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 memo].freeze
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
- METRICS.each do |type|
104
- define_method("#{type}_time") do
105
- @data[type.to_s]
106
- end
131
+ def time_for(key)
132
+ @data[key.to_s][:time]
133
+ end
107
134
 
108
- define_method("total_#{type}_time") do
109
- @data["total_#{type}"]
110
- end
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
- @before_results = Utils::SizedOrderedSet.new(
25
- top_count, sort_by: :before
26
- )
27
- @memo_results = Utils::SizedOrderedSet.new(
28
- top_count, sort_by: :memo
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
- @before_results << data
55
- @memo_results << data
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
- @before_results.each do |group|
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
- if RSpecDissect.memoization_available?
96
- msgs <<
97
- <<-MSG.strip_heredoc
76
+ msgs << "\n"
98
77
 
99
- Top #{top_count} slowest suites (by `let` time):
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
- (@before_results.to_a + @memo_results.to_a)
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
 
@@ -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
- @raw = ENV['TEST_STACK_PROF'] == 'raw' || ENV['TEST_STACK_PROF_RAW'] == 1
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TestProf
4
- VERSION = "0.5.0".freeze
4
+ VERSION = "0.6.0.pre1".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.5.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-05-25 00:00:00.000000000 Z
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.56.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.56.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: '0'
214
+ version: 1.3.1
208
215
  requirements: []
209
216
  rubyforge_project:
210
217
  rubygems_version: 2.7.6