test-prof 0.4.9 → 0.5.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 +106 -20
- data/README.md +10 -57
- data/assets/tagprof.demo.html +447 -0
- data/assets/tagprof.template.html +447 -0
- data/lib/minitest/event_prof_formatter.rb +18 -16
- data/lib/test_prof/any_fixture/dsl.rb +18 -0
- data/lib/test_prof/any_fixture.rb +92 -8
- data/lib/test_prof/event_prof/custom_events/factory_create.rb +1 -1
- data/lib/test_prof/event_prof/custom_events/sidekiq_inline.rb +1 -1
- data/lib/test_prof/event_prof/custom_events/sidekiq_jobs.rb +1 -1
- data/lib/test_prof/event_prof/custom_events.rb +30 -0
- data/lib/test_prof/event_prof/minitest.rb +6 -0
- data/lib/test_prof/event_prof/profiler.rb +129 -0
- data/lib/test_prof/event_prof/rspec.rb +20 -11
- data/lib/test_prof/event_prof.rb +10 -108
- data/lib/test_prof/ext/active_record_refind.rb +20 -0
- data/lib/test_prof/factory_all_stub/factory_bot_patch.rb +13 -0
- data/lib/test_prof/factory_all_stub.rb +32 -0
- data/lib/test_prof/factory_doctor/rspec.rb +3 -2
- data/lib/test_prof/factory_prof/printers/flamegraph.rb +9 -13
- data/lib/test_prof/recipes/active_record_one_love.rb +4 -0
- data/lib/test_prof/recipes/active_record_shared_connection.rb +55 -0
- data/lib/test_prof/recipes/logging.rb +37 -0
- data/lib/test_prof/recipes/rspec/any_fixture.rb +4 -1
- data/lib/test_prof/recipes/rspec/factory_all_stub.rb +10 -0
- data/lib/test_prof/rspec_dissect/rspec.rb +4 -2
- data/lib/test_prof/rspec_stamp/rspec.rb +3 -2
- data/lib/test_prof/tag_prof/printers/html.rb +24 -0
- data/lib/test_prof/tag_prof/printers/simple.rb +82 -0
- data/lib/test_prof/tag_prof/result.rb +38 -0
- data/lib/test_prof/tag_prof/rspec.rb +43 -40
- data/lib/test_prof/tag_prof.rb +4 -0
- data/lib/test_prof/utils/html_builder.rb +21 -0
- data/lib/test_prof/version.rb +1 -1
- data/lib/test_prof.rb +2 -1
- metadata +20 -22
- data/assets/logo.svg +0 -1
- data/assets/testprof.png +0 -0
- data/guides/.rubocop.yml +0 -1
- data/guides/any_fixture.md +0 -114
- data/guides/before_all.md +0 -98
- data/guides/event_prof.md +0 -177
- data/guides/factory_default.md +0 -111
- data/guides/factory_doctor.md +0 -119
- data/guides/factory_prof.md +0 -86
- data/guides/let_it_be.md +0 -97
- data/guides/rspec_dissect.md +0 -60
- data/guides/rspec_stamp.md +0 -52
- data/guides/rubocop.md +0 -48
- data/guides/ruby_prof.md +0 -63
- data/guides/stack_prof.md +0 -47
- data/guides/tag_prof.md +0 -51
- data/guides/tests_sampling.md +0 -24
|
@@ -11,35 +11,37 @@ module Minitest
|
|
|
11
11
|
using ::TestProf::StringTruncate
|
|
12
12
|
using ::TestProf::StringStripHeredoc
|
|
13
13
|
|
|
14
|
-
def initialize(
|
|
15
|
-
@
|
|
14
|
+
def initialize(profilers)
|
|
15
|
+
@profilers = profilers
|
|
16
16
|
@results = []
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def prepare_results
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
@profilers.each do |profiler|
|
|
21
|
+
total_results(profiler)
|
|
22
|
+
by_groups(profiler)
|
|
23
|
+
by_examples(profiler)
|
|
24
|
+
end
|
|
23
25
|
@results.join
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
private
|
|
27
29
|
|
|
28
|
-
def total_results
|
|
30
|
+
def total_results(profiler)
|
|
29
31
|
@results <<
|
|
30
32
|
<<-MSG.strip_heredoc
|
|
31
|
-
EventProf results for #{
|
|
33
|
+
EventProf results for #{profiler.event}
|
|
32
34
|
|
|
33
|
-
Total time: #{
|
|
34
|
-
Total events: #{
|
|
35
|
+
Total time: #{profiler.total_time.duration}
|
|
36
|
+
Total events: #{profiler.total_count}
|
|
35
37
|
|
|
36
|
-
Top #{
|
|
38
|
+
Top #{profiler.top_count} slowest suites (by #{profiler.rank_by}):
|
|
37
39
|
|
|
38
40
|
MSG
|
|
39
41
|
end
|
|
40
42
|
|
|
41
|
-
def by_groups
|
|
42
|
-
|
|
43
|
+
def by_groups(profiler)
|
|
44
|
+
profiler.results[:groups].each do |group|
|
|
43
45
|
description = group[:id][:name]
|
|
44
46
|
location = group[:id][:location]
|
|
45
47
|
|
|
@@ -50,11 +52,11 @@ module Minitest
|
|
|
50
52
|
end
|
|
51
53
|
end
|
|
52
54
|
|
|
53
|
-
def by_examples
|
|
54
|
-
return unless
|
|
55
|
-
@results << "\nTop #{
|
|
55
|
+
def by_examples(profiler)
|
|
56
|
+
return unless profiler.results[:examples]
|
|
57
|
+
@results << "\nTop #{profiler.top_count} slowest tests (by #{profiler.rank_by}):\n\n"
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
profiler.results[:examples].each do |example|
|
|
58
60
|
description = example[:id][:name]
|
|
59
61
|
location = example[:id][:location]
|
|
60
62
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TestProf
|
|
4
|
+
module AnyFixture
|
|
5
|
+
# Adds "global" `fixture` method (through refinement)
|
|
6
|
+
module DSL
|
|
7
|
+
module Ext # :nodoc:
|
|
8
|
+
def fixture(id, &block)
|
|
9
|
+
::TestProf::AnyFixture.register(:"#{id}", &block)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
refine Kernel do
|
|
14
|
+
include Ext
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -1,27 +1,53 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "test_prof/ext/float_duration"
|
|
4
|
+
require "test_prof/ext/string_strip_heredoc"
|
|
5
|
+
|
|
3
6
|
module TestProf
|
|
4
7
|
# Make DB fixtures from blocks.
|
|
5
8
|
module AnyFixture
|
|
6
9
|
INSERT_RXP = /^INSERT INTO ([\S]+)/
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
using FloatDuration
|
|
12
|
+
using StringStripHeredoc
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
class Cache # :nodoc:
|
|
15
|
+
attr_reader :store, :stats
|
|
12
16
|
|
|
13
17
|
def initialize
|
|
14
18
|
@store = {}
|
|
19
|
+
@stats = {}
|
|
15
20
|
end
|
|
16
21
|
|
|
17
22
|
def fetch(key)
|
|
18
|
-
|
|
23
|
+
if store.key?(key)
|
|
24
|
+
stats[key][:hit] += 1
|
|
25
|
+
return store[key]
|
|
26
|
+
end
|
|
27
|
+
|
|
19
28
|
return unless block_given?
|
|
29
|
+
|
|
30
|
+
ts = TestProf.now
|
|
20
31
|
store[key] = yield
|
|
32
|
+
stats[key] = { time: TestProf.now - ts, hit: 0 }
|
|
33
|
+
store[key]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def clear
|
|
37
|
+
store.clear
|
|
38
|
+
stats.clear
|
|
21
39
|
end
|
|
22
40
|
end
|
|
23
41
|
|
|
24
42
|
class << self
|
|
43
|
+
include Logging
|
|
44
|
+
|
|
45
|
+
attr_accessor :reporting_enabled
|
|
46
|
+
|
|
47
|
+
def reporting_enabled?
|
|
48
|
+
reporting_enabled == true
|
|
49
|
+
end
|
|
50
|
+
|
|
25
51
|
# Register a block of code as a fixture,
|
|
26
52
|
# returns the result of the block execution
|
|
27
53
|
def register(id)
|
|
@@ -34,10 +60,12 @@ module TestProf
|
|
|
34
60
|
|
|
35
61
|
# Clean all affected tables (but do not reset cache)
|
|
36
62
|
def clean
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
63
|
+
disable_referential_integrity do
|
|
64
|
+
tables_cache.keys.reverse_each do |table|
|
|
65
|
+
ActiveRecord::Base.connection.execute %(
|
|
66
|
+
DELETE FROM #{table}
|
|
67
|
+
)
|
|
68
|
+
end
|
|
41
69
|
end
|
|
42
70
|
end
|
|
43
71
|
|
|
@@ -53,6 +81,54 @@ module TestProf
|
|
|
53
81
|
tables_cache[matches[1]] = true if matches
|
|
54
82
|
end
|
|
55
83
|
|
|
84
|
+
def report_stats
|
|
85
|
+
msgs = []
|
|
86
|
+
|
|
87
|
+
msgs <<
|
|
88
|
+
<<-MSG.strip_heredoc
|
|
89
|
+
AnyFixture usage stats:
|
|
90
|
+
MSG
|
|
91
|
+
|
|
92
|
+
first_column = cache.stats.keys.map(&:size).max + 2
|
|
93
|
+
|
|
94
|
+
msgs << format(
|
|
95
|
+
"%#{first_column}s %12s %9s %12s",
|
|
96
|
+
'key', 'build time', 'hit count', 'saved time'
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
msgs << ""
|
|
100
|
+
|
|
101
|
+
total_spent = 0.0
|
|
102
|
+
total_saved = 0.0
|
|
103
|
+
total_miss = 0.0
|
|
104
|
+
|
|
105
|
+
cache.stats.to_a.sort_by { |(_, v)| -v[:hit] }.each do |(key, stats)|
|
|
106
|
+
total_spent += stats[:time]
|
|
107
|
+
|
|
108
|
+
saved = stats[:time] * stats[:hit]
|
|
109
|
+
|
|
110
|
+
total_saved += saved
|
|
111
|
+
|
|
112
|
+
total_miss += stats[:time] if stats[:hit].zero?
|
|
113
|
+
|
|
114
|
+
msgs << format(
|
|
115
|
+
"%#{first_column}s %12s %9d %12s",
|
|
116
|
+
key, stats[:time].duration, stats[:hit],
|
|
117
|
+
saved.duration
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
msgs <<
|
|
122
|
+
<<-MSG.strip_heredoc
|
|
123
|
+
|
|
124
|
+
Total time spent: #{total_spent.duration}
|
|
125
|
+
Total time saved: #{total_saved.duration}
|
|
126
|
+
Total time wasted: #{total_miss.duration}
|
|
127
|
+
MSG
|
|
128
|
+
|
|
129
|
+
log :info, msgs.join("\n")
|
|
130
|
+
end
|
|
131
|
+
|
|
56
132
|
private
|
|
57
133
|
|
|
58
134
|
def cache
|
|
@@ -62,6 +138,14 @@ module TestProf
|
|
|
62
138
|
def tables_cache
|
|
63
139
|
@tables_cache ||= {}
|
|
64
140
|
end
|
|
141
|
+
|
|
142
|
+
def disable_referential_integrity
|
|
143
|
+
connection = ActiveRecord::Base.connection
|
|
144
|
+
return yield unless connection.respond_to?(:disable_referential_integrity)
|
|
145
|
+
connection.disable_referential_integrity { yield }
|
|
146
|
+
end
|
|
65
147
|
end
|
|
148
|
+
|
|
149
|
+
self.reporting_enabled = ENV['ANYFIXTURE_REPORT'] == '1'
|
|
66
150
|
end
|
|
67
151
|
end
|
|
@@ -44,7 +44,7 @@ module TestProf::EventProf::CustomEvents
|
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
TestProf.
|
|
47
|
+
TestProf::EventProf::CustomEvents.register("factory.create") do
|
|
48
48
|
if defined? TestProf::FactoryBot
|
|
49
49
|
TestProf::EventProf::CustomEvents::FactoryCreate.setup!
|
|
50
50
|
else
|
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
module TestProf
|
|
4
|
+
module EventProf
|
|
5
|
+
# Registers and activates custom events (which require patches).
|
|
6
|
+
module CustomEvents
|
|
7
|
+
class << self
|
|
8
|
+
def register(event)
|
|
9
|
+
raise ArgumentError, "Block is required!" unless block_given?
|
|
10
|
+
registrations[event] = Proc.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def activate_all(events)
|
|
14
|
+
events = events.split(",")
|
|
15
|
+
events.each { |event| try_activate(event) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def try_activate(event)
|
|
19
|
+
return unless registrations.key?(event)
|
|
20
|
+
registrations.delete(event).call
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def registrations
|
|
26
|
+
@registrations ||= {}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
3
33
|
require "test_prof/event_prof/custom_events/factory_create"
|
|
4
34
|
require "test_prof/event_prof/custom_events/sidekiq_inline"
|
|
5
35
|
require "test_prof/event_prof/custom_events/sidekiq_jobs"
|
|
@@ -9,6 +9,9 @@ module Minitest
|
|
|
9
9
|
def initialize(io = $stdout, options = {})
|
|
10
10
|
super
|
|
11
11
|
@profiler = configure_profiler(options)
|
|
12
|
+
|
|
13
|
+
log :info, "EventProf enabled (#{@profiler.events.join(', ')})"
|
|
14
|
+
|
|
12
15
|
@formatter = EventProfFormatter.new(@profiler)
|
|
13
16
|
@current_group = nil
|
|
14
17
|
@current_example = nil
|
|
@@ -64,7 +67,10 @@ module Minitest
|
|
|
64
67
|
config.rank_by = options[:rank_by] if options[:rank_by]
|
|
65
68
|
config.top_count = options[:top_count] if options[:top_count]
|
|
66
69
|
config.per_example = options[:per_example] if options[:per_example]
|
|
70
|
+
|
|
71
|
+
::TestProf::EventProf::CustomEvents.activate_all config.event
|
|
67
72
|
end
|
|
73
|
+
|
|
68
74
|
::TestProf::EventProf.build
|
|
69
75
|
end
|
|
70
76
|
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TestProf
|
|
4
|
+
module EventProf
|
|
5
|
+
class Profiler # :nodoc:
|
|
6
|
+
attr_reader :event, :total_count, :total_time, :rank_by, :top_count, :per_example,
|
|
7
|
+
:time, :count, :example_time, :example_count
|
|
8
|
+
|
|
9
|
+
alias per_example? per_example
|
|
10
|
+
|
|
11
|
+
def initialize(event:, instrumenter:, rank_by: :time, top_count: 5, per_example: false)
|
|
12
|
+
@event = event
|
|
13
|
+
@rank_by = rank_by
|
|
14
|
+
@top_count = top_count
|
|
15
|
+
@per_example = per_example
|
|
16
|
+
|
|
17
|
+
instrumenter.subscribe(event) { |time| track(time) }
|
|
18
|
+
|
|
19
|
+
@groups = Utils::SizedOrderedSet.new(
|
|
20
|
+
top_count, sort_by: rank_by
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
@examples = Utils::SizedOrderedSet.new(
|
|
24
|
+
top_count, sort_by: rank_by
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@total_count = 0
|
|
28
|
+
@total_time = 0.0
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def track(time)
|
|
32
|
+
return if @current_group.nil?
|
|
33
|
+
@total_time += time
|
|
34
|
+
@total_count += 1
|
|
35
|
+
|
|
36
|
+
@time += time
|
|
37
|
+
@count += 1
|
|
38
|
+
|
|
39
|
+
return if @current_example.nil?
|
|
40
|
+
|
|
41
|
+
@example_time += time
|
|
42
|
+
@example_count += 1
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def group_started(id)
|
|
46
|
+
reset_group!
|
|
47
|
+
@current_group = id
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def group_finished(id)
|
|
51
|
+
data = { id: id, time: @time, count: @count, examples: @total_examples }
|
|
52
|
+
|
|
53
|
+
@groups << data unless data[rank_by].zero?
|
|
54
|
+
|
|
55
|
+
@current_group = nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def example_started(id)
|
|
59
|
+
return unless per_example?
|
|
60
|
+
reset_example!
|
|
61
|
+
@current_example = id
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def example_finished(id)
|
|
65
|
+
@total_examples += 1
|
|
66
|
+
return unless per_example?
|
|
67
|
+
|
|
68
|
+
data = { id: id, time: @example_time, count: @example_count }
|
|
69
|
+
@examples << data unless data[rank_by].zero?
|
|
70
|
+
@current_example = nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def results
|
|
74
|
+
{
|
|
75
|
+
groups: @groups.to_a
|
|
76
|
+
}.tap do |data|
|
|
77
|
+
next unless per_example?
|
|
78
|
+
|
|
79
|
+
data[:examples] = @examples.to_a
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def reset_group!
|
|
86
|
+
@time = 0.0
|
|
87
|
+
@count = 0
|
|
88
|
+
@total_examples = 0
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def reset_example!
|
|
92
|
+
@example_count = 0
|
|
93
|
+
@example_time = 0.0
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Multiple profilers wrapper
|
|
98
|
+
class ProfilersGroup
|
|
99
|
+
attr_reader :profilers
|
|
100
|
+
|
|
101
|
+
def initialize(event:, **options)
|
|
102
|
+
events = event.split(",")
|
|
103
|
+
@profilers = events.map do |ev|
|
|
104
|
+
Profiler.new(event: ev, **options)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def each
|
|
109
|
+
if block_given?
|
|
110
|
+
@profilers.each(&Proc.new)
|
|
111
|
+
else
|
|
112
|
+
@profilers.each
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def events
|
|
117
|
+
@profilers.map(&:event)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
%i[group_started group_finished example_started example_finished].each do |name|
|
|
121
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
|
122
|
+
def #{name}(id)
|
|
123
|
+
@profilers.each { |profiler| profiler.#{name}(id) }
|
|
124
|
+
end
|
|
125
|
+
CODE
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -23,6 +23,8 @@ module TestProf
|
|
|
23
23
|
|
|
24
24
|
def initialize
|
|
25
25
|
@profiler = EventProf.build
|
|
26
|
+
|
|
27
|
+
log :info, "EventProf enabled (#{@profiler.events.join(', ')})"
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
def example_group_started(notification)
|
|
@@ -49,18 +51,22 @@ module TestProf
|
|
|
49
51
|
alias example_pending example_finished
|
|
50
52
|
|
|
51
53
|
def print
|
|
52
|
-
|
|
54
|
+
@profiler.each(&method(:report))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def report(profiler)
|
|
58
|
+
result = profiler.results
|
|
53
59
|
|
|
54
60
|
msgs = []
|
|
55
61
|
|
|
56
62
|
msgs <<
|
|
57
63
|
<<-MSG.strip_heredoc
|
|
58
|
-
EventProf results for #{
|
|
64
|
+
EventProf results for #{profiler.event}
|
|
59
65
|
|
|
60
|
-
Total time: #{
|
|
61
|
-
Total events: #{
|
|
66
|
+
Total time: #{profiler.total_time.duration}
|
|
67
|
+
Total events: #{profiler.total_count}
|
|
62
68
|
|
|
63
|
-
Top #{
|
|
69
|
+
Top #{profiler.top_count} slowest suites (by #{profiler.rank_by}):
|
|
64
70
|
|
|
65
71
|
MSG
|
|
66
72
|
|
|
@@ -75,7 +81,7 @@ module TestProf
|
|
|
75
81
|
end
|
|
76
82
|
|
|
77
83
|
if result[:examples]
|
|
78
|
-
msgs << "\nTop #{
|
|
84
|
+
msgs << "\nTop #{profiler.top_count} slowest tests (by #{profiler.rank_by}):\n\n"
|
|
79
85
|
|
|
80
86
|
result[:examples].each do |example|
|
|
81
87
|
description = example[:id].description
|
|
@@ -89,11 +95,11 @@ module TestProf
|
|
|
89
95
|
|
|
90
96
|
log :info, msgs.join
|
|
91
97
|
|
|
92
|
-
stamp! if EventProf.config.stamp?
|
|
98
|
+
stamp!(profiler) if EventProf.config.stamp?
|
|
93
99
|
end
|
|
94
100
|
|
|
95
|
-
def stamp!
|
|
96
|
-
result =
|
|
101
|
+
def stamp!(profiler)
|
|
102
|
+
result = profiler.results
|
|
97
103
|
|
|
98
104
|
stamper = RSpecStamp::Stamper.new
|
|
99
105
|
|
|
@@ -130,15 +136,18 @@ end
|
|
|
130
136
|
|
|
131
137
|
# Register EventProf listener
|
|
132
138
|
TestProf.activate('EVENT_PROF') do
|
|
139
|
+
TestProf::EventProf::CustomEvents.activate_all(ENV['EVENT_PROF'])
|
|
140
|
+
|
|
133
141
|
RSpec.configure do |config|
|
|
134
|
-
listener =
|
|
142
|
+
listener = nil
|
|
135
143
|
|
|
136
144
|
config.before(:suite) do
|
|
145
|
+
listener = TestProf::EventProf::RSpecListener.new
|
|
137
146
|
config.reporter.register_listener(
|
|
138
147
|
listener, *TestProf::EventProf::RSpecListener::NOTIFICATIONS
|
|
139
148
|
)
|
|
140
149
|
end
|
|
141
150
|
|
|
142
|
-
config.after(:suite) { listener.print }
|
|
151
|
+
config.after(:suite) { listener.print unless listener.nil? }
|
|
143
152
|
end
|
|
144
153
|
end
|
data/lib/test_prof/event_prof.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "test_prof/rspec_stamp"
|
|
4
|
+
require "test_prof/event_prof/profiler"
|
|
4
5
|
require "test_prof/event_prof/instrumentations/active_support"
|
|
5
6
|
require "test_prof/utils/sized_ordered_set"
|
|
6
7
|
|
|
@@ -72,119 +73,20 @@ module TestProf
|
|
|
72
73
|
yield config
|
|
73
74
|
end
|
|
74
75
|
|
|
75
|
-
# Returns new configured instance of
|
|
76
|
-
def build
|
|
77
|
-
|
|
78
|
-
event:
|
|
79
|
-
instrumenter: config.resolve_instrumenter
|
|
76
|
+
# Returns new configured instance of profilers group
|
|
77
|
+
def build(event = config.event)
|
|
78
|
+
ProfilersGroup.new(
|
|
79
|
+
event: event,
|
|
80
|
+
instrumenter: config.resolve_instrumenter,
|
|
81
|
+
rank_by: config.rank_by,
|
|
82
|
+
top_count: config.top_count,
|
|
83
|
+
per_example: config.per_example?
|
|
80
84
|
)
|
|
81
85
|
end
|
|
82
86
|
end
|
|
83
|
-
|
|
84
|
-
class Profiler # :nodoc:
|
|
85
|
-
include TestProf::Logging
|
|
86
|
-
|
|
87
|
-
attr_reader :event, :total_count, :total_time
|
|
88
|
-
|
|
89
|
-
def initialize(event:, instrumenter:)
|
|
90
|
-
@event = event
|
|
91
|
-
|
|
92
|
-
log :info, "EventProf enabled (#{@event})"
|
|
93
|
-
|
|
94
|
-
instrumenter.subscribe(event) { |time| track(time) }
|
|
95
|
-
|
|
96
|
-
@groups = Utils::SizedOrderedSet.new(
|
|
97
|
-
top_count, sort_by: rank_by
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
@examples = Utils::SizedOrderedSet.new(
|
|
101
|
-
top_count, sort_by: rank_by
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
@total_count = 0
|
|
105
|
-
@total_time = 0.0
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def track(time)
|
|
109
|
-
return if @current_group.nil?
|
|
110
|
-
@total_time += time
|
|
111
|
-
@total_count += 1
|
|
112
|
-
|
|
113
|
-
@time += time
|
|
114
|
-
@count += 1
|
|
115
|
-
|
|
116
|
-
return if @current_example.nil?
|
|
117
|
-
|
|
118
|
-
@example_time += time
|
|
119
|
-
@example_count += 1
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def group_started(id)
|
|
123
|
-
reset_group!
|
|
124
|
-
@current_group = id
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def group_finished(id)
|
|
128
|
-
data = { id: id, time: @time, count: @count, examples: @total_examples }
|
|
129
|
-
|
|
130
|
-
@groups << data unless data[rank_by].zero?
|
|
131
|
-
|
|
132
|
-
@current_group = nil
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def example_started(id)
|
|
136
|
-
return unless config.per_example?
|
|
137
|
-
reset_example!
|
|
138
|
-
@current_example = id
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def example_finished(id)
|
|
142
|
-
@total_examples += 1
|
|
143
|
-
return unless config.per_example?
|
|
144
|
-
|
|
145
|
-
data = { id: id, time: @example_time, count: @example_count }
|
|
146
|
-
@examples << data unless data[rank_by].zero?
|
|
147
|
-
@current_example = nil
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def results
|
|
151
|
-
{
|
|
152
|
-
groups: @groups.to_a
|
|
153
|
-
}.tap do |data|
|
|
154
|
-
next unless config.per_example?
|
|
155
|
-
|
|
156
|
-
data[:examples] = @examples.to_a
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
def rank_by
|
|
161
|
-
EventProf.config.rank_by
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def top_count
|
|
165
|
-
EventProf.config.top_count
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
private
|
|
169
|
-
|
|
170
|
-
def config
|
|
171
|
-
EventProf.config
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
def reset_group!
|
|
175
|
-
@time = 0.0
|
|
176
|
-
@count = 0
|
|
177
|
-
@total_examples = 0
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def reset_example!
|
|
181
|
-
@example_count = 0
|
|
182
|
-
@example_time = 0.0
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
87
|
end
|
|
186
88
|
end
|
|
187
89
|
|
|
90
|
+
require "test_prof/event_prof/custom_events"
|
|
188
91
|
require "test_prof/event_prof/rspec" if defined?(RSpec::Core)
|
|
189
92
|
require "test_prof/event_prof/minitest" if defined?(Minitest)
|
|
190
|
-
require "test_prof/event_prof/custom_events"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TestProf
|
|
4
|
+
module Ext
|
|
5
|
+
# Adds `ActiveRecord::Base#refind` method (through refinement)
|
|
6
|
+
module ActiveRecordRefind
|
|
7
|
+
refine ActiveRecord::Base do
|
|
8
|
+
# Returns new reloaded record.
|
|
9
|
+
#
|
|
10
|
+
# Unlike `reload` this method returns
|
|
11
|
+
# completely re-initialized instance.
|
|
12
|
+
#
|
|
13
|
+
# We need it to make sure that the state is clean.
|
|
14
|
+
def refind
|
|
15
|
+
self.class.find(send(self.class.primary_key))
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TestProf
|
|
4
|
+
module FactoryAllStub
|
|
5
|
+
# Wrap #run method to override strategy
|
|
6
|
+
module FactoryBotPatch
|
|
7
|
+
def run(_strategy = @strategy)
|
|
8
|
+
return super unless FactoryAllStub.enabled?
|
|
9
|
+
super(:build_stubbed)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|