test-prof 1.3.0 → 1.4.4

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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +78 -0
  3. data/README.md +2 -2
  4. data/lib/minitest/test_prof_plugin.rb +3 -0
  5. data/lib/test_prof/any_fixture/dsl.rb +19 -1
  6. data/lib/test_prof/any_fixture/dump.rb +1 -1
  7. data/lib/test_prof/any_fixture.rb +16 -2
  8. data/lib/test_prof/before_all/adapters/active_record.rb +82 -47
  9. data/lib/test_prof/before_all/isolator.rb +6 -15
  10. data/lib/test_prof/before_all.rb +31 -8
  11. data/lib/test_prof/cops/rspec/aggregate_examples/its.rb +2 -2
  12. data/lib/test_prof/cops/rspec/aggregate_examples/line_range_helpers.rb +1 -1
  13. data/lib/test_prof/cops/rspec/aggregate_examples/matchers_with_side_effects.rb +1 -1
  14. data/lib/test_prof/cops/rspec/aggregate_examples/metadata_helpers.rb +1 -1
  15. data/lib/test_prof/cops/rspec/aggregate_examples/node_matchers.rb +1 -1
  16. data/lib/test_prof/cops/rspec/aggregate_examples.rb +12 -18
  17. data/lib/test_prof/core.rb +28 -1
  18. data/lib/test_prof/event_prof/monitor.rb +3 -10
  19. data/lib/test_prof/ext/active_record_refind.rb +1 -1
  20. data/lib/test_prof/ext/string_truncate.rb +1 -1
  21. data/lib/test_prof/factory_bot.rb +2 -0
  22. data/lib/test_prof/factory_default/fabrication_patch.rb +60 -0
  23. data/lib/test_prof/factory_default/factory_bot_patch.rb +59 -9
  24. data/lib/test_prof/factory_default.rb +11 -39
  25. data/lib/test_prof/factory_doctor.rb +1 -2
  26. data/lib/test_prof/factory_prof/fabrication_patch.rb +7 -1
  27. data/lib/test_prof/factory_prof/factory_bot_patch.rb +15 -1
  28. data/lib/test_prof/factory_prof/factory_builders/fabrication.rb +2 -2
  29. data/lib/test_prof/factory_prof/factory_builders/factory_bot.rb +2 -2
  30. data/lib/test_prof/factory_prof/printers/json.rb +41 -0
  31. data/lib/test_prof/factory_prof/printers/nate_heckler.rb +1 -1
  32. data/lib/test_prof/factory_prof/printers/simple.rb +23 -4
  33. data/lib/test_prof/factory_prof.rb +57 -25
  34. data/lib/test_prof/memory_prof/printer.rb +2 -0
  35. data/lib/test_prof/memory_prof/rspec.rb +7 -4
  36. data/lib/test_prof/memory_prof/tracker/linked_list.rb +8 -6
  37. data/lib/test_prof/memory_prof/tracker.rb +14 -10
  38. data/lib/test_prof/recipes/minitest/before_all.rb +2 -2
  39. data/lib/test_prof/recipes/minitest/sample.rb +14 -5
  40. data/lib/test_prof/recipes/rspec/any_fixture.rb +8 -0
  41. data/lib/test_prof/recipes/rspec/let_it_be.rb +19 -0
  42. data/lib/test_prof/rspec_stamp/parser.rb +1 -1
  43. data/lib/test_prof/rspec_stamp.rb +7 -5
  44. data/lib/test_prof/ruby_prof/rspec_no_boot.rb +15 -0
  45. data/lib/test_prof/ruby_prof.rb +20 -7
  46. data/lib/test_prof/tag_prof/minitest.rb +74 -0
  47. data/lib/test_prof/tag_prof/result.rb +2 -2
  48. data/lib/test_prof/tps_prof/profiler.rb +55 -0
  49. data/lib/test_prof/tps_prof/reporter/text.rb +48 -0
  50. data/lib/test_prof/tps_prof/rspec.rb +63 -0
  51. data/lib/test_prof/tps_prof.rb +46 -0
  52. data/lib/test_prof/vernier.rb +3 -1
  53. data/lib/test_prof/version.rb +1 -1
  54. data/lib/test_prof.rb +1 -0
  55. metadata +15 -7
