test-prof 0.7.5 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -2
  3. data/README.md +5 -3
  4. data/lib/minitest/base_reporter.rb +18 -12
  5. data/lib/minitest/test_prof_plugin.rb +8 -8
  6. data/lib/test_prof.rb +3 -3
  7. data/lib/test_prof/any_fixture.rb +3 -3
  8. data/lib/test_prof/before_all.rb +9 -0
  9. data/lib/test_prof/before_all/adapters/active_record.rb +12 -0
  10. data/lib/test_prof/before_all/isolator.rb +18 -0
  11. data/lib/test_prof/cops/rspec/aggregate_failures.rb +6 -6
  12. data/lib/test_prof/event_prof.rb +6 -6
  13. data/lib/test_prof/event_prof/custom_events/factory_create.rb +1 -1
  14. data/lib/test_prof/event_prof/custom_events/sidekiq_inline.rb +2 -2
  15. data/lib/test_prof/event_prof/custom_events/sidekiq_jobs.rb +2 -2
  16. data/lib/test_prof/event_prof/instrumentations/active_support.rb +1 -1
  17. data/lib/test_prof/event_prof/minitest.rb +4 -4
  18. data/lib/test_prof/event_prof/rspec.rb +4 -11
  19. data/lib/test_prof/factory_bot.rb +1 -1
  20. data/lib/test_prof/factory_default.rb +1 -1
  21. data/lib/test_prof/factory_doctor.rb +2 -2
  22. data/lib/test_prof/factory_doctor/minitest.rb +4 -4
  23. data/lib/test_prof/factory_doctor/rspec.rb +7 -10
  24. data/lib/test_prof/factory_prof.rb +6 -6
  25. data/lib/test_prof/factory_prof/factory_builders/fabrication.rb +1 -1
  26. data/lib/test_prof/factory_prof/printers/flamegraph.rb +1 -1
  27. data/lib/test_prof/recipes/logging.rb +86 -21
  28. data/lib/test_prof/recipes/minitest/before_all.rb +3 -2
  29. data/lib/test_prof/recipes/minitest/sample.rb +5 -5
  30. data/lib/test_prof/recipes/rspec/any_fixture.rb +3 -1
  31. data/lib/test_prof/recipes/rspec/before_all.rb +12 -3
  32. data/lib/test_prof/recipes/rspec/factory_all_stub.rb +5 -1
  33. data/lib/test_prof/recipes/rspec/let_it_be.rb +3 -13
  34. data/lib/test_prof/rspec_dissect.rb +13 -19
  35. data/lib/test_prof/rspec_dissect/rspec.rb +2 -6
  36. data/lib/test_prof/rspec_stamp.rb +14 -14
  37. data/lib/test_prof/rspec_stamp/parser.rb +1 -1
  38. data/lib/test_prof/rspec_stamp/rspec.rb +1 -1
  39. data/lib/test_prof/ruby_prof.rb +32 -24
  40. data/lib/test_prof/ruby_prof/rspec.rb +2 -6
  41. data/lib/test_prof/stack_prof.rb +23 -15
  42. data/lib/test_prof/stack_prof/rspec.rb +5 -6
  43. data/lib/test_prof/tag_prof/printers/simple.rb +4 -4
  44. data/lib/test_prof/tag_prof/result.rb +3 -3
  45. data/lib/test_prof/tag_prof/rspec.rb +9 -14
  46. data/lib/test_prof/utils/html_builder.rb +1 -1
  47. data/lib/test_prof/utils/sized_ordered_set.rb +1 -1
  48. data/lib/test_prof/version.rb +1 -1
  49. metadata +37 -9
@@ -14,11 +14,6 @@ module TestProf
14
14
  end
15
15
  end
16
16
 
17
- # Only works with RSpec 3.2.0
18
- def supported?
19
- TestProf::Utils.verify_gem_version('rspec-core', at_least: '3.2.0')
20
- end
21
-
22
17
  private
23
18
 
24
19
  def modules
@@ -28,20 +23,15 @@ module TestProf
28
23
  # Use uniq prefix for instance variables to avoid collisions
