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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -2
- data/README.md +5 -3
- data/lib/minitest/base_reporter.rb +18 -12
- data/lib/minitest/test_prof_plugin.rb +8 -8
- data/lib/test_prof.rb +3 -3
- data/lib/test_prof/any_fixture.rb +3 -3
- data/lib/test_prof/before_all.rb +9 -0
- data/lib/test_prof/before_all/adapters/active_record.rb +12 -0
- data/lib/test_prof/before_all/isolator.rb +18 -0
- data/lib/test_prof/cops/rspec/aggregate_failures.rb +6 -6
- data/lib/test_prof/event_prof.rb +6 -6
- data/lib/test_prof/event_prof/custom_events/factory_create.rb +1 -1
- data/lib/test_prof/event_prof/custom_events/sidekiq_inline.rb +2 -2
- data/lib/test_prof/event_prof/custom_events/sidekiq_jobs.rb +2 -2
- data/lib/test_prof/event_prof/instrumentations/active_support.rb +1 -1
- data/lib/test_prof/event_prof/minitest.rb +4 -4
- data/lib/test_prof/event_prof/rspec.rb +4 -11
- data/lib/test_prof/factory_bot.rb +1 -1
- data/lib/test_prof/factory_default.rb +1 -1
- data/lib/test_prof/factory_doctor.rb +2 -2
- data/lib/test_prof/factory_doctor/minitest.rb +4 -4
- data/lib/test_prof/factory_doctor/rspec.rb +7 -10
- data/lib/test_prof/factory_prof.rb +6 -6
- data/lib/test_prof/factory_prof/factory_builders/fabrication.rb +1 -1
- data/lib/test_prof/factory_prof/printers/flamegraph.rb +1 -1
- data/lib/test_prof/recipes/logging.rb +86 -21
- data/lib/test_prof/recipes/minitest/before_all.rb +3 -2
- data/lib/test_prof/recipes/minitest/sample.rb +5 -5
- data/lib/test_prof/recipes/rspec/any_fixture.rb +3 -1
- data/lib/test_prof/recipes/rspec/before_all.rb +12 -3
- data/lib/test_prof/recipes/rspec/factory_all_stub.rb +5 -1
- data/lib/test_prof/recipes/rspec/let_it_be.rb +3 -13
- data/lib/test_prof/rspec_dissect.rb +13 -19
- data/lib/test_prof/rspec_dissect/rspec.rb +2 -6
- data/lib/test_prof/rspec_stamp.rb +14 -14
- data/lib/test_prof/rspec_stamp/parser.rb +1 -1
- data/lib/test_prof/rspec_stamp/rspec.rb +1 -1
- data/lib/test_prof/ruby_prof.rb +32 -24
- data/lib/test_prof/ruby_prof/rspec.rb +2 -6
- data/lib/test_prof/stack_prof.rb +23 -15
- data/lib/test_prof/stack_prof/rspec.rb +5 -6
- data/lib/test_prof/tag_prof/printers/simple.rb +4 -4
- data/lib/test_prof/tag_prof/result.rb +3 -3
- data/lib/test_prof/tag_prof/rspec.rb +9 -14
- data/lib/test_prof/utils/html_builder.rb +1 -1
- data/lib/test_prof/utils/sized_ordered_set.rb +1 -1
- data/lib/test_prof/version.rb +1 -1
- 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 ==
|
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
|
-
|
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
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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[
|
49
|
-
@top_count = (ENV[
|
50
|
-
@stamp = ENV[
|
51
|
-
@mode = ENV[
|
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
|
-
|
91
|
-
|
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] = {
|
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(
|
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
|
-
|
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(
|
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[
|
20
|
-
self.tags = ENV[
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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 ||
|
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 ?
|
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)}"
|
data/lib/test_prof/ruby_prof.rb
CHANGED
@@ -25,24 +25,24 @@ module TestProf
|
|
25
25
|
# RubyProf configuration
|
26
26
|
class Configuration
|
27
27
|
PRINTERS = {
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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[
|
57
|
-
@printer ||= ENV.fetch(
|
58
|
-
@mode = ENV.fetch(
|
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 [
|
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,
|
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,
|
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
|
-
|
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
|
-
|
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(
|
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(
|
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
|
-
|
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]
|
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)
|
data/lib/test_prof/stack_prof.rb
CHANGED
@@ -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(
|
33
|
-
@target = ENV[
|
34
|
-
@raw = ENV[
|
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[
|
37
|
-
ENV[
|
36
|
+
if FORMATS.include?(ENV["TEST_STACK_PROF_FORMAT"])
|
37
|
+
ENV["TEST_STACK_PROF_FORMAT"]
|
38
38
|
else
|
39
|
-
|
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
|
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
|
-
|
82
|
-
|
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 ?
|
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
|
-
|
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(
|
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$/,
|
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$/,
|
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(
|
185
|
+
TestProf.activate("TEST_STACK_PROF") do
|
178
186
|
TestProf::StackProf.run
|
179
187
|
end
|