@@ -3,6 +3,8 @@
3
3
  module TestProf # :nodoc: all
4
4
  FACTORY_GIRL_NAMES = {"factory_bot" => "::FactoryBot", "factory_girl" => "::FactoryGirl"}.freeze
5
5
 
6
+ TestProf.require("active_support")
7
+
6
8
  FACTORY_GIRL_NAMES.find do |name, cname|
7
9
  TestProf.require(name) do
8
10
  TestProf::FactoryBot = Object.const_get(cname)
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TestProf
4
+ module FactoryDefault # :nodoc: all
5
+ module FabricationPatch
6
+ module DefaultExt
7
+ def create_default(name, overrides = {}, &block)
8
+ obj = ::Fabricate.create(name, overrides, &block)
9
+ set_fabricate_default(name, obj)
10
+ end
11
+
12
+ def set_fabricate_default(name, obj, **opts)
13
+ FactoryDefault.register(
14
+ name, obj,
15
+ preserve_attributes: FactoryDefault.config.preserve_attributes,
16
+ preserve_traits: FactoryDefault.config.preserve_traits,
17
+ **opts
18
+ )
19
+ end
20
+
21
+ def get_fabricate_default(name, **overrides)
22
+ FactoryDefault.get(name, nil, overrides, skip_stats: true)
23
+ end
24
+
25
+ def skip_fabricate_default(&block)
26
+ FactoryDefault.disable!(&block)
27
+ end
28
+
29
+ def create(name, overrides = {}, &block)
30
+ self.fabrication_depth += 1
31
+ # We do not support defaults for objects created with attribute blocks
32
+ return super if block
33
+
34
+ return super if fabrication_depth < 2
35
+
36
+ FactoryDefault.get(name, nil, overrides, **{}) ||
37
+ FactoryDefault.profiler.instrument(name, nil, overrides) { super }
38
+ ensure
39
+ self.fabrication_depth -= 1
40
+ end
41
+
42
+ private
43
+
44
+ def fabrication_depth
45
+ Thread.current[:_fab_depth_] ||= 0
46
+ end
47
+
48
+ def fabrication_depth=(value)
49
+ Thread.current[:_fab_depth_] = value
50
+ end
51
+ end
52
+
53
+ def self.patch
54
+ TestProf.require "fabrication" do
55
+ ::Fabricate.singleton_class.prepend(DefaultExt)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -1,19 +1,69 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "test_prof/factory_bot"
4
+
3
5
  module TestProf
4
6
  module FactoryDefault # :nodoc: all
5
- module RunnerExt
6
- refine TestProf::FactoryBot::FactoryRunner do
7
- attr_reader :name, :traits, :overrides
7
+ module FactoryBotPatch
8
+ if defined?(TestProf::FactoryBot::FactoryRunner)
9
+ module RunnerExt
10
+ refine TestProf::FactoryBot::FactoryRunner do
11
+ attr_reader :name, :traits, :overrides
12
+ end
13
+ end
14
+
15
+ using RunnerExt
16
+ end
17
+
18
+ module StrategyExt
19
+ def association(runner)
20
+ FactoryDefault.get(runner.name, runner.traits, runner.overrides, **{}) ||
21
+ FactoryDefault.profiler.instrument(runner.name, runner.traits, runner.overrides) { super }
22
+ end
23
+ end
24
+
25
+ module SyntaxExt
26
+ def create_default(name, *args, &block)
27
+ options = args.extract_options!
28
+ default_options = {}
29
+ default_options[:preserve_traits] = options.delete(:preserve_traits) if options.key?(:preserve_traits)
30
+ default_options[:preserve_attributes] = options.delete(:preserve_attributes) if options.key?(:preserve_attributes)
31
+
32
+ obj = TestProf::FactoryBot.create(name, *args, options, &block)
33
+
34
+ # Factory with traits
35
+ name = [name, *args] if args.any?
36
+
37
+ set_factory_default(name, obj, **default_options)
38
+ end
39
+
40
+ def set_factory_default(*name, obj, preserve_traits: FactoryDefault.config.preserve_traits, preserve_attributes: FactoryDefault.config.preserve_attributes, **other)
41
+ name = name.first if name.size == 1
42
+ FactoryDefault.register(
43
+ name, obj,
44
+ preserve_traits: preserve_traits,
45
+ preserve_attributes: preserve_attributes,
46
+ **other
47
+ )
48
+ end
49
+
50
+ def get_factory_default(name, *traits, **overrides)
51
+ FactoryDefault.get(name, traits, overrides, skip_stats: true)
52
+ end
53
+
54
+ def skip_factory_default(&block)
55
+ FactoryDefault.disable!(&block)
56
+ end
8
57
  end