29
24
  # We want to use the power of Ruby's unicode support)
30
25
  # And we love cats!)
31
- PREFIX = RUBY_ENGINE == 'jruby' ? "@__jruby_is_not_cat_friendly__" : "@😸"
26
+ PREFIX = RUBY_ENGINE == "jruby" ? "@__jruby_is_not_cat_friendly__" : "@😸"
32
27
 
33
28
  def let_it_be(identifier, **options, &block)
34
- unless LetItBe.supported?
35
- TestProf.log :warn, "let_it_be requires RSpec >= 3.2.0. Fallback to let!"
36
- return let!(identifier, &block)
37
- end
38
-
39
29
  initializer = proc do
40
30
  instance_variable_set(:"#{PREFIX}#{identifier}", instance_exec(&block))
41
31
  end
42
32
 
43
33
  if within_before_all?
44
- before(:all, &initializer)
34
+ within_before_all(&initializer)
45
35
  else
46
36
  before_all(&initializer)
47
37
  end
@@ -72,7 +62,7 @@ module TestProf
72
62
  LetItBe.module_for(self).module_eval do
73
63
  define_method(identifier) do
74
64
  # Trying to detect the context (couldn't find other way so far)
75
- if @__inspect_output =~ /\(:context\)/
65
+ if /\(:context\)/.match?(@__inspect_output)
76
66
  instance_variable_get(:"#{PREFIX}#{identifier}")
77
67
  else
78
68
  # Fallback to let definition
@@ -21,10 +21,10 @@ module TestProf
21
21
  Thread.current[:_rspec_dissect_let_depth] += 1
22
22
  begin
23
23
  res = if Thread.current[:_rspec_dissect_let_depth] == 1
24
- RSpecDissect.track(:let, id) { super }
25
- else
26
- super
27
- end
24
+ RSpecDissect.track(:let, id) { super }
25
+ else
26
+ super
27
+ end
28
28
  ensure
29
29
  Thread.current[:_rspec_dissect_let_depth] -= 1
30
30
  end
@@ -45,14 +45,14 @@ module TestProf
45
45
 
46
46
  def initialize
47
47
  @let_stats_enabled = true
48
- @let_top_count = (ENV['RD_PROF_LET_TOP'] || 3).to_i
49
- @top_count = (ENV['RD_PROF_TOP'] || 5).to_i
50
- @stamp = ENV['RD_PROF_STAMP']
51
- @mode = ENV['RD_PROF'] == '1' ? 'all' : ENV['RD_PROF']
48
+ @let_top_count = (ENV["RD_PROF_LET_TOP"] || 3).to_i
49
+ @top_count = (ENV["RD_PROF_TOP"] || 5).to_i
50
+ @stamp = ENV["RD_PROF_STAMP"]
51
+ @mode = ENV["RD_PROF"] == "1" ? "all" : ENV["RD_PROF"]
52
52
 
53
53
  unless MODES.include?(mode)
54
54
  raise "Unknown RSpecDissect mode: #{mode};" \
55
- "available modes: #{MODES.join(', ')}"
55
+ "available modes: #{MODES.join(", ")}"
56
56
  end
57
57
 
58
58
  RSpecStamp.config.tags = @stamp if stamp?
@@ -87,10 +87,8 @@ module TestProf
87
87
  def init
88
88
  RSpec::Core::Example.prepend(ExampleInstrumentation)
89
89
 
90
- if memoization_available?
91
- RSpec::Core::MemoizedHelpers::ThreadsafeMemoized.prepend(MemoizedInstrumentation)
92
- RSpec::Core::MemoizedHelpers::NonThreadSafeMemoized.prepend(MemoizedInstrumentation)
93
- end
90
+ RSpec::Core::MemoizedHelpers::ThreadsafeMemoized.prepend(MemoizedInstrumentation)
91
+ RSpec::Core::MemoizedHelpers::NonThreadSafeMemoized.prepend(MemoizedInstrumentation)
94
92
 
95
93
  @data = {}
96
94
 
@@ -100,10 +98,6 @@ module TestProf
100
98
 
101
99
  reset!
102
100
 
