test-prof 0.4.9 → 0.5.0.pre1
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 +63 -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.rb +2 -1
- data/lib/test_prof/any_fixture.rb +80 -4
- data/lib/test_prof/any_fixture/dsl.rb +18 -0
- data/lib/test_prof/event_prof.rb +10 -108
- data/lib/test_prof/event_prof/custom_events.rb +30 -0
- 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/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/factory_all_stub.rb +32 -0
- data/lib/test_prof/factory_all_stub/factory_bot_patch.rb +13 -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_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.rb +4 -0
- 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/utils/html_builder.rb +21 -0
- data/lib/test_prof/version.rb +1 -1
- metadata +20 -24
- 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
@@ -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
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_prof/factory_bot"
|
4
|
+
require "test_prof/factory_all_stub/factory_bot_patch"
|
5
|
+
|
6
|
+
module TestProf
|
7
|
+
# FactoryAllStub inject into FactoryBot to make
|
8
|
+
# all strategies be `build_stubbed` strategy.
|
9
|
+
module FactoryAllStub
|
10
|
+
LOCAL_NAME = :__factory_bot_stub_all__
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def init
|
14
|
+
# Monkey-patch FactoryBot / FactoryGirl
|
15
|
+
TestProf::FactoryBot::FactoryRunner.prepend(FactoryBotPatch) if
|
16
|
+
defined?(TestProf::FactoryBot)
|
17
|
+
end
|
18
|
+
|
19
|
+
def enabled?
|
20
|
+
Thread.current[LOCAL_NAME] == true
|
21
|
+
end
|
22
|
+
|
23
|
+
def enable!
|
24
|
+
Thread.current[LOCAL_NAME] = true
|
25
|
+
end
|
26
|
+
|
27
|
+
def disable!
|
28
|
+
Thread.current[LOCAL_NAME] = false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
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
|
@@ -128,15 +128,16 @@ TestProf.activate('FDOC') do
|
|
128
128
|
TestProf::FactoryDoctor.init
|
129
129
|
|
130
130
|
RSpec.configure do |config|
|
131
|
-
listener =
|
131
|
+
listener = nil
|
132
132
|
|
133
133
|
config.before(:suite) do
|
134
|
+
listener = TestProf::FactoryDoctor::RSpecListener.new
|
134
135
|
config.reporter.register_listener(
|
135
136
|
listener, *TestProf::FactoryDoctor::RSpecListener::NOTIFICATIONS
|
136
137
|
)
|
137
138
|
end
|
138
139
|
|
139
|
-
config.after(:suite) { listener.print }
|
140
|
+
config.after(:suite) { listener.print unless listener.nil? }
|
140
141
|
end
|
141
142
|
|
142
143
|
RSpec.shared_context "factory_doctor:ignore", fd_ignore: true do
|
@@ -1,10 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "test_prof/utils/html_builder"
|
4
4
|
|
5
5
|
module TestProf::FactoryProf
|
6
6
|
module Printers
|
7
7
|
module Flamegraph # :nodoc: all
|
8
|
+
TEMPLATE = "flamegraph.template.html".freeze
|
9
|
+
OUTPUT_NAME = "factory-flame.html".freeze
|
10
|
+
|
8
11
|
class << self
|
9
12
|
include TestProf::Logging
|
10
13
|
|
@@ -17,7 +20,11 @@ module TestProf::FactoryProf
|
|
17
20
|
|
18
21
|
report_data[:roots] = convert_stacks(result)
|
19
22
|
|
20
|
-
path =
|
23
|
+
path = TestProf::Utils::HTMLBuilder.generate(
|
24
|
+
data: report_data,
|
25
|
+
template: TEMPLATE,
|
26
|
+
output: OUTPUT_NAME
|
27
|
+
)
|
21
28
|
|
22
29
|
log :info, "FactoryFlame report generated: #{path}"
|
23
30
|
end
|
@@ -55,17 +62,6 @@ module TestProf::FactoryProf
|
|
55
62
|
|
56
63
|
res
|
57
64
|
end
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
def generate_html(data)
|
62
|
-
template = File.read(TestProf.asset_path("flamegraph.template.html"))
|
63
|
-
template.sub! '/**REPORT-DATA**/', data.to_json
|
64
|
-
|
65
|
-
outpath = TestProf.artifact_path("factory-flame.html")
|
66
|
-
File.write(outpath, template)
|
67
|
-
outpath
|
68
|
-
end
|
69
65
|
end
|
70
66
|
end
|
71
67
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TestProf
|
4
|
+
# Forces ActiveRecord to use the same connection between threads
|
5
|
+
module ActiveRecordSharedConnection # :nodoc: all
|
6
|
+
class << self
|
7
|
+
attr_reader :connection
|
8
|
+
|
9
|
+
def enable!
|
10
|
+
self.connection = ActiveRecord::Base.connection
|
11
|
+
end
|
12
|
+
|
13
|
+
def disable!
|
14
|
+
self.connection = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def connection=(conn)
|
20
|
+
@connection = conn
|
21
|
+
connection.singleton_class.prepend Connection
|
22
|
+
connection
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module Connection
|
27
|
+
def shared_lock
|
28
|
+
@shared_lock ||= Mutex.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def exec_cache(*)
|
32
|
+
shared_lock.synchronize { super }
|
33
|
+
end
|
34
|
+
|
35
|
+
def exec_no_cache(*)
|
36
|
+
shared_lock.synchronize { super }
|
37
|
+
end
|
38
|
+
|
39
|
+
def execute(*)
|
40
|
+
shared_lock.synchronize { super }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module Ext
|
45
|
+
def connection
|
46
|
+
ActiveRecordSharedConnection.connection || super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
ActiveSupport.on_load(:active_record) do
|
53
|
+
TestProf::ActiveRecordSharedConnection.enable!
|
54
|
+
ActiveRecord::Base.singleton_class.prepend TestProf::ActiveRecordSharedConnection::Ext
|
55
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
if defined?(RSpec)
|
2
|
+
RSpec.shared_context "logging:verbose", log: true do
|
3
|
+
around(:each) do |ex|
|
4
|
+
*loggers = ActiveSupport::LogSubscriber.logger,
|
5
|
+
Rails.logger,
|
6
|
+
ActiveRecord::Base.logger
|
7
|
+
ActiveSupport::LogSubscriber.logger =
|
8
|
+
Rails.logger =
|
9
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
10
|
+
ex.run
|
11
|
+
ActiveSupport::LogSubscriber.logger,
|
12
|
+
Rails.logger,
|
13
|
+
ActiveRecord::Base.logger = *loggers
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
RSpec.shared_context "logging:active_record", log: :ar do
|
18
|
+
around(:each) do |ex|
|
19
|
+
logger = ActiveRecord::Base.logger
|
20
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
21
|
+
ex.run
|
22
|
+
ActiveRecord::Base.logger = logger
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
TestProf.activate("LOG", "all") do
|
28
|
+
TestProf.log :info, "Rails verbose logging enabled"
|
29
|
+
ActiveSupport::LogSubscriber.logger =
|
30
|
+
Rails.logger =
|
31
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
32
|
+
end
|
33
|
+
|
34
|
+
TestProf.activate("LOG", "ar") do
|
35
|
+
TestProf.log :info, "Active Record verbose logging enabled"
|
36
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
37
|
+
end
|
@@ -17,5 +17,8 @@ RSpec.shared_context "any_fixture:clean", with_clean_fixture: true do
|
|
17
17
|
end
|
18
18
|
|
19
19
|
RSpec.configure do |config|
|
20
|
-
config.after(:suite)
|
20
|
+
config.after(:suite) do
|
21
|
+
TestProf::AnyFixture.report_stats if TestProf::AnyFixture.reporting_enabled?
|
22
|
+
TestProf::AnyFixture.reset
|
23
|
+
end
|
21
24
|
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_prof/factory_all_stub"
|
4
|
+
|
5
|
+
TestProf::FactoryAllStub.init
|
6
|
+
|
7
|
+
RSpec.shared_context "factory:stub", factory: :stub do
|
8
|
+
prepend_before(:all) { TestProf::FactoryAllStub.enable! }
|
9
|
+
append_after(:all) { TestProf::FactoryAllStub.disable! }
|
10
|
+
end
|
@@ -157,14 +157,16 @@ end
|
|
157
157
|
# Register RSpecDissect listener
|
158
158
|
TestProf.activate('RD_PROF') do
|
159
159
|
RSpec.configure do |config|
|
160
|
-
listener =
|
160
|
+
listener = nil
|
161
161
|
|
162
162
|
config.before(:suite) do
|
163
|
+
listener = TestProf::RSpecDissect::Listener.new
|
164
|
+
|
163
165
|
config.reporter.register_listener(
|
164
166
|
listener, *TestProf::RSpecDissect::Listener::NOTIFICATIONS
|
165
167
|
)
|
166
168
|
end
|
167
169
|
|
168
|
-
config.after(:suite) { listener.print }
|
170
|
+
config.after(:suite) { listener.print unless listener.nil? }
|
169
171
|
end
|
170
172
|
end
|
@@ -58,14 +58,15 @@ end
|
|
58
58
|
# Register EventProf listener
|
59
59
|
TestProf.activate('RSTAMP') do
|
60
60
|
RSpec.configure do |config|
|
61
|
-
listener =
|
61
|
+
listener = nil
|
62
62
|
|
63
63
|
config.before(:suite) do
|
64
|
+
listener = TestProf::RSpecStamp::RSpecListener.new
|
64
65
|
config.reporter.register_listener(
|
65
66
|
listener, *TestProf::RSpecStamp::RSpecListener::NOTIFICATIONS
|
66
67
|
)
|
67
68
|
end
|
68
69
|
|
69
|
-
config.after(:suite) { listener.stamp! }
|
70
|
+
config.after(:suite) { listener.stamp! unless listener.nil? }
|
70
71
|
end
|
71
72
|
end
|
data/lib/test_prof/tag_prof.rb
CHANGED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TestProf::TagProf
|
4
|
+
module Printers
|
5
|
+
module HTML # :nodoc: all
|
6
|
+
TEMPLATE = "tagprof.template.html".freeze
|
7
|
+
OUTPUT_NAME = "tag-prof.html".freeze
|
8
|
+
|
9
|
+
class << self
|
10
|
+
include TestProf::Logging
|
11
|
+
|
12
|
+
def dump(result)
|
13
|
+
path = TestProf::Utils::HTMLBuilder.generate(
|
14
|
+
data: result,
|
15
|
+
template: TEMPLATE,
|
16
|
+
output: OUTPUT_NAME
|
17
|
+
)
|
18
|
+
|
19
|
+
log :info, "TagProf report generated: #{path}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_prof/ext/float_duration"
|
4
|
+
require "test_prof/ext/string_strip_heredoc"
|
5
|
+
|
6
|
+
module TestProf::TagProf
|
7
|
+
module Printers
|
8
|
+
module Simple # :nodoc: all
|
9
|
+
class << self
|
10
|
+
include TestProf::Logging
|
11
|
+
using TestProf::StringStripHeredoc
|
12
|
+
using TestProf::FloatDuration
|
13
|
+
|
14
|
+
def dump(result)
|
15
|
+
msgs = []
|
16
|
+
|
17
|
+
msgs <<
|
18
|
+
<<-MSG.strip_heredoc
|
19
|
+
TagProf report for #{result.tag}
|
20
|
+
MSG
|
21
|
+
|
22
|
+
header = []
|
23
|
+
|
24
|
+
header << format(
|
25
|
+
'%15s %12s ',
|
26
|
+
result.tag, 'time'
|
27
|
+
)
|
28
|
+
|
29
|
+
events_format = nil
|
30
|
+
|
31
|
+
unless result.events.empty?
|
32
|
+
events_format = result.events.map { |event| "%#{event.size + 2}s " }.join
|
33
|
+
|
34
|
+
header << format(
|
35
|
+
events_format,
|
36
|
+
*result.events
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
header << format(
|
41
|
+
'%6s %6s %6s %12s',
|
42
|
+
'total', '%total', '%time', 'avg'
|
43
|
+
)
|
44
|
+
|
45
|
+
msgs << header.join
|
46
|
+
|
47
|
+
msgs << ""
|
48
|
+
|
49
|
+
total = result.data.values.inject(0) { |acc, v| acc + v[:count] }
|
50
|
+
total_time = result.data.values.inject(0) { |acc, v| acc + v[:time] }
|
51
|
+
|
52
|
+
result.data.values.sort_by { |v| -v[:time] }.each do |tag|
|
53
|
+
line = []
|
54
|
+
line << format(
|
55
|
+
"%15s %12s ",
|
56
|
+
tag[:value], tag[:time].duration
|
57
|
+
)
|
58
|
+
|
59
|
+
unless result.events.empty?
|
60
|
+
line << format(
|
61
|
+
events_format,
|
62
|
+
*result.events.map { |event| tag[event].duration }
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
line << format(
|
67
|
+
"%6d %6.2f %6.2f %12s",
|
68
|
+
tag[:count],
|
69
|
+
100 * tag[:count].to_f / total,
|
70
|
+
100 * tag[:time] / total_time,
|
71
|
+
(tag[:time] / tag[:count]).duration
|
72
|
+
)
|
73
|
+
|
74
|
+
msgs << line.join
|
75
|
+
end
|
76
|
+
|
77
|
+
log :info, msgs.join("\n")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|