9
- end
10
58
 
11
- using RunnerExt
59
+ def self.patch
60
+ return unless defined?(TestProf::FactoryBot)
12
61
 
13
- module StrategyExt
14
- def association(runner)
15
- FactoryDefault.get(runner.name, runner.traits, runner.overrides) ||
16
- FactoryDefault.profiler.instrument(runner.name, runner.traits, runner.overrides) { super }
62
+ TestProf::FactoryBot::Syntax::Methods.include SyntaxExt
63
+ TestProf::FactoryBot.extend SyntaxExt
64
+ TestProf::FactoryBot::Strategy::Create.prepend StrategyExt
65
+ TestProf::FactoryBot::Strategy::Build.prepend StrategyExt
66
+ TestProf::FactoryBot::Strategy::Stub.prepend StrategyExt
17
67
  end
18
68
  end
19
69
  end
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "test_prof/core"
4
- require "test_prof/factory_bot"
4
+
5
5
  require "test_prof/factory_default/factory_bot_patch"
6
+ require "test_prof/factory_default/fabrication_patch"
7
+
6
8
  require "test_prof/ext/float_duration"
7
9
  require "test_prof/ext/active_record_refind" if defined?(::ActiveRecord::Base)
8
10
 
@@ -144,35 +146,6 @@ module TestProf
144
146
  end
145
147
  end
146
148
 
147
- module DefaultSyntax # :nodoc:
148
- def create_default(name, *args, &block)
149
- options = args.extract_options!
150
- default_options = {}
151
- default_options[:preserve_traits] = options.delete(:preserve_traits) if options.key?(:preserve_traits)
152
- default_options[:preserve_attributes] = options.delete(:preserve_attributes) if options.key?(:preserve_attributes)
153
-
154
- obj = TestProf::FactoryBot.create(name, *args, options, &block)
155
-
156
- # Factory with traits
157
- name = [name, *args] if args.any?
158
-
159
- set_factory_default(name, obj, **default_options)
160
- end
161
-
162
- def set_factory_default(name, obj, preserve_traits: FactoryDefault.config.preserve_traits, preserve_attributes: FactoryDefault.config.preserve_attributes, **other)
163
- FactoryDefault.register(
164
- name, obj,
165
- preserve_traits: preserve_traits,
166
- preserve_attributes: preserve_attributes,
167
- **other
168
- )
169
- end
170
-
171
- def skip_factory_default(&block)
172
- FactoryDefault.disable!(&block)
173
- end
174
- end
175
-
176
149
  class Configuration
177
150
  attr_accessor :preserve_traits, :preserve_attributes,
178
151
  :report_summary, :report_stats,
@@ -197,11 +170,8 @@ module TestProf
197
170
  attr_reader :stats, :profiler
198
171
 
199
172
  def init
200
- TestProf::FactoryBot::Syntax::Methods.include DefaultSyntax
201
- TestProf::FactoryBot.extend DefaultSyntax
202
- TestProf::FactoryBot::Strategy::Create.prepend StrategyExt
203
- TestProf::FactoryBot::Strategy::Build.prepend StrategyExt
204
- TestProf::FactoryBot::Strategy::Stub.prepend StrategyExt
173
+ FactoryBotPatch.patch
174
+ FabricationPatch.patch
205
175
 
206
176
  @profiler = config.profiling_enabled? ? Profiler.new : NoopProfiler.new
207
177
  @enabled = ENV["FACTORY_DEFAULT_DISABLED"] != "1"
@@ -236,7 +206,7 @@ module TestProf
236
206
  obj