103
- if config.let? && !memoization_available?
104
- log :warn, "RSpecDissect: `let` profiling is not supported (requires RSpec >= 3.3.0)\n"
105
- end
106
-
107
101
  log :info, "RSpecDissect enabled"
108
102
  end
109
103
 
@@ -120,7 +114,7 @@ module TestProf
120
114
 
121
115
  def reset!
122
116
  METRICS.each do |type|
123
- @data[type.to_s] = { time: 0.0, meta: [] }
117
+ @data[type.to_s] = {time: 0.0, meta: []}
124
118
  end
125
119
  end
126
120
 
@@ -148,6 +142,6 @@ require "test_prof/rspec_dissect/collectors/let"
148
142
  require "test_prof/rspec_dissect/collectors/before"
149
143
  require "test_prof/rspec_dissect/rspec" if TestProf.rspec?
150
144
 
151
- TestProf.activate('RD_PROF') do
145
+ TestProf.activate("RD_PROF") do
152
146
  TestProf::RSpecDissect.init
153
147
  end
@@ -10,8 +10,7 @@ module TestProf
10
10
 
11
11
  NOTIFICATIONS = %i[
12
12
  example_group_finished
13
- example_passed
14
- example_failed
13
+ example_finished
15
14
  ].freeze
16
15
 
17
16
  def initialize
@@ -35,9 +34,6 @@ module TestProf
35
34
  @examples_time += notification.example.execution_result.run_time
36
35
  end
37
36
 
38
- alias example_passed example_finished
39
- alias example_failed example_finished
40
-
41
37
  def example_group_finished(notification)
42
38
  return unless notification.group.top_level?
43
39
 
@@ -127,7 +123,7 @@ module TestProf
127
123
  end
128
124
 
129
125
  # Register RSpecDissect listener
130
- TestProf.activate('RD_PROF') do
126
+ TestProf.activate("RD_PROF") do
131
127
  RSpec.configure do |config|
132
128
  listener = nil
133
129
 
@@ -16,8 +16,8 @@ module TestProf
16
16
 
17
17
  def initialize
18
18
  @ignore_files = [%r{spec/support}]
19
- @dry_run = ENV['RSTAMP_DRY_RUN'] == '1'
20
- self.tags = ENV['RSTAMP']
19
+ @dry_run = ENV["RSTAMP_DRY_RUN"] == "1"
20
+ self.tags = ENV["RSTAMP"]
21
21
  end
22
22
 
23
23
  def dry_run?
@@ -26,10 +26,10 @@ module TestProf
26
26
 
27
27
  def tags=(val)
28
28
  @tags = if val.is_a?(String)
29
- parse_tags(val)
30
- else
31
- val
32
- end
29
+ parse_tags(val)
30
+ else
31
+ val
32
+ end
33
33
  end
34
34
 
35
35
  private
@@ -38,10 +38,10 @@ module TestProf
38
38
  str.split(/\s*,\s*/).each_with_object([]) do |tag, acc|
39
39
  k, v = tag.split(":")
40
40
  acc << if v.nil?
41
- k.to_sym
42
- else
43
- Hash[k.to_sym, v.to_sym]
44
- end
41
+ k.to_sym
42
+ else
43
+ Hash[k.to_sym, v.to_sym]
44
+ end
45
45
  end
46
46
  end
47
47
  end
@@ -129,7 +129,7 @@ module TestProf
129
129
  parsed = Parser.parse(code)
130
130
  return false unless parsed
131
131
 
132
- desc = parsed.desc_const || quote(parsed.desc || 'works')
132
+ desc = parsed.desc_const || quote(parsed.desc || "works")
133
133
 
134
134
  tags.each do |t|
135
135
  if t.is_a?(Hash)
@@ -156,9 +156,9 @@ module TestProf
156
156
  end
157
157
  end
158
158
 
159
- replacement = "\\1#{parsed.fname}#{need_parens ? '(' : ' '}"\
160
- "#{[desc, tags_str, htags_str].compact.join(', ')}"\
161
- "#{need_parens ? ') ' : ' '}\\3"
159
+ replacement = "\\1#{parsed.fname}#{need_parens ? "(" : " "}"\
160
+ "#{[desc, tags_str, htags_str].compact.join(", ")}"\
161
+ "#{need_parens ? ") " : " "}\\3"
162
162
 
