test-prof 0.7.5 → 0.8.0

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