test-prof 0.5.0 → 0.6.0.pre1

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
  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