163
163
  if config.dry_run?
164
164
  log :info, "Patched: #{example.sub(EXAMPLE_RXP, replacement)}"
@@ -126,7 +126,7 @@ module TestProf
126
126
  def parse_literal(expr)
127
127
  val = expr[1][1][1]
128
128
  val = val.to_sym if expr[0] == :symbol_literal ||
129
- expr[0] == :assoc_new
129
+ expr[0] == :assoc_new
130
130
  val
131
131
  end
132
132
 
@@ -53,7 +53,7 @@ module TestProf
53
53
  end
54
54
 
55
55
  # Register EventProf listener
56
- TestProf.activate('RSTAMP') do
56
+ TestProf.activate("RSTAMP") do
57
57
  RSpec.configure do |config|
58
58
  listener = nil
59
59
 
@@ -25,24 +25,24 @@ module TestProf
25
25
  # RubyProf configuration
26
26
  class Configuration
27
27
  PRINTERS = {
28
- 'flat' => 'FlatPrinter',
29
- 'flat_wln' => 'FlatPrinterWithLineNumbers',
30
- 'graph' => 'GraphPrinter',
31
- 'graph_html' => 'GraphHtmlPrinter',
32
- 'dot' => 'DotPrinter',
33
- '.' => 'DotPrinter',
34
- 'call_stack' => 'CallStackPrinter',
35
- 'call_tree' => 'CallTreePrinter',
36
- 'multi' => 'MultiPrinter'
28
+ "flat" => "FlatPrinter",
29
+ "flat_wln" => "FlatPrinterWithLineNumbers",
30
+ "graph" => "GraphPrinter",
31
+ "graph_html" => "GraphHtmlPrinter",
32
+ "dot" => "DotPrinter",
33
+ "." => "DotPrinter",
34
+ "call_stack" => "CallStackPrinter",
35
+ "call_tree" => "CallTreePrinter",
36
+ "multi" => "MultiPrinter"
37
37
  }.freeze
38
38
 
39
39
  # Mapping from printer to report file extension
40
40
  # NOTE: txt is not included and considered default
41
41
  PRINTER_EXTENSTION = {
42
- 'graph_html' => 'html',
43
- 'dot' => 'dot',
44
- '.' => 'dot',
45
- 'call_stack' => 'html'
42
+ "graph_html" => "html",
43
+ "dot" => "dot",
44
+ "." => "dot",
45
+ "call_stack" => "html"
46
46
  }.freeze
47
47
 
48
48
  LOGFILE_PREFIX = "ruby-prof-report"
@@ -53,9 +53,9 @@ module TestProf
53
53
  :custom_exclusions
54
54
 
55
55
  def initialize
56
- @printer = ENV['TEST_RUBY_PROF'].to_sym if PRINTERS.key?(ENV['TEST_RUBY_PROF'])
57
- @printer ||= ENV.fetch('TEST_RUBY_PROF_PRINTER', :flat).to_sym
58
- @mode = ENV.fetch('TEST_RUBY_PROF_MODE', :wall).to_sym
56
+ @printer = ENV["TEST_RUBY_PROF"].to_sym if PRINTERS.key?(ENV["TEST_RUBY_PROF"])
57
+ @printer ||= ENV.fetch("TEST_RUBY_PROF_PRINTER", :flat).to_sym
58
+ @mode = ENV.fetch("TEST_RUBY_PROF_MODE", :wall).to_sym
59
59
  @min_percent = 1
60
60
  @include_threads = false
61
61
  @exclude_common_methods = true
@@ -77,7 +77,7 @@ module TestProf
77
77
 
78
78
  # Returns an array of printer type (ID) and class.
79
79
  def resolve_printer
80
- return ['custom', printer] if printer.is_a?(Module)
80
+ return ["custom", printer] if printer.is_a?(Module)
81
81
 
82
82
  type = printer.to_s
83
83
 
