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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +78 -0
- data/README.md +2 -2
- data/lib/minitest/test_prof_plugin.rb +3 -0
- data/lib/test_prof/any_fixture/dsl.rb +19 -1
- data/lib/test_prof/any_fixture/dump.rb +1 -1
- data/lib/test_prof/any_fixture.rb +16 -2
- data/lib/test_prof/before_all/adapters/active_record.rb +82 -47
- data/lib/test_prof/before_all/isolator.rb +6 -15
- data/lib/test_prof/before_all.rb +31 -8
- data/lib/test_prof/cops/rspec/aggregate_examples/its.rb +2 -2
- data/lib/test_prof/cops/rspec/aggregate_examples/line_range_helpers.rb +1 -1
- data/lib/test_prof/cops/rspec/aggregate_examples/matchers_with_side_effects.rb +1 -1
- data/lib/test_prof/cops/rspec/aggregate_examples/metadata_helpers.rb +1 -1
- data/lib/test_prof/cops/rspec/aggregate_examples/node_matchers.rb +1 -1
- data/lib/test_prof/cops/rspec/aggregate_examples.rb +12 -18
- data/lib/test_prof/core.rb +28 -1
- data/lib/test_prof/event_prof/monitor.rb +3 -10
- data/lib/test_prof/ext/active_record_refind.rb +1 -1
- data/lib/test_prof/ext/string_truncate.rb +1 -1
- data/lib/test_prof/factory_bot.rb +2 -0
- data/lib/test_prof/factory_default/fabrication_patch.rb +60 -0
- data/lib/test_prof/factory_default/factory_bot_patch.rb +59 -9
- data/lib/test_prof/factory_default.rb +11 -39
- data/lib/test_prof/factory_doctor.rb +1 -2
- data/lib/test_prof/factory_prof/fabrication_patch.rb +7 -1
- data/lib/test_prof/factory_prof/factory_bot_patch.rb +15 -1
- data/lib/test_prof/factory_prof/factory_builders/fabrication.rb +2 -2
- data/lib/test_prof/factory_prof/factory_builders/factory_bot.rb +2 -2
- data/lib/test_prof/factory_prof/printers/json.rb +41 -0
- data/lib/test_prof/factory_prof/printers/nate_heckler.rb +1 -1
- data/lib/test_prof/factory_prof/printers/simple.rb +23 -4
- data/lib/test_prof/factory_prof.rb +57 -25
- data/lib/test_prof/memory_prof/printer.rb +2 -0
- data/lib/test_prof/memory_prof/rspec.rb +7 -4
- data/lib/test_prof/memory_prof/tracker/linked_list.rb +8 -6
- data/lib/test_prof/memory_prof/tracker.rb +14 -10
- data/lib/test_prof/recipes/minitest/before_all.rb +2 -2
- data/lib/test_prof/recipes/minitest/sample.rb +14 -5
- data/lib/test_prof/recipes/rspec/any_fixture.rb +8 -0
- data/lib/test_prof/recipes/rspec/let_it_be.rb +19 -0
- data/lib/test_prof/rspec_stamp/parser.rb +1 -1
- data/lib/test_prof/rspec_stamp.rb +7 -5
- data/lib/test_prof/ruby_prof/rspec_no_boot.rb +15 -0
- data/lib/test_prof/ruby_prof.rb +20 -7
- data/lib/test_prof/tag_prof/minitest.rb +74 -0
- data/lib/test_prof/tag_prof/result.rb +2 -2
- data/lib/test_prof/tps_prof/profiler.rb +55 -0
- data/lib/test_prof/tps_prof/reporter/text.rb +48 -0
- data/lib/test_prof/tps_prof/rspec.rb +63 -0
- data/lib/test_prof/tps_prof.rb +46 -0
- data/lib/test_prof/vernier.rb +3 -1
- data/lib/test_prof/version.rb +1 -1
- data/lib/test_prof.rb +1 -0
- metadata +15 -7
@@ -36,22 +36,26 @@ module TestProf
|
|
36
36
|
@total_memory = node.total_memory
|
37
37
|
end
|
38
38
|
|
39
|
-
def example_started(example)
|
40
|
-
list.add_node(example, track)
|
39
|
+
def example_started(id, example = id)
|
40
|
+
list.add_node(id, example, track)
|
41
41
|
end
|
42
42
|
|
43
|
-
def example_finished(
|
44
|
-
node = list.remove_node(
|
45
|
-
|
43
|
+
def example_finished(id)
|
44
|
+
node = list.remove_node(id, track)
|
45
|
+
return unless node
|
46
|
+
|
47
|
+
examples << {**node.item, memory: node.total_memory}
|
46
48
|
end
|
47
49
|
|
48
|
-
def group_started(group)
|
49
|
-
list.add_node(group, track)
|
50
|
+
def group_started(id, group = id)
|
51
|
+
list.add_node(id, group, track)
|
50
52
|
end
|
51
53
|
|
52
|
-
def group_finished(
|
53
|
-
node = list.remove_node(
|
54
|
-
|
54
|
+
def group_finished(id)
|
55
|
+
node = list.remove_node(id, track)
|
56
|
+
return unless node
|
57
|
+
|
58
|
+
groups << {**node.item, memory: node.hooks_memory}
|
55
59
|
end
|
56
60
|
end
|
57
61
|
|
@@ -104,7 +104,7 @@ module TestProf
|
|
104
104
|
if base.respond_to?(:parallelize_teardown)
|
105
105
|
base.parallelize_teardown do
|
106
106
|
last_klass = ::Minitest.previous_klass
|
107
|
-
if last_klass&.respond_to?(:parallelized) && last_klass
|
107
|
+
if last_klass&.respond_to?(:parallelized) && last_klass.parallelized
|
108
108
|
last_klass.before_all_executor&.deactivate!
|
109
109
|
end
|
110
110
|
end
|
@@ -114,7 +114,7 @@ module TestProf
|
|
114
114
|
base.singleton_class.prepend(Module.new do
|
115
115
|
def parallelize(workers: :number_of_processors, with: :processes)
|
116
116
|
# super.parallelize returns nil when no parallelization is set up
|
117
|
-
if super
|
117
|
+
if super.nil?
|
118
118
|
return
|
119
119
|
end
|
120
120
|
|
@@ -7,14 +7,16 @@ module TestProf
|
|
7
7
|
CORE_RUNNABLES = [
|
8
8
|
Minitest::Test,
|
9
9
|
defined?(Minitest::Unit::TestCase) ? Minitest::Unit::TestCase : nil,
|
10
|
-
Minitest::Spec
|
10
|
+
defined?(Minitest::Spec) ? Minitest::Spec : nil
|
11
11
|
].compact.freeze
|
12
12
|
|
13
13
|
class << self
|
14
14
|
def suites
|
15
15
|
# Make sure that sample contains only _real_ suites
|
16
16
|
Minitest::Runnable.runnables
|
17
|
-
.
|
17
|
+
.select do |suite|
|
18
|
+
CORE_RUNNABLES.any? { |kl| suite < kl } && suite.runnable_methods.any?
|
19
|
+
end
|
18
20
|
end
|
19
21
|
|
20
22
|
def sample_groups(sample_size)
|
@@ -27,11 +29,18 @@ module TestProf
|
|
27
29
|
all_examples = suites.flat_map do |runnable|
|
28
30
|
runnable.runnable_methods.map { |method| [runnable, method] }
|
29
31
|
end
|
30
|
-
|
32
|
+
|
33
|
+
sample = all_examples.sample(sample_size).group_by(&:first)
|
34
|
+
sample.transform_values! { |v| v.map(&:last) }
|
35
|
+
|
31
36
|
# Filter examples by overriding #runnable_methods for all suites
|
32
37
|
suites.each do |runnable|
|
33
|
-
|
34
|
-
|
38
|
+
if sample.key?(runnable)
|
39
|
+
runnable.define_singleton_method(:runnable_methods) do
|
40
|
+
super() & sample[runnable]
|
41
|
+
end
|
42
|
+
else
|
43
|
+
runnable.define_singleton_method(:runnable_methods) { [] }
|
35
44
|
end
|
36
45
|
end
|
37
46
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "test_prof/any_fixture"
|
4
|
+
require "test_prof/any_fixture/dsl"
|
5
|
+
require "test_prof/ext/active_record_refind"
|
6
|
+
|
4
7
|
require "test_prof/recipes/rspec/before_all"
|
5
8
|
|
6
9
|
RSpec.shared_context "any_fixture:clean" do
|
@@ -8,6 +11,11 @@ RSpec.shared_context "any_fixture:clean" do
|
|
8
11
|
|
9
12
|
before_all do
|
10
13
|
TestProf::AnyFixture.clean
|
14
|
+
TestProf::AnyFixture.disable!
|
15
|
+
end
|
16
|
+
|
17
|
+
after(:all) do
|
18
|
+
TestProf::AnyFixture.enable!
|
11
19
|
end
|
12
20
|
end
|
13
21
|
|
@@ -7,6 +7,8 @@ module TestProf
|
|
7
7
|
# Just like `let`, but persist the result for the whole group.
|
8
8
|
# NOTE: Experimental and magical, for more control use `before_all`.
|
9
9
|
module LetItBe
|
10
|
+
class DuplicationError < StandardError; end
|
11
|
+
|
10
12
|
Modifier = Struct.new(:scope, :block) do
|
11
13
|
def call(record, config)
|
12
14
|
block.call(record, config)
|
@@ -14,6 +16,8 @@ module TestProf
|
|
14
16
|
end
|
15
17
|
|
16
18
|
class Configuration
|
19
|
+
attr_accessor :report_duplicates
|
20
|
+
|
17
21
|
# Define an alias for `let_it_be` with the predefined options:
|
18
22
|
#
|
19
23
|
# TestProf::LetItBe.configure do |config|
|
@@ -117,6 +121,8 @@ module TestProf
|
|
117
121
|
instance_variable_get(:"#{PREFIX}#{identifier}")
|
118
122
|
end
|
119
123
|
|
124
|
+
report_duplicates(identifier) if LetItBe.config.report_duplicates
|
125
|
+
|
120
126
|
LetItBe.module_for(self).module_eval do
|
121
127
|
define_method(identifier) do
|
122
128
|
# Trying to detect the context
|
@@ -135,6 +141,19 @@ module TestProf
|
|
135
141
|
let(identifier, &let_accessor)
|
136
142
|
end
|
137
143
|
|
144
|
+
private def report_duplicates(identifier)
|
145
|
+
if instance_methods.include?(identifier) && File.basename(__FILE__) == File.basename(instance_method(identifier).source_location[0])
|
146
|
+
error_msg = "let_it_be(:#{identifier}) was redefined in nested group"
|
147
|
+
report_level = LetItBe.config.report_duplicates.to_sym
|
148
|
+
|
149
|
+
if report_level == :warn
|
150
|
+
::RSpec.warn_with(error_msg)
|
151
|
+
elsif report_level == :raise
|
152
|
+
raise DuplicationError, error_msg
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
138
157
|
module Freezer
|
139
158
|
# Stoplist to prevent freezing objects and theirs associations that are defined
|
140
159
|
# with `let_it_be`'s `freeze: false` options during deep freezing.
|
@@ -153,14 +153,16 @@ module TestProf
|
|
153
153
|
end
|
154
154
|
end
|
155
155
|
|
156
|
-
replacement =
|
157
|
-
|
158
|
-
|
156
|
+
replacement = proc do
|
157
|
+
$1 + "#{parsed.fname}#{need_parens ? "(" : " "}" \
|
158
|
+
"#{[desc, tags_str, htags_str].compact.join(", ")}" \
|
159
|
+
"#{need_parens ? ") " : " "}" + $3
|
160
|
+
end
|
159
161
|
|
160
162
|
if config.dry_run?
|
161
|
-
log :info, "Patched: #{example.sub(EXAMPLE_RXP, replacement)}"
|
163
|
+
log :info, "Patched: #{example.sub(EXAMPLE_RXP, &replacement)}"
|
162
164
|
else
|
163
|
-
example.sub!(EXAMPLE_RXP, replacement)
|
165
|
+
example.sub!(EXAMPLE_RXP, &replacement)
|
164
166
|
end
|
165
167
|
true
|
166
168
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.configure do |config|
|
4
|
+
report = nil
|
5
|
+
|
6
|
+
config.append_before(:suite) do
|
7
|
+
report = TestProf::RubyProf.profile(locked: true)
|
8
|
+
|
9
|
+
TestProf.log :info, "RubyProf enabled for examples"
|
10
|
+
end
|
11
|
+
|
12
|
+
config.after(:suite) do
|
13
|
+
report&.dump("examples")
|
14
|
+
end
|
15
|
+
end
|
data/lib/test_prof/ruby_prof.rb
CHANGED
@@ -48,12 +48,13 @@ module TestProf
|
|
48
48
|
attr_accessor :printer, :mode, :min_percent,
|
49
49
|
:include_threads, :exclude_common_methods,
|
50
50
|
:test_prof_exclusions_enabled,
|
51
|
-
:custom_exclusions
|
51
|
+
:custom_exclusions, :skip_boot
|
52
52
|
|
53
53
|
def initialize
|
54
54
|
@printer = ENV["TEST_RUBY_PROF"].to_sym if PRINTERS.key?(ENV["TEST_RUBY_PROF"])
|
55
55
|
@printer ||= ENV.fetch("TEST_RUBY_PROF_PRINTER", :flat).to_sym
|
56
56
|
@mode = ENV.fetch("TEST_RUBY_PROF_MODE", :wall).to_s
|
57
|
+
@skip_boot = %w[0 false f].include?(ENV["TEST_RUBY_PROF_BOOT"])
|
57
58
|
@min_percent = 1
|
58
59
|
@include_threads = false
|
59
60
|
@exclude_common_methods = true
|
@@ -65,6 +66,10 @@ module TestProf
|
|
65
66
|
include_threads == true
|
66
67
|
end
|
67
68
|
|
69
|
+
def skip_boot?
|
70
|
+
skip_boot == true
|
71
|
+
end
|
72
|
+
|
68
73
|
def exclude_common_methods?
|
69
74
|
exclude_common_methods == true
|
70
75
|
end
|
@@ -166,23 +171,21 @@ module TestProf
|
|
166
171
|
#
|
167
172
|
# Use this method to profile the whole run.
|
168
173
|
def run
|
169
|
-
report = profile
|
174
|
+
report = profile(locked: true)
|
170
175
|
|
171
176
|
return unless report
|
172
177
|
|
173
|
-
@locked = true
|
174
|
-
|
175
178
|
log :info, "RubyProf enabled globally"
|
176
179
|
|
177
180
|
at_exit { report.dump("total") }
|
178
181
|
end
|
179
182
|
|
180
|
-
def profile
|
183
|
+
def profile(locked: false)
|
181
184
|
if locked?
|
182
185
|
log :warn, <<~MSG
|
183
186
|
RubyProf is activated globally, you cannot generate per-example report.
|
184
187
|
|
185
|
-
Make sure you haven
|
188
|
+
Make sure you haven not set the TEST_RUBY_PROF environmental variable.
|
186
189
|
MSG
|
187
190
|
return
|
188
191
|
end
|
@@ -212,6 +215,8 @@ module TestProf
|
|
212
215
|
|
213
216
|
profiler.start
|
214
217
|
|
218
|
+
@locked = true if locked
|
219
|
+
|
215
220
|
Report.new(profiler)
|
216
221
|
end
|
217
222
|
|
@@ -282,5 +287,13 @@ end
|
|
282
287
|
|
283
288
|
# Hook to run RubyProf globally
|
284
289
|
TestProf.activate("TEST_RUBY_PROF") do
|
285
|
-
TestProf::RubyProf.
|
290
|
+
if TestProf::RubyProf.config.skip_boot?
|
291
|
+
if TestProf.rspec?
|
292
|
+
require "test_prof/ruby_prof/rspec_no_boot"
|
293
|
+
else
|
294
|
+
TestProf.log :warn, "RubyProf tests profiling w/o test suite boot is only supported in RSpec"
|
295
|
+
end
|
296
|
+
else
|
297
|
+
TestProf::RubyProf.run
|
298
|
+
end
|
286
299
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Minitest
|
4
|
+
module TestProf
|
5
|
+
class TagProfReporter < BaseReporter # :nodoc:
|
6
|
+
attr_reader :results
|
7
|
+
|
8
|
+
def initialize(io = $stdout, _options = {})
|
9
|
+
super
|
10
|
+
@results = ::TestProf::TagProf::Result.new("type")
|
11
|
+
|
12
|
+
if event_prof_activated?
|
13
|
+
require "test_prof/event_prof"
|
14
|
+
@current_group_id = nil
|
15
|
+
@events_profiler = configure_profiler
|
16
|
+
@results = ::TestProf::TagProf::Result.new("type", @events_profiler.events)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def prerecord(group, example)
|
21
|
+
return unless event_prof_activated?
|
22
|
+
|
23
|
+
# enable event profiling
|
24
|
+
@events_profiler.group_started(true)
|
25
|
+
end
|
26
|
+
|
27
|
+
def record(result)
|
28
|
+
results.track(main_folder_path(result), time: result.time, events: fetch_events_data)
|
29
|
+
@events_profiler.group_started(nil) if event_prof_activated? # reset and disable event profilers
|
30
|
+
end
|
31
|
+
|
32
|
+
def report
|
33
|
+
printer = (ENV["TAG_PROF_FORMAT"] == "html") ? ::TestProf::TagProf::Printers::HTML : ::TestProf::TagProf::Printers::Simple
|
34
|
+
printer.dump(results)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def main_folder_path(result)
|
40
|
+
return :__unknown__ if absolute_path_from(result).nil?
|
41
|
+
|
42
|
+
absolute_path_from(result)
|
43
|
+
end
|
44
|
+
|
45
|
+
def absolute_path_from(result)
|
46
|
+
absolute_path = File.expand_path(result.source_location.first)
|
47
|
+
absolute_path.slice(/(?<=(?:spec|test)\/)\w*/)
|
48
|
+
end
|
49
|
+
|
50
|
+
def configure_profiler
|
51
|
+
::TestProf::EventProf::CustomEvents.activate_all(tag_prof_event)
|
52
|
+
::TestProf::EventProf.build(tag_prof_event)
|
53
|
+
end
|
54
|
+
|
55
|
+
def event_prof_activated?
|
56
|
+
return false if tag_prof_event.nil?
|
57
|
+
|
58
|
+
!tag_prof_event.empty?
|
59
|
+
end
|
60
|
+
|
61
|
+
def tag_prof_event
|
62
|
+
ENV["TAG_PROF_EVENT"]
|
63
|
+
end
|
64
|
+
|
65
|
+
def fetch_events_data
|
66
|
+
return {} unless @events_profiler
|
67
|
+
|
68
|
+
@events_profiler.profilers.map do |profiler|
|
69
|
+
[profiler.event, profiler.time || 0.0]
|
70
|
+
end.to_h
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_prof/utils/sized_ordered_set"
|
4
|
+
|
5
|
+
module TestProf
|
6
|
+
module TPSProf
|
7
|
+
class Profiler
|
8
|
+
attr_reader :top_count, :groups, :total_count, :total_time, :threshold
|
9
|
+
|
10
|
+
def initialize(top_count, threshold: 10)
|
11
|
+
@threshold = threshold
|
12
|
+
@top_count = top_count
|
13
|
+
@total_count = 0
|
14
|
+
@total_time = 0.0
|
15
|
+
@groups = Utils::SizedOrderedSet.new(top_count, sort_by: :tps)
|
16
|
+
end
|
17
|
+
|
18
|
+
def group_started(id)
|
19
|
+
@current_group = id
|
20
|
+
@examples_count = 0
|
21
|
+
@examples_time = 0.0
|
22
|
+
@group_started_at = TestProf.now
|
23
|
+
end
|
24
|
+
|
25
|
+
def group_finished(id)
|
26
|
+
return unless @examples_count >= threshold
|
27
|
+
|
28
|
+
# Context-time
|
29
|
+
group_time = (TestProf.now - @group_started_at) - @examples_time
|
30
|
+
run_time = @examples_time + group_time
|
31
|
+
|
32
|
+
groups << {
|
33
|
+
id: id,
|
34
|
+
run_time: run_time,
|
35
|
+
group_time: group_time,
|
36
|
+
count: @examples_count,
|
37
|
+
tps: -(@examples_count / run_time).round(2)
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def example_started(id)
|
42
|
+
@example_started_at = TestProf.now
|
43
|
+
end
|
44
|
+
|
45
|
+
def example_finished(id)
|
46
|
+
@examples_count += 1
|
47
|
+
@total_count += 1
|
48
|
+
|
49
|
+
time = (TestProf.now - @example_started_at)
|
50
|
+
@examples_time += time
|
51
|
+
@total_time += time
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_prof/ext/float_duration"
|
4
|
+
require "test_prof/ext/string_truncate"
|
5
|
+
|
6
|
+
module TestProf
|
7
|
+
module TPSProf
|
8
|
+
module Reporter
|
9
|
+
class Text
|
10
|
+
include Logging
|
11
|
+
using FloatDuration
|
12
|
+
using StringTruncate
|
13
|
+
|
14
|
+
def print(profiler)
|
15
|
+
groups = profiler.groups
|
16
|
+
|
17
|
+
total_tps = (profiler.total_count / profiler.total_time).round(2)
|
18
|
+
|
19
|
+
msgs = []
|
20
|
+
|
21
|
+
msgs <<
|
22
|
+
<<~MSG
|
23
|
+
Total TPS (tests per second): #{total_tps}
|
24
|
+
|
25
|
+
Top #{profiler.top_count} slowest suites by TPS (tests per second) (min examples per group: #{profiler.threshold}):
|
26
|
+
|
27
|
+
MSG
|
28
|
+
|
29
|
+
groups.each do |group|
|
30
|
+
description = group[:id].top_level_description
|
31
|
+
location = group[:id].metadata[:location]
|
32
|
+
time = group[:run_time]
|
33
|
+
group_time = group[:group_time]
|
34
|
+
count = group[:count]
|
35
|
+
tps = -group[:tps]
|
36
|
+
|
37
|
+
msgs <<
|
38
|
+
<<~GROUP
|
39
|
+
#{description.truncate} (#{location}) – #{tps} TPS (#{time.duration} / #{count}), group time: #{group_time.duration}
|
40
|
+
GROUP
|
41
|
+
end
|
42
|
+
|
43
|
+
log :info, msgs.join
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TestProf
|
4
|
+
module TPSProf
|
5
|
+
class RSpecListener # :nodoc:
|
6
|
+
include Logging
|
7
|
+
|
8
|
+
NOTIFICATIONS = %i[
|
9
|
+
example_group_started
|
10
|
+
example_group_finished
|
11
|
+
example_started
|
12
|
+
example_finished
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
attr_reader :reporter, :profiler
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@profiler = Profiler.new(TPSProf.config.top_count, threshold: TPSProf.config.threshold)
|
19
|
+
@reporter = TPSProf.config.reporter
|
20
|
+
|
21
|
+
log :info, "TPSProf enabled (top-#{TPSProf.config.top_count})"
|
22
|
+
end
|
23
|
+
|
24
|
+
def example_group_started(notification)
|
25
|
+
return unless notification.group.top_level?
|
26
|
+
profiler.group_started notification.group
|
27
|
+
end
|
28
|
+
|
29
|
+
def example_group_finished(notification)
|
30
|
+
return unless notification.group.top_level?
|
31
|
+
profiler.group_finished notification.group
|
32
|
+
end
|
33
|
+
|
34
|
+
def example_started(notification)
|
35
|
+
profiler.example_started notification.example
|
36
|
+
end
|
37
|
+
|
38
|
+
def example_finished(notification)
|
39
|
+
profiler.example_finished notification.example
|
40
|
+
end
|
41
|
+
|
42
|
+
def print
|
43
|
+
reporter.print(profiler)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Register TPSProf listener
|
50
|
+
TestProf.activate("TPS_PROF") do
|
51
|
+
RSpec.configure do |config|
|
52
|
+
listener = nil
|
53
|
+
|
54
|
+
config.before(:suite) do
|
55
|
+
listener = TestProf::TPSProf::RSpecListener.new
|
56
|
+
config.reporter.register_listener(
|
57
|
+
listener, *TestProf::TPSProf::RSpecListener::NOTIFICATIONS
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
config.after(:suite) { listener&.print }
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_prof/tps_prof/profiler"
|
4
|
+
require "test_prof/tps_prof/reporter/text"
|
5
|
+
|
6
|
+
module TestProf
|
7
|
+
# TPSProf shows top-N example group based on their tests-per-second value.
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# TPS_PROF=10 rspec ...
|
12
|
+
#
|
13
|
+
module TPSProf
|
14
|
+
class Configuration
|
15
|
+
attr_accessor :top_count, :threshold, :reporter
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@top_count = ENV["TPS_PROF"].to_i
|
19
|
+
@top_count = 10 if @top_count == 1
|
20
|
+
@threshold = ENV.fetch("TPS_PROF_MIN", 10).to_i
|
21
|
+
@reporter = resolve_reporter(ENV["TPS_PROF_FORMAT"])
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def resolve_reporter(format)
|
27
|
+
# TODO: support other formats
|
28
|
+
TPSProf::Reporter::Text.new
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class << self
|
33
|
+
def config
|
34
|
+
@config ||= Configuration.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def configure
|
38
|
+
yield config
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
require "test_prof/tps_prof/rspec" if TestProf.rspec?
|
45
|
+
# TODO: Minitest support
|
46
|
+
# require "test_prof/tps_prof/minitest" if TestProf.minitest?
|
data/lib/test_prof/vernier.rb
CHANGED
@@ -19,7 +19,7 @@ module TestProf
|
|
19
19
|
module Vernier
|
20
20
|
# Vernier configuration
|
21
21
|
class Configuration
|
22
|
-
attr_accessor :mode, :target, :interval
|
22
|
+
attr_accessor :mode, :target, :interval, :hooks
|
23
23
|
|
24
24
|
def initialize
|
25
25
|
@mode = ENV.fetch("TEST_VERNIER_MODE", :wall).to_sym
|
@@ -27,6 +27,7 @@ module TestProf
|
|
27
27
|
|
28
28
|
sample_interval = ENV["TEST_VERNIER_INTERVAL"].to_i
|
29
29
|
@interval = (sample_interval > 0) ? sample_interval : nil
|
30
|
+
@hooks = ENV["TEST_VERNIER_HOOKS"]&.split(",")&.map { |hook| hook.strip.to_sym }
|
30
31
|
end
|
31
32
|
|
32
33
|
def boot?
|
@@ -82,6 +83,7 @@ module TestProf
|
|
82
83
|
options = {}
|
83
84
|
|
84
85
|
options[:interval] = config.interval if config.interval
|
86
|
+
options[:hooks] = config.hooks if config.hooks
|
85
87
|
|
86
88
|
if block_given?
|
87
89
|
options[:mode] = config.mode
|
data/lib/test_prof/version.rb
CHANGED
data/lib/test_prof.rb
CHANGED