237
207
  end
238
208
 
239
- def get(name, traits = nil, overrides = nil)
209
+ def get(name, traits = nil, overrides = nil, skip_stats: false)
240
210
  return unless enabled?
241
211
 
242
212
  record = store[name]
@@ -248,7 +218,7 @@ module TestProf
248
218
  traits = nil
249
219
  end
250
220
 
251
- stats[name][:miss] += 1
221
+ stats[name][:miss] += 1 unless skip_stats
252
222
 
253
223
  if traits && !traits.empty? && record[:preserve_traits]
254
224
  return
@@ -263,8 +233,10 @@ module TestProf
263
233
  end
264
234
  end
265
235
 
266
- stats[name][:miss] -= 1
267
- stats[name][:hit] += 1
236
+ unless skip_stats
237
+ stats[name][:miss] -= 1
238
+ stats[name][:hit] += 1
239
+ end
268
240
 
269
241
  if record[:context] && (record[:context] != :example)
270
242
  object.refind
@@ -105,10 +105,9 @@ module TestProf
105
105
  # Do not analyze code within the block
106
106
  def ignore
107
107
  @ignored = true
108
- res = yield
108
+ yield
109
109
  ensure
110
110
  @ignored = false
111
- res
112
111
  end
113
112
 
114
113
  def ignore!
@@ -5,7 +5,13 @@ module TestProf
5
5
  # Wrap #run method with FactoryProf tracking
6
6
  module FabricationPatch
7
7
  def create(name, overrides = {})