@@ -113,7 +113,7 @@ module TestProf
113
113
  )
114
114
  else
115
115
  path = build_path name, printer_type
116
- File.open(path, 'w') do |f|
116
+ File.open(path, "w") do |f|
117
117
  printer_class.new(result).print(f, min_percent: config.min_percent)
118
118
  end
119
119
 
@@ -127,7 +127,7 @@ module TestProf
127
127
  def build_path(name, printer)
128
128
  TestProf.artifact_path(
129
129
  "#{RubyProf::Configuration::LOGFILE_PREFIX}-#{printer}-#{config.mode}-#{name}" \
130
- ".#{RubyProf::Configuration::PRINTER_EXTENSTION.fetch(printer, 'txt')}"
130
+ ".#{RubyProf::Configuration::PRINTER_EXTENSTION.fetch(printer, "txt")}"
131
131
  )
132
132
  end
133
133
 
@@ -158,13 +158,21 @@ module TestProf
158
158
 
159
159
  @locked = true
160
160
 
161
- log :info, "RubyProf enabled"
161
+ log :info, "RubyProf enabled globally"
162
162
 
163
163
  at_exit { report.dump("total") }
164
164
  end
165
165
 
166
166
  def profile
167
- return if locked?
167
+ if locked?
168
+ log :warn, <<~MSG
169
+ RubyProf is activated globally, you cannot generate per-example report.
170
+
171
+ Make sure you haven's set the TEST_RUBY_PROF environmental variable.
172
+ MSG
173
+ return
174
+ end
175
+
168
176
  return unless init_ruby_prof
169
177
 
