test-prof 1.6.3 → 1.6.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 +12 -0
- data/README.md +3 -6
- data/lib/minitest/test_prof_plugin.rb +3 -0
- data/lib/test_prof/before_all/adapters/active_record.rb +57 -17
- data/lib/test_prof/event_prof/minitest.rb +2 -4
- data/lib/test_prof/event_prof/printers/base.rb +29 -0
- data/lib/test_prof/event_prof/printers/json.rb +72 -0
- data/lib/test_prof/event_prof/printers/simple.rb +76 -0
- data/lib/test_prof/event_prof/rspec.rb +3 -61
- data/lib/test_prof/event_prof.rb +12 -1
- data/lib/test_prof/recipes/minitest/before_all.rb +26 -17
- data/lib/test_prof/rspec_dissect/collector.rb +11 -1
- data/lib/test_prof/version.rb +1 -1
- metadata +5 -3
- data/lib/minitest/event_prof_formatter.rb +0 -87
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f0609d37fef8dc561de55a0f192414738049f53a4c47772fad6d6f1adaa596a1
|
|
4
|
+
data.tar.gz: b7bef544e98f887c86650268907adca116b30f87bf6d9c7c2a846ac6b2fe3649
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fcb4fc774acc4ac145817c00a7b8530151791481d32016375d54c5519c430dca9b2cdfe7064d1f05f633697b55ddbeefc6343b38c82e81944304481f24845ab7
|
|
7
|
+
data.tar.gz: e011f71777982864fd1dc5c1537872631eb3aeada674e6aa8e5be1e70648feee519eac9b02e1175f2b42117d07cd2fb1ff0e26c1fa0a2e4adc2b721cda780df9
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## master (unreleased)
|
|
4
4
|
|
|
5
|
+
## 1.6.4 (2026-10-09)
|
|
6
|
+
|
|
7
|
+
- Fix `RSpecDissect` writing bold escape sequences when the output is not a TTY. ([@meganemura][])
|
|
8
|
+
|
|
9
|
+
- Fix `before_all` with parallel Minitest 6. ([@GabrielNagy][])
|
|
10
|
+
|
|
11
|
+
- Add `EVENT_PROF_FORMAT` (`json`) support to EventProf along with the printers infrastructure (`Printers::Simple`, `Printers::Json`). ([@lHydra][])
|
|
12
|
+
|
|
13
|
+
- Fix `before_all` Minitest `parallelize` patch rejecting Rails 7.0+ keyword arguments (`threshold:`, `parallelize_databases:`). ([@hnegishi][])
|
|
14
|
+
|
|
5
15
|
## 1.6.3 (2026-07-20)
|
|
6
16
|
|
|
7
17
|
- Fix `before_all` firing N times in Minitest 6. ([@palkan][])
|
|
@@ -519,3 +529,5 @@ See [changelog](https://github.com/test-prof/test-prof/blob/v0.8.0/CHANGELOG.md)
|
|
|
519
529
|
[@Rylan12]: https://github.com/Rylan12
|
|
520
530
|
[@kddnewton]: https://github.com/kddnewton
|
|
521
531
|
[@moznion]: https://github.com/moznion
|
|
532
|
+
[@hnegishi]: https://github.com/hnegishi
|
|
533
|
+
[@meganemura]: https://github.com/meganemura
|
data/README.md
CHANGED
|
@@ -37,12 +37,9 @@ TestProf toolbox aims to help you identify bottlenecks in your test suite. It co
|
|
|
37
37
|
</a>
|
|
38
38
|
</p>
|
|
39
39
|
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
alt="Sponsored by Evil Martians" width="236" height="54">
|
|
44
|
-
</a>
|
|
45
|
-
</p>
|
|
40
|
+
<br/>
|
|
41
|
+
|
|
42
|
+
<img src="https://cdn.evilmartians.com/badges/logo-no-label.svg" alt="Evil Martians logo" width="22" height="16" /> <b>TestProf</b> is built by <b><a href="https://evilmartians.com/">Evil Martians</a></b>, an American design and engineering consultancy for <b>developer tools, AI, and cybersecurity startups</b>.
|
|
46
43
|
|
|
47
44
|
## Who uses TestProf
|
|
48
45
|
|
|
@@ -35,6 +35,9 @@ module Minitest # :nodoc:
|
|
|
35
35
|
opts.on "--event-prof-per-example", TrueClass, "Includes examples metrics to results" do |flag|
|
|
36
36
|
options[:per_example] = flag
|
|
37
37
|
end
|
|
38
|
+
opts.on "--event-prof-format=FORMAT", "Defines output FORMAT for results" do |format|
|
|
39
|
+
options[:event_prof_format] = format
|
|
40
|
+
end
|
|
38
41
|
opts.on "--factory-doctor", TrueClass, "Enable Factory Doctor for your examples" do |flag|
|
|
39
42
|
options[:fdoc] = flag
|
|
40
43
|
end
|
|
@@ -10,47 +10,87 @@ module TestProf
|
|
|
10
10
|
class << self
|
|
11
11
|
if ::ActiveRecord::Base.connection.pool.respond_to?(:pin_connection!)
|
|
12
12
|
def begin_transaction
|
|
13
|
-
|
|
13
|
+
# don't rely on connection_handler.connection_pool_list,
|
|
14
|
+
# track pinned pools ourselves
|
|
15
|
+
# see https://github.com/rails/rails/pull/58489
|
|
16
|
+
pinned_pools = []
|
|
17
|
+
pinned_pools_stack.push(pinned_pools)
|
|
18
|
+
subscribe! if pinned_pools_stack.size == 1
|
|
19
|
+
|
|
14
20
|
::ActiveRecord::Base.connection_handler.connection_pool_list(:writing).each do |pool|
|
|
15
|
-
|
|
21
|
+
pin_pool!(pool)
|
|
22
|
+
pinned_pools << pool
|
|
16
23
|
end
|
|
17
24
|
end
|
|
18
25
|
|
|
19
26
|
def rollback_transaction
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
unsubscribe!
|
|
27
|
+
pinned_pools = pinned_pools_stack.pop
|
|
28
|
+
pinned_pools&.each { |pool| unpin_pool!(pool) }
|
|
29
|
+
ensure
|
|
30
|
+
unsubscribe! if pinned_pools_stack.empty?
|
|
24
31
|
end
|
|
25
32
|
|
|
26
|
-
def
|
|
27
|
-
Thread.current[:
|
|
28
|
-
|
|
33
|
+
def pinned_pools_stack
|
|
34
|
+
Thread.current[:before_all_pinned_pools_stack] ||= []
|
|
35
|
+
end
|
|
29
36
|
|
|
30
|
-
|
|
37
|
+
def subscribe!
|
|
38
|
+
# notifications are not Thread-pinned, so
|
|
39
|
+
# we must keep a reference to the current stack
|
|
40
|
+
stack = pinned_pools_stack
|
|
31
41
|
|
|
32
42
|
Thread.current[:before_all_connection_subscriber] = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
|
|
33
43
|
connection_name = payload[:connection_name] if payload.key?(:connection_name)
|
|
34
44
|
shard = payload[:shard] if payload.key?(:shard)
|
|
45
|
+
# Use the role of the established connection, not the current one
|
|
46
|
+
role = payload.fetch(:role, ::ActiveRecord::Base.current_role)
|
|
35
47
|
next unless connection_name
|
|
36
48
|
|
|
37
|
-
pool = ::ActiveRecord::Base.connection_handler.retrieve_connection_pool(connection_name, shard: shard)
|
|
49
|
+
pool = ::ActiveRecord::Base.connection_handler.retrieve_connection_pool(connection_name, role: role, shard: shard)
|
|
38
50
|
next unless pool && pool.role == :writing
|
|
39
51
|
|
|
40
|
-
|
|
52
|
+
pinned_pools = stack.last
|
|
53
|
+
next if pinned_pools.nil? || pinned_pools.include?(pool)
|
|
54
|
+
|
|
55
|
+
pin_pool!(pool)
|
|
56
|
+
pinned_pools << pool
|
|
41
57
|
end
|
|
42
58
|
end
|
|
43
59
|
|
|
44
60
|
def unsubscribe!
|
|
45
|
-
return unless Thread.current[:
|
|
46
|
-
|
|
47
|
-
Thread.current[:before_all_subscription_count] -= 1
|
|
48
|
-
|
|
49
|
-
return unless Thread.current[:before_all_subscription_count] == 0 && Thread.current[:before_all_connection_subscriber]
|
|
61
|
+
return unless Thread.current[:before_all_connection_subscriber]
|
|
50
62
|
|
|
51
63
|
ActiveSupport::Notifications.unsubscribe(Thread.current[:before_all_connection_subscriber])
|
|
52
64
|
Thread.current[:before_all_connection_subscriber] = nil
|
|
53
65
|
end
|
|
66
|
+
|
|
67
|
+
# Rails 8.2+
|
|
68
|
+
if ::ActiveRecord.version >= Gem::Version.new("8.2.0.alpha")
|
|
69
|
+
def pin_pool!(pool)
|
|
70
|
+
pool.pin_connection!(true)
|
|
71
|
+
pool.lease_connection.begin_transaction(joinable: false, _lazy: false)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def unpin_pool!(pool)
|
|
75
|
+
connection = pool.lease_connection
|
|
76
|
+
if connection.transaction_open?
|
|
77
|
+
connection.rollback_transaction
|
|
78
|
+
else
|
|
79
|
+
warn "!!! before_all transaction has been already rollbacked and could work incorrectly"
|
|
80
|
+
connection.reset!
|
|
81
|
+
end
|
|
82
|
+
ensure
|
|
83
|
+
pool.unpin_connection!
|
|
84
|
+
end
|
|
85
|
+
else
|
|
86
|
+
def pin_pool!(pool)
|
|
87
|
+
pool.pin_connection!(true)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def unpin_pool!(pool)
|
|
91
|
+
pool.unpin_connection!
|
|
92
|
+
end
|
|
93
|
+
end
|
|
54
94
|
else
|
|
55
95
|
def all_connections
|
|
56
96
|
@all_connections ||= if ::ActiveRecord::Base.respond_to? :connects_to
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "minitest/base_reporter"
|
|
4
|
-
require "minitest/event_prof_formatter"
|
|
5
4
|
|
|
6
5
|
module Minitest
|
|
7
6
|
module TestProf
|
|
@@ -12,7 +11,6 @@ module Minitest
|
|
|
12
11
|
|
|
13
12
|
log :info, "EventProf enabled (#{@profiler.events.join(", ")})"
|
|
14
13
|
|
|
15
|
-
@formatter = EventProfFormatter.new(@profiler)
|
|
16
14
|
@current_group = nil
|
|
17
15
|
@current_example = nil
|
|
18
16
|
end
|
|
@@ -32,8 +30,7 @@ module Minitest
|
|
|
32
30
|
|
|
33
31
|
def report
|
|
34
32
|
@profiler.group_finished(@current_group)
|
|
35
|
-
|
|
36
|
-
log :info, result
|
|
33
|
+
::TestProf::EventProf.config.printer.dump(@profiler)
|
|
37
34
|
end
|
|
38
35
|
|
|
39
36
|
private
|
|
@@ -64,6 +61,7 @@ module Minitest
|
|
|
64
61
|
def configure_profiler(options)
|
|
65
62
|
::TestProf::EventProf.configure do |config|
|
|
66
63
|
config.event = options[:event]
|
|
64
|
+
config.format = options[:event_prof_format] if options[:event_prof_format]
|
|
67
65
|
config.rank_by = options[:rank_by] if options[:rank_by]
|
|
68
66
|
config.top_count = options[:top_count] if options[:top_count]
|
|
69
67
|
config.per_example = options[:per_example] if options[:per_example]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TestProf
|
|
4
|
+
module EventProf
|
|
5
|
+
module Printers
|
|
6
|
+
module Base
|
|
7
|
+
include TestProf::Logging
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def describe_group(id)
|
|
12
|
+
id.is_a?(Hash) ? id[:name] : id.top_level_description
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def describe_example(id)
|
|
16
|
+
id.is_a?(Hash) ? id[:name] : id.description
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def locate(id)
|
|
20
|
+
id.is_a?(Hash) ? id[:location] : id.metadata[:location]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def time_percentage(time, total_time)
|
|
24
|
+
(time / total_time * 100).round(2)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_prof/event_prof/printers/base"
|
|
4
|
+
require "test_prof/ext/float_duration"
|
|
5
|
+
|
|
6
|
+
module TestProf
|
|
7
|
+
module EventProf
|
|
8
|
+
module Printers
|
|
9
|
+
module Json # :nodoc: all
|
|
10
|
+
class << self
|
|
11
|
+
using TestProf::FloatDuration
|
|
12
|
+
|
|
13
|
+
include Base
|
|
14
|
+
|
|
15
|
+
def dump(profilers)
|
|
16
|
+
data = []
|
|
17
|
+
profilers.each { |profiler| data << convert_results(profiler) }
|
|
18
|
+
|
|
19
|
+
outpath = TestProf.artifact_path("event-prof.json")
|
|
20
|
+
File.write(outpath, data.to_json)
|
|
21
|
+
|
|
22
|
+
log :info, "EventProf results to JSON: #{outpath}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def convert_results(profiler)
|
|
28
|
+
result = profiler.results
|
|
29
|
+
|
|
30
|
+
{
|
|
31
|
+
event: profiler.event,
|
|
32
|
+
total_time: profiler.total_time.duration,
|
|
33
|
+
total_count: profiler.total_count,
|
|
34
|
+
absolute_run_time: profiler.absolute_run_time.duration,
|
|
35
|
+
time_percentage: time_percentage(profiler.total_time, profiler.absolute_run_time),
|
|
36
|
+
rank_by: profiler.rank_by,
|
|
37
|
+
top_count: profiler.top_count,
|
|
38
|
+
groups: result[:groups].map { |group| convert_group(group) }
|
|
39
|
+
}.tap do |data|
|
|
40
|
+
next unless result[:examples]
|
|
41
|
+
|
|
42
|
+
data[:examples] = result[:examples].map { |example| convert_example(example) }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def convert_group(group)
|
|
47
|
+
{
|
|
48
|
+
description: describe_group(group[:id]),
|
|
49
|
+
location: locate(group[:id]),
|
|
50
|
+
time: group[:time].duration,
|
|
51
|
+
run_time: group[:run_time].duration,
|
|
52
|
+
time_percentage: time_percentage(group[:time], group[:run_time]),
|
|
53
|
+
count: group[:count],
|
|
54
|
+
examples: group[:examples]
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def convert_example(example)
|
|
59
|
+
{
|
|
60
|
+
description: describe_example(example[:id]),
|
|
61
|
+
location: locate(example[:id]),
|
|
62
|
+
time: example[:time].duration,
|
|
63
|
+
run_time: example[:run_time].duration,
|
|
64
|
+
time_percentage: time_percentage(example[:time], example[:run_time]),
|
|
65
|
+
count: example[:count]
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_prof/event_prof/printers/base"
|
|
4
|
+
require "test_prof/ext/float_duration"
|
|
5
|
+
require "test_prof/ext/string_truncate"
|
|
6
|
+
|
|
7
|
+
module TestProf
|
|
8
|
+
module EventProf
|
|
9
|
+
module Printers
|
|
10
|
+
module Simple # :nodoc: all
|
|
11
|
+
class << self
|
|
12
|
+
using TestProf::FloatDuration
|
|
13
|
+
using TestProf::StringTruncate
|
|
14
|
+
|
|
15
|
+
include Base
|
|
16
|
+
|
|
17
|
+
def dump(profilers)
|
|
18
|
+
profilers.each { |profiler| dump_profiler(profiler) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def dump_profiler(profiler)
|
|
24
|
+
result = profiler.results
|
|
25
|
+
time_percentage = time_percentage(profiler.total_time, profiler.absolute_run_time)
|
|
26
|
+
|
|
27
|
+
msgs = []
|
|
28
|
+
|
|
29
|
+
msgs <<
|
|
30
|
+
<<~MSG
|
|
31
|
+
EventProf results for #{profiler.event}
|
|
32
|
+
|
|
33
|
+
Total time: #{profiler.total_time.duration} of #{profiler.absolute_run_time.duration} (#{time_percentage}%)
|
|
34
|
+
Total events: #{profiler.total_count}
|
|
35
|
+
|
|
36
|
+
Top #{profiler.top_count} slowest suites (by #{profiler.rank_by}):
|
|
37
|
+
|
|
38
|
+
MSG
|
|
39
|
+
|
|
40
|
+
result[:groups].each do |group|
|
|
41
|
+
description = describe_group(group[:id])
|
|
42
|
+
location = locate(group[:id])
|
|
43
|
+
time = group[:time]
|
|
44
|
+
run_time = group[:run_time]
|
|
45
|
+
time_percentage = time_percentage(time, run_time)
|
|
46
|
+
|
|
47
|
+
msgs <<
|
|
48
|
+
<<~GROUP
|
|
49
|
+
#{description.truncate} (#{location}) – #{time.duration} (#{group[:count]} / #{group[:examples]}) of #{run_time.duration} (#{time_percentage}%)
|
|
50
|
+
GROUP
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if result[:examples]
|
|
54
|
+
msgs << "\nTop #{profiler.top_count} slowest tests (by #{profiler.rank_by}):\n\n"
|
|
55
|
+
|
|
56
|
+
result[:examples].each do |example|
|
|
57
|
+
description = describe_example(example[:id])
|
|
58
|
+
location = locate(example[:id])
|
|
59
|
+
time = example[:time]
|
|
60
|
+
run_time = example[:run_time]
|
|
61
|
+
time_percentage = time_percentage(time, run_time)
|
|
62
|
+
|
|
63
|
+
msgs <<
|
|
64
|
+
<<~GROUP
|
|
65
|
+
#{description.truncate} (#{location}) – #{time.duration} (#{example[:count]}) of #{run_time.duration} (#{time_percentage}%)
|
|
66
|
+
GROUP
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
log :info, msgs.join
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "test_prof/ext/float_duration"
|
|
4
|
-
require "test_prof/ext/string_truncate"
|
|
5
|
-
|
|
6
3
|
module TestProf
|
|
7
4
|
module EventProf
|
|
8
5
|
class RSpecListener # :nodoc:
|
|
9
6
|
include Logging
|
|
10
7
|
|
|
11
|
-
using FloatDuration
|
|
12
|
-
using StringTruncate
|
|
13
|
-
|
|
14
8
|
NOTIFICATIONS = %i[
|
|
15
9
|
example_group_started
|
|
16
10
|
example_group_finished
|
|
@@ -43,59 +37,11 @@ module TestProf
|
|
|
43
37
|
end
|
|
44
38
|
|
|
45
39
|
def print
|
|
46
|
-
@profiler
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def report(profiler)
|
|
50
|
-
result = profiler.results
|
|
51
|
-
time_percentage = time_percentage(profiler.total_time, profiler.absolute_run_time)
|
|
52
|
-
|
|
53
|
-
msgs = []
|
|
54
|
-
|
|
55
|
-
msgs <<
|
|
56
|
-
<<~MSG
|
|
57
|
-
EventProf results for #{profiler.event}
|
|
58
|
-
|
|
59
|
-
Total time: #{profiler.total_time.duration} of #{profiler.absolute_run_time.duration} (#{time_percentage}%)
|
|
60
|
-
Total events: #{profiler.total_count}
|
|
40
|
+
EventProf.config.printer.dump(@profiler)
|
|
61
41
|
|
|
62
|
-
|
|
42
|
+
return unless EventProf.config.stamp?
|
|
63
43
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
result[:groups].each do |group|
|
|
67
|
-
description = group[:id].top_level_description
|
|
68
|
-
location = group[:id].metadata[:location]
|
|
69
|
-
time = group[:time]
|
|
70
|
-
run_time = group[:run_time]
|
|
71
|
-
time_percentage = time_percentage(time, run_time)
|
|
72
|
-
|
|
73
|
-
msgs <<
|
|
74
|
-
<<~GROUP
|
|
75
|
-
#{description.truncate} (#{location}) – #{time.duration} (#{group[:count]} / #{group[:examples]}) of #{run_time.duration} (#{time_percentage}%)
|
|
76
|
-
GROUP
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
if result[:examples]
|
|
80
|
-
msgs << "\nTop #{profiler.top_count} slowest tests (by #{profiler.rank_by}):\n\n"
|
|
81
|
-
|
|
82
|
-
result[:examples].each do |example|
|
|
83
|
-
description = example[:id].description
|
|
84
|
-
location = example[:id].metadata[:location]
|
|
85
|
-
time = example[:time]
|
|
86
|
-
run_time = example[:run_time]
|
|
87
|
-
time_percentage = time_percentage(time, run_time)
|
|
88
|
-
|
|
89
|
-
msgs <<
|
|
90
|
-
<<~GROUP
|
|
91
|
-
#{description.truncate} (#{location}) – #{time.duration} (#{example[:count]}) of #{run_time.duration} (#{time_percentage}%)
|
|
92
|
-
GROUP
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
log :info, msgs.join
|
|
97
|
-
|
|
98
|
-
stamp!(profiler) if EventProf.config.stamp?
|
|
44
|
+
@profiler.each { |profiler| stamp!(profiler) }
|
|
99
45
|
end
|
|
100
46
|
|
|
101
47
|
def stamp!(profiler)
|
|
@@ -130,10 +76,6 @@ module TestProf
|
|
|
130
76
|
|
|
131
77
|
log :info, msgs.join
|
|
132
78
|
end
|
|
133
|
-
|
|
134
|
-
def time_percentage(time, total_time)
|
|
135
|
-
(time / total_time * 100).round(2)
|
|
136
|
-
end
|
|
137
79
|
end
|
|
138
80
|
end
|
|
139
81
|
end
|
data/lib/test_prof/event_prof.rb
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
require "test_prof/rspec_stamp"
|
|
4
4
|
require "test_prof/event_prof/profiler"
|
|
5
|
+
require "test_prof/event_prof/printers/base"
|
|
6
|
+
require "test_prof/event_prof/printers/simple"
|
|
7
|
+
require "test_prof/event_prof/printers/json"
|
|
5
8
|
require "test_prof/event_prof/instrumentations/active_support"
|
|
6
9
|
require "test_prof/event_prof/monitor"
|
|
7
10
|
require "test_prof/utils/sized_ordered_set"
|
|
@@ -34,7 +37,7 @@ module TestProf
|
|
|
34
37
|
}.freeze
|
|
35
38
|
|
|
36
39
|
attr_accessor :instrumenter, :top_count, :per_example,
|
|
37
|
-
:rank_by, :event
|
|
40
|
+
:rank_by, :event, :format
|
|
38
41
|
|
|
39
42
|
def initialize
|
|
40
43
|
@event = ENV["EVENT_PROF"]
|
|
@@ -42,6 +45,7 @@ module TestProf
|
|
|
42
45
|
@top_count = (ENV["EVENT_PROF_TOP"] || 5).to_i
|
|
43
46
|
@per_example = ENV["EVENT_PROF_EXAMPLES"] == "1"
|
|
44
47
|
@rank_by = (ENV["EVENT_PROF_RANK"] || :time).to_sym
|
|
48
|
+
@format = ENV["EVENT_PROF_FORMAT"]
|
|
45
49
|
@stamp = ENV["EVENT_PROF_STAMP"]
|
|
46
50
|
|
|
47
51
|
RSpecStamp.config.tags = @stamp if stamp?
|
|
@@ -55,6 +59,13 @@ module TestProf
|
|
|
55
59
|
per_example == true
|
|
56
60
|
end
|
|
57
61
|
|
|
62
|
+
def printer
|
|
63
|
+
case format.to_s
|
|
64
|
+
when "json" then Printers::Json
|
|
65
|
+
else Printers::Simple
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
58
69
|
def resolve_instrumenter
|
|
59
70
|
return instrumenter if instrumenter.is_a?(Module)
|
|
60
71
|
|
|
@@ -3,22 +3,13 @@
|
|
|
3
3
|
require "test_prof/before_all"
|
|
4
4
|
|
|
5
5
|
Minitest.singleton_class.prepend(Module.new do
|
|
6
|
-
|
|
6
|
+
attr_accessor :previous_klass
|
|
7
7
|
@previous_klass = nil
|
|
8
|
+
end)
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return super unless klass.respond_to?(:parallelized) && klass.parallelized
|
|
13
|
-
|
|
14
|
-
if @previous_klass && @previous_klass != klass
|
|
15
|
-
@previous_klass.before_all_executor&.deactivate!
|
|
16
|
-
end
|
|
17
|
-
@previous_klass = klass
|
|
18
|
-
|
|
19
|
-
super
|
|
20
|
-
end
|
|
21
|
-
else
|
|
10
|
+
# Minitest 5
|
|
11
|
+
unless Minitest::Runnable.respond_to?(:run_suite)
|
|
12
|
+
Minitest.singleton_class.prepend(Module.new do
|
|
22
13
|
def run_one_method(klass, *rest)
|
|
23
14
|
return super unless klass.respond_to?(:parallelized) && klass.parallelized
|
|
24
15
|
|
|
@@ -29,8 +20,8 @@ Minitest.singleton_class.prepend(Module.new do
|
|
|
29
20
|
|
|
30
21
|
super
|
|
31
22
|
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
23
|
+
end)
|
|
24
|
+
end
|
|
34
25
|
|
|
35
26
|
module TestProf
|
|
36
27
|
module BeforeAll
|
|
@@ -115,6 +106,24 @@ module TestProf
|
|
|
115
106
|
base.extend ClassMethods
|
|
116
107
|
|
|
117
108
|
base.cattr_accessor :parallelized
|
|
109
|
+
# Minitest 6+
|
|
110
|
+
if ::Minitest::Runnable.respond_to?(:run_suite)
|
|
111
|
+
base.prepend(Module.new do
|
|
112
|
+
def run(*)
|
|
113
|
+
klass = self.class
|
|
114
|
+
return super unless klass.parallelized
|
|
115
|
+
|
|
116
|
+
previous_klass = ::Minitest.previous_klass
|
|
117
|
+
if previous_klass && previous_klass != klass
|
|
118
|
+
previous_klass.before_all_executor&.deactivate!
|
|
119
|
+
end
|
|
120
|
+
::Minitest.previous_klass = klass
|
|
121
|
+
|
|
122
|
+
super
|
|
123
|
+
end
|
|
124
|
+
end)
|
|
125
|
+
end
|
|
126
|
+
|
|
118
127
|
if base.respond_to?(:parallelize_teardown)
|
|
119
128
|
base.parallelize_teardown do
|
|
120
129
|
last_klass = ::Minitest.previous_klass
|
|
@@ -126,7 +135,7 @@ module TestProf
|
|
|
126
135
|
|
|
127
136
|
if base.respond_to?(:parallelize)
|
|
128
137
|
base.singleton_class.prepend(Module.new do
|
|
129
|
-
def parallelize(workers: :number_of_processors, with: :processes)
|
|
138
|
+
def parallelize(workers: :number_of_processors, with: :processes, **options)
|
|
130
139
|
# super.parallelize returns nil when no parallelization is set up
|
|
131
140
|
if super.nil?
|
|
132
141
|
return
|
|
@@ -32,7 +32,7 @@ module TestProf # :nodoc: all
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def print_group_result(group)
|
|
35
|
-
"#{group[:desc].truncate} (#{group[:loc]}) –
|
|
35
|
+
"#{group[:desc].truncate} (#{group[:loc]}) – #{bold(group[:total_setup].duration)} " \
|
|
36
36
|
"of #{group[:total].duration} / #{group[:count]} " \
|
|
37
37
|
"(before: #{(group[:total_setup] - group[:total_lazy_let]).duration}, " \
|
|
38
38
|
"before let: #{group[:total_before_let].duration}, " \
|
|
@@ -53,6 +53,16 @@ module TestProf # :nodoc: all
|
|
|
53
53
|
|
|
54
54
|
msgs.join
|
|
55
55
|
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# Follow the logger's color setting, so that a report written to a file
|
|
60
|
+
# or a pipe has no escape sequences in it
|
|
61
|
+
def bold(str)
|
|
62
|
+
return str unless TestProf.config.color?
|
|
63
|
+
|
|
64
|
+
"\e[1m#{str}\e[22m"
|
|
65
|
+
end
|
|
56
66
|
end
|
|
57
67
|
end
|
|
58
68
|
end
|
data/lib/test_prof/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: test-prof
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.6.
|
|
4
|
+
version: 1.6.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vladimir Dementyev
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: logger
|
|
@@ -135,7 +135,6 @@ files:
|
|
|
135
135
|
- config/default.yml
|
|
136
136
|
- config/rubocop-rspec.yml
|
|
137
137
|
- lib/minitest/base_reporter.rb
|
|
138
|
-
- lib/minitest/event_prof_formatter.rb
|
|
139
138
|
- lib/minitest/test_prof_plugin.rb
|
|
140
139
|
- lib/rubocop/test_prof.rb
|
|
141
140
|
- lib/rubocop/test_prof/cops/rspec/aggregate_examples.rb
|
|
@@ -167,6 +166,9 @@ files:
|
|
|
167
166
|
- lib/test_prof/event_prof/instrumentations/active_support.rb
|
|
168
167
|
- lib/test_prof/event_prof/minitest.rb
|
|
169
168
|
- lib/test_prof/event_prof/monitor.rb
|
|
169
|
+
- lib/test_prof/event_prof/printers/base.rb
|
|
170
|
+
- lib/test_prof/event_prof/printers/json.rb
|
|
171
|
+
- lib/test_prof/event_prof/printers/simple.rb
|
|
170
172
|
- lib/test_prof/event_prof/profiler.rb
|
|
171
173
|
- lib/test_prof/event_prof/rspec.rb
|
|
172
174
|
- lib/test_prof/ext/active_record_refind.rb
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "test_prof/ext/float_duration"
|
|
4
|
-
require "test_prof/ext/string_truncate"
|
|
5
|
-
|
|
6
|
-
module Minitest
|
|
7
|
-
module TestProf
|
|
8
|
-
class EventProfFormatter # :nodoc:
|
|
9
|
-
using ::TestProf::FloatDuration
|
|
10
|
-
using ::TestProf::StringTruncate
|
|
11
|
-
|
|
12
|
-
def initialize(profilers)
|
|
13
|
-
@profilers = profilers
|
|
14
|
-
@results = []
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def prepare_results
|
|
18
|
-
@profilers.each do |profiler|
|
|
19
|
-
total_results(profiler)
|
|
20
|
-
by_groups(profiler)
|
|
21
|
-
by_examples(profiler)
|
|
22
|
-
end
|
|
23
|
-
@results.join
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
private
|
|
27
|
-
|
|
28
|
-
def total_results(profiler)
|
|
29
|
-
time_percentage = time_percentage(profiler.total_time, profiler.absolute_run_time)
|
|
30
|
-
|
|
31
|
-
@results <<
|
|
32
|
-
<<~MSG
|
|
33
|
-
EventProf results for #{profiler.event}
|
|
34
|
-
|
|
35
|
-
Total time: #{profiler.total_time.duration} of #{profiler.absolute_run_time.duration} (#{time_percentage}%)
|
|
36
|
-
Total events: #{profiler.total_count}
|
|
37
|
-
|
|
38
|
-
Top #{profiler.top_count} slowest suites (by #{profiler.rank_by}):
|
|
39
|
-
|
|
40
|
-
MSG
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def by_groups(profiler)
|
|
44
|
-
result = profiler.results
|
|
45
|
-
groups = result[:groups]
|
|
46
|
-
|
|
47
|
-
groups.each do |group|
|
|
48
|
-
description = group[:id][:name]
|
|
49
|
-
location = group[:id][:location]
|
|
50
|
-
time = group[:time]
|
|
51
|
-
run_time = group[:run_time]
|
|
52
|
-
time_percentage = time_percentage(time, run_time)
|
|
53
|
-
|
|
54
|
-
@results <<
|
|
55
|
-
<<~GROUP
|
|
56
|
-
#{description.truncate} (#{location}) – #{time.duration} (#{group[:count]} / #{group[:examples]}) of #{run_time.duration} (#{time_percentage}%)
|
|
57
|
-
GROUP
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def by_examples(profiler)
|
|
62
|
-
result = profiler.results
|
|
63
|
-
examples = result[:examples]
|
|
64
|
-
|
|
65
|
-
return unless examples
|
|
66
|
-
@results << "\nTop #{profiler.top_count} slowest tests (by #{profiler.rank_by}):\n\n"
|
|
67
|
-
|
|
68
|
-
examples.each do |example|
|
|
69
|
-
description = example[:id][:name]
|
|
70
|
-
location = example[:id][:location]
|
|
71
|
-
time = example[:time]
|
|
72
|
-
run_time = example[:run_time]
|
|
73
|
-
time_percentage = time_percentage(time, run_time)
|
|
74
|
-
|
|
75
|
-
@results <<
|
|
76
|
-
<<~GROUP
|
|
77
|
-
#{description.truncate} (#{location}) – #{time.duration} (#{example[:count]}) of #{run_time.duration} (#{time_percentage}%)
|
|
78
|
-
GROUP
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def time_percentage(time, total_time)
|
|
83
|
-
(time / total_time * 100).round(2)
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|