8
- FactoryBuilders::Fabrication.track(name) { super }
8
+ variation = ""
9
+
10
+ if FactoryProf.config.include_variations? && !overrides.empty?
11
+ variation += overrides.keys.sort.to_s.gsub(/[\\":]/, "")
12
+ end
13
+
14
+ FactoryBuilders::Fabrication.track(name, variation: variation.to_sym) { super }
9
15
  end
10
16
  end
11
17
  end
@@ -5,7 +5,21 @@ module TestProf
5
5
  # Wrap #run method with FactoryProf tracking
6
6
  module FactoryBotPatch
7
7
  def run(strategy = @strategy)
8
- FactoryBuilders::FactoryBot.track(strategy, @name) { super }
8
+ variation = ""
9
+
10
+ if FactoryProf.config.include_variations?
11
+ if @traits || @overrides
12
+ unless @traits.empty?
13
+ variation += @traits.sort.join(".").prepend(".")
14
+ end
15
+
16
+ unless @overrides.empty?
17
+ variation += @overrides.keys.sort.to_s.gsub(/[\\":]/, "")
18
+ end
19
+ end
20
+ end
21
+
22
+ FactoryBuilders::FactoryBot.track(strategy, @name, variation: variation.to_sym) { super }
9
23
  end
10
24
  end
11
25
  end
@@ -15,8 +15,8 @@ module TestProf
15
15
  end
16
16
  end
17
17
 
18
- def self.track(factory, &block)
19
- FactoryProf.track(factory, &block)
18
+ def self.track(factory, **opts, &block)
19
+ FactoryProf.track(factory, **opts, &block)
20
20
  end
21
21
  end
22
22
  end
@@ -18,9 +18,9 @@ module TestProf
18
18
  defined? TestProf::FactoryBot
19
19
  end
20
20
 
21
- def self.track(strategy, factory, &block)
21
+ def self.track(strategy, factory, **opts, &block)
22
22
  return yield unless strategy.create?
23
- FactoryProf.track(factory, &block)
23
+ FactoryProf.track(factory, **opts, &block)
24
24
  end
25
25
  end
26
26
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_prof/ext/float_duration"
4
+
5
+ module TestProf::FactoryProf
6
+ module Printers
7
+ module Json # :nodoc: all
8
+ class << self
9
+ using TestProf::FloatDuration
10
+ include TestProf::Logging
11
+
12
+ def dump(result, start_time:, **)
13
+ return log(:info, "No factories detected") if result.raw_stats == {}
14
+
15
+ outpath = TestProf.artifact_path("test-prof.result.json")
16
+ File.write(outpath, convert_stats(result, start_time).to_json)
17
+
18
+ log :info, "Profile results to JSON: #{outpath}"
19
+ end
20
+
21
+ def convert_stats(result, start_time)
22
+ total_run_time = TestProf.now - start_time
23
+ total_count = result.stats.sum { |stat| stat[:total_count] }
24
+ total_top_level_count = result.stats.sum { |stat| stat[:top_level_count] }
25
+ total_time = result.stats.sum { |stat| stat[:top_level_time] }
26
+ total_uniq_factories = result.stats.map { |stat| stat[:name] }.uniq.count
27
+
28
+ {
29
+ total_count: total_count,
30
+ total_top_level_count: total_top_level_count,
31
+ total_time: total_time.duration,
32
+ total_run_time: total_run_time.duration,
33
+ total_uniq_factories: total_uniq_factories,
34
+
35
+ stats: result.stats
36
+ }
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -10,7 +10,7 @@ module TestProf::FactoryProf
10
10
  using TestProf::FloatDuration
11
11
  include TestProf::Logging
12
12
 
13
- def dump(result, start_time:)
13
+ def dump(result, start_time:, **)
14
14
  return if result.raw_stats == {}
15
15
 
16
16
  total_time = result.stats.sum { |stat| stat[:top_level_time] }
@@ -9,7 +9,7 @@ module TestProf::FactoryProf
9
9
  using TestProf::FloatDuration
10
10
  include TestProf::Logging
11
11
 
12
- def dump(result, start_time:)
12
+ def dump(result, start_time:, threshold:)
13
13
  return log(:info, "No factories detected") if result.raw_stats == {}
14
14
  msgs = []
15
15
 
@@ -28,17 +28,36 @@ module TestProf::FactoryProf
28
28
  Total time: #{total_time.duration} (out of #{total_run_time.duration})
29
29
  Total uniq factories: #{total_uniq_factories}
30
30
 
31
- total top-level total time time per call top-level time name
31
+ name total top-level total time time per call top-level time
32
32
  MSG
33
33
 
34
34
  result.stats.each do |stat|
35
- time_per_call = stat[:total_time] / stat[:total_count]
35
+ next if stat[:total_count] < threshold
36
36
 
37
- msgs << format("%8d %11d %13.4fs %17.4fs %18.4fs %18s", stat[:total_count], stat[:top_level_count], stat[:total_time], time_per_call, stat[:top_level_time], stat[:name])
37
+ msgs << format("%-3s%-20s %8d %11d %13.4fs %17.4fs %18.4fs", *format_args(stat))
38
+ # move other variation ("[...]") to the end of the array
39
+ sorted_variations = stat[:variations].sort_by.with_index do |variation, i|
40
+ (variation[:name] == "[...]") ? stat[:variations].size + 1 : i
41
+ end
42
+ sorted_variations.each do |variation_stat|
43
+ next if variation_stat[:total_count] < threshold
44
+
45
+ msgs << format("%-5s%-18s %8d %11d %13.4fs %17.4fs %18.4fs", *format_args(variation_stat))
46
+ end
38
47
  end
39
48
 
40
49
  log :info, msgs.join("\n")
41
50
  end
51
+
52
+ private
53
+
54
+ def format_args(stat)
55
+ time_per_call = stat[:total_time] / stat[:total_count]
56
+ format_args = [""]
57
+ format_args += stat.values_at(:name, :total_count, :top_level_count, :total_time)
58
+ format_args << time_per_call
59
+ format_args << stat[:top_level_time]
60
+ end
42
61
  end
43
62
  end
44
63
  end
@@ -3,6 +3,7 @@
3
3
  require "test_prof/factory_prof/printers/simple"
4
4
  require "test_prof/factory_prof/printers/flamegraph"
5
5
  require "test_prof/factory_prof/printers/nate_heckler"
6
+ require "test_prof/factory_prof/printers/json"
6
7
  require "test_prof/factory_prof/factory_builders/factory_bot"
7
8
  require "test_prof/factory_prof/factory_builders/fabrication"
8
9
 
@@ -15,7 +16,7 @@ module TestProf
15
16
 
16
17
  # FactoryProf configuration
17
18
  class Configuration
18
- attr_accessor :mode, :printer
19
+ attr_accessor :mode, :printer, :threshold, :include_variations, :variations_limit
19
20
 
20
21
  def initialize
21
22
  @mode = (ENV["FPROF"] == "flamegraph") ? :flamegraph : :simple
@@ -25,15 +26,24 @@ module TestProf
25
26
  Printers::Flamegraph
26
27
  when "nate_heckler"
27
28
  Printers::NateHeckler
29
+ when "json"
30
+ Printers::Json
28
31
  else
29
32
  Printers::Simple
30
33
  end
34
+ @threshold = ENV.fetch("FPROF_THRESHOLD", 0).to_i
35
+ @include_variations = ENV["FPROF_VARS"] == "1"
36
+ @variations_limit = ENV.fetch("FPROF_VARIATIONS_LIMIT", 2).to_i
31
37
  end
32
38
 
33
39
  # Whether we want to generate flamegraphs
34
40
  def flamegraph?
35
41
  @mode == :flamegraph
36
42
  end
43
+
44
+ def include_variations?
45
+ @include_variations == true
46
+ end
37
47
  end
38
48
 
39
49
  class Result # :nodoc:
@@ -46,8 +56,14 @@ module TestProf
46
56
 
47
57
  # Returns sorted stats
48
58
  def stats
49
- @stats ||= @raw_stats.values
50
- .sort_by { |el| -el[:total_count] }
59
+ @stats ||= @raw_stats.values.sort_by { |el| -el[:total_count] }.map do |stat|
60
+ unless stat[:variations].empty?
61
+ stat = stat.dup
62
+ stat[:variations] = stat[:variations].values.sort_by { |nested_el| -nested_el[:total_count] }
63
+ end
64
+
65
+ stat
66
+ end
51
67
  end
52
68
 
53
69
  def total_count
@@ -57,14 +73,6 @@ module TestProf
57
73
  def total_time
58
74
  @total_time ||= @raw_stats.values.sum { |v| v[:total_time] }
59
75
  end
60
-
61
- private
62
-
63
- def sorted_stats(key)
64
- @raw_stats.values
65
- .map { |el| [el[:name], el[key]] }
66
- .sort_by { |el| -el[1] }
67
- end
68
76
  end
69
77
 
70
78
  class << self
@@ -112,7 +120,7 @@ module TestProf
112
120
  def print(started_at)
113
121
  printer = config.printer
114
122
 
115
- printer.dump(result, start_time: started_at)
123
+ printer.dump(result, start_time: started_at, threshold: config.threshold)
116
124
  end
117
125
 
118
126
  def start
@@ -128,20 +136,19 @@ module TestProf
128
136
  Result.new(@stacks, @stats)
129
137
  end
130
138
 
131
- def track(factory)
139
+ def track(factory, variation:)
132
140
  return yield unless running?
133
141
  @depth += 1
134
142
  @current_stack << factory if config.flamegraph?
135
- @stats[factory][:total_count] += 1
136
- @stats[factory][:top_level_count] += 1 if @depth == 1
143
+ track_count(@stats[factory])
144
+ track_count(@stats[factory][:variations][variation_name(variation)]) if config.include_variations?
137
145
  t1 = TestProf.now
138
146
  begin
139
147
  yield
140
148
  ensure
141
149
  t2 = TestProf.now
142
- elapsed = t2 - t1
143
- @stats[factory][:total_time] += elapsed
144
- @stats[factory][:top_level_time] += elapsed if @depth == 1
150
+ track_time(@stats[factory], t1, t2)
151
+ track_time(@stats[factory][:variations][variation_name(variation)], t1, t2) if config.include_variations?
145
152
  @depth -= 1
146
153
  flush_stack if @depth.zero?
147
154
  end
@@ -149,21 +156,46 @@ module TestProf
149
156
 
150
157
  private
151
158
 
159
+ def variation_name(variation)
160
+ return "-" if variation.empty?
161
+ variations_count = variation.to_s.scan(/[\w]+/).size
162
+ return "[...]" if variations_count > config.variations_limit
163
+
164
+ variation
165
+ end
166
+
152
167
  def reset!
153
168
  @stacks = [] if config.flamegraph?
154
169
  @depth = 0
155
170
  @stats = Hash.new do |h, k|
156
- h[k] = {
157
- name: k,
158
- total_count: 0,
159
- top_level_count: 0,
160
- total_time: 0.0,
161
- top_level_time: 0.0
162
- }
171
+ h[k] = hash_template(k)
172
+ h[k][:variations] = Hash.new { |hh, variation_key| hh[variation_key] = hash_template(variation_key) }
173
+ h[k]
163
174
  end
164
175
  flush_stack
165
176
  end
166
177
 
178
+ def hash_template(name)
179
+ {
180
+ name: name,
181
+ total_count: 0,
182
+ top_level_count: 0,
183
+ total_time: 0.0,
184
+ top_level_time: 0.0
185
+ }
186
+ end
187
+
188
+ def track_count(factory)
189
+ factory[:total_count] += 1
190
+ factory[:top_level_count] += 1 if @depth == 1
191
+ end
192
+
193
+ def track_time(factory, t1, t2)
194
+ elapsed = t2 - t1
195
+ factory[:total_time] += elapsed
196
+ factory[:top_level_time] += elapsed if @depth == 1
197
+ end
198
+
167
199
  def flush_stack
168
200
  return unless config.flamegraph?
169
201
  @stacks << @current_stack unless @current_stack.nil? || @current_stack.empty?
@@ -50,6 +50,8 @@ module TestProf
50
50
  end
51
51
 
52
52
  def memory_percentage(item)
53
+ return 0 if tracker.total_memory.zero? || item[:memory].zero?
54
+
53
55
  (100.0 * item[:memory] / tracker.total_memory).round(2)
54
56
  end
55
57
 
@@ -16,23 +16,26 @@ module TestProf
16
16
  @tracker = MemoryProf.tracker
17
17
  @printer = MemoryProf.printer(tracker)
18
18
 
19
+ @current_group = nil
20
+ @current_example = nil
21
+
19
22
  @tracker.start
20
23
  end
21
24
 
22
25
  def example_started(notification)
23
- tracker.example_started(example(notification))
26
+ tracker.example_started(notification.example, example(notification))
24
27
  end
25
28
 
26
29
  def example_finished(notification)
27
- tracker.example_finished(example(notification))
30
+ tracker.example_finished(notification.example)
28
31
  end
29
32
 
30
33
  def example_group_started(notification)
31
- tracker.group_started(group(notification))
34
+ tracker.group_started(notification.group, group(notification))
32
35
  end
33
36
 
34
37
  def example_group_finished(notification)
35
- tracker.group_finished(group(notification))
38
+ tracker.group_finished(notification.group)
36
39
  end
37
40
 
38
41
  def report
@@ -52,19 +52,20 @@ module TestProf
52
52
  attr_reader :head
53
53
 
54
54
  def initialize(memory_at_start)
55
- add_node(:total, memory_at_start)
55
+ add_node(:total, :total, memory_at_start)
56
56
  end
57
57
 
58
- def add_node(item, memory_at_start)
58
+ def add_node(id, item, memory_at_start)
59
59
  @head = LinkedListNode.new(
60
+ id: id,
60
61
  item: item,
61
62
  previous: head,
62
63
  memory_at_start: memory_at_start
63
64
  )
64
65
  end
65
66
 
66
- def remove_node(item, memory_at_finish)
67
- return if head.item != item
67
+ def remove_node(id, memory_at_finish)
68
+ return if head.id != id
68
69
  head.finish(memory_at_finish)
69
70
 
70
71
  current = head
@@ -75,9 +76,10 @@ module TestProf
75
76
  end
76
77
 
77
78
  class LinkedListNode
78
- attr_reader :item, :previous, :memory_at_start, :memory_at_finish, :nested_memory
79
+ attr_reader :id, :item, :previous, :memory_at_start, :memory_at_finish, :nested_memory
79
80
 
80
- def initialize(item:, memory_at_start:, previous:)
81
+ def initialize(id:, item:, memory_at_start:, previous:)
82
+ @id = id
81
83
  @item = item
82
84
  @previous = previous
83
85