170
178
  options = {
@@ -204,7 +212,7 @@ module TestProf
204
212
  return @initialized if instance_variable_defined?(:@initialized)
205
213
  ENV["RUBY_PROF_MEASURE_MODE"] = config.mode.to_s
206
214
  @initialized = TestProf.require(
207
- 'ruby-prof',
215
+ "ruby-prof",
208
216
  <<~MSG
209
217
  Please, install 'ruby-prof' first:
210
218
  # Gemfile
@@ -214,7 +222,7 @@ module TestProf
214
222
  end
215
223
 
216
224
  def check_ruby_prof_version
217
- if Utils.verify_gem_version('ruby-prof', at_least: '0.17.0')
225
+ if Utils.verify_gem_version("ruby-prof", at_least: "0.17.0")
218
226
  true
219
227
  else
220
228
  log :error, <<~MGS
@@ -259,6 +267,6 @@ if TestProf.rspec?
259
267
  end
260
268
 
261
269
  # Hook to run RubyProf globally
262
- TestProf.activate('TEST_RUBY_PROF') do
270
+ TestProf.activate("TEST_RUBY_PROF") do
263
271
  TestProf::RubyProf.run
264
272
  end
@@ -6,8 +6,7 @@ module TestProf
6
6
  class Listener # :nodoc:
7
7
  NOTIFICATIONS = %i[
8
8
  example_started
9
- example_failed
10
- example_passed
9
+ example_finished
11
10
  ].freeze
12
11
 
13
12
  def example_started(notification)
@@ -18,14 +17,11 @@ module TestProf
18
17
 
19
18
  def example_finished(notification)
20
19
  return unless profile?(notification.example)
21
- notification.example.metadata[:rprof_report].dump(
20
+ notification.example.metadata[:rprof_report]&.dump(
22
21
  notification.example.full_description.parameterize
23
22
  )
24
23
  end
25
24
 
26
- alias example_passed example_finished
27
- alias example_failed example_finished
28
-
29
25
  private
30
26
 
31
27
  def profile?(example)
@@ -29,14 +29,14 @@ module TestProf
29
29
  attr_accessor :mode, :interval, :raw, :target, :format
30
30
 
31
31
  def initialize
32
- @mode = ENV.fetch('TEST_STACK_PROF_MODE', :wall).to_sym
33
- @target = ENV['TEST_STACK_PROF'] == 'boot' ? :boot : :suite
34
- @raw = ENV['TEST_STACK_PROF_RAW'] != '0'
32
+ @mode = ENV.fetch("TEST_STACK_PROF_MODE", :wall).to_sym
33
+ @target = ENV["TEST_STACK_PROF"] == "boot" ? :boot : :suite
34
+ @raw = ENV["TEST_STACK_PROF_RAW"] != "0"
35
35
  @format =
36
- if FORMATS.include?(ENV['TEST_STACK_PROF_FORMAT'])
37
- ENV['TEST_STACK_PROF_FORMAT']
36
+ if FORMATS.include?(ENV["TEST_STACK_PROF_FORMAT"])
37
+ ENV["TEST_STACK_PROF_FORMAT"]
38
38
  else
39
- 'html'
39
+ "html"
40
40
  end
41
41
  end
42
42
 
@@ -71,15 +71,23 @@ module TestProf
71
71
 
72
72
  @locked = true
73
73
 
74
- log :info, "StackProf enabled#{config.raw? ? ' (raw)' : ''}: " \
74
+ log :info, "StackProf#{config.raw? ? " (raw)" : ""} enabled globally: " \
75
75
  "mode – #{config.mode}, target – #{config.target}"
76
76
 
77
77
  at_exit { dump("total") } if config.suite?
78
78
  end
79
79
 
80
80
  def profile(name = nil)
81
- return if locked?
82
- return unless init_stack_prof
81
+ if locked?
82
+ log :warn, <<~MSG
83
+ StackProf is activated globally, you cannot generate per-example report.
84
+
85
+ Make sure you haven's set the TEST_STACK_PROF environmental variable.
86
+ MSG
87
+ return false
88
+ end
89
+
90
+ return false unless init_stack_prof
83
91
 
84
92
  options = {
85
93
  mode: config.mode,
@@ -115,7 +123,7 @@ module TestProf
115
123
 
116
124
  def build_path(name)
117
125
  TestProf.artifact_path(
118
- "stack-prof-report-#{config.mode}#{config.raw ? '-raw' : ''}-#{name}.dump"
126
+ "stack-prof-report-#{config.mode}#{config.raw ? "-raw" : ""}-#{name}.dump"
119
127
  )
120
128
  end
121
129
 
@@ -126,7 +134,7 @@ module TestProf
126
134
  def init_stack_prof
127
135
  return @initialized if instance_variable_defined?(:@initialized)
128
136
  @initialized = TestProf.require(
129
- 'stackprof',
137
+ "stackprof",
130
138
  <<~MSG
131
139
  Please, install 'stackprof' first:
132
140
  # Gemfile
@@ -136,7 +144,7 @@ module TestProf
136
144
  end
137
145
 
138
146
  def check_stack_prof_version
139
- if Utils.verify_gem_version('stackprof', at_least: '0.2.9')
147
+ if Utils.verify_gem_version("stackprof", at_least: "0.2.9")
140
148
  true
141
149
  else
142
150
  log :error, <<~MSG
@@ -147,7 +155,7 @@ module TestProf
147
155
  end
148
156
 
149
157
  def dump_html_report(path)
150
- html_path = path.gsub(/\.dump$/, '.html')
158
+ html_path = path.gsub(/\.dump$/, ".html")
151
159
 
152
160
  log :info, <<~MSG
153
161
  Run the following command to generate a flame graph report:
@@ -160,7 +168,7 @@ module TestProf
160
168
  report = ::StackProf::Report.new(
161
169
  Marshal.load(IO.binread(path)) # rubocop:disable Security/MarshalLoad
162
170
  )
163
- json_path = path.gsub(/\.dump$/, '.json')
171
+ json_path = path.gsub(/\.dump$/, ".json")
164
172
  File.write(json_path, JSON.generate(report.data))
165
173
 
166
174
  log :info, <<~MSG
@@ -174,6 +182,6 @@ end
174
182
  require "test_prof/stack_prof/rspec" if TestProf.rspec?
175
183
 
176
184
  # Hook to run StackProf globally
177
- TestProf.activate('TEST_STACK_PROF') do
185
+ TestProf.activate("TEST_STACK_PROF") do
178
186
  TestProf::StackProf.run
179
187
  end