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