test-prof 0.4.9 → 0.5.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|