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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +63 -20
  3. data/README.md +10 -57
  4. data/assets/tagprof.demo.html +447 -0
  5. data/assets/tagprof.template.html +447 -0
  6. data/lib/minitest/event_prof_formatter.rb +18 -16
  7. data/lib/test_prof.rb +2 -1
  8. data/lib/test_prof/any_fixture.rb +80 -4
  9. data/lib/test_prof/any_fixture/dsl.rb +18 -0
  10. data/lib/test_prof/event_prof.rb +10 -108
  11. data/lib/test_prof/event_prof/custom_events.rb +30 -0
  12. data/lib/test_prof/event_prof/custom_events/factory_create.rb +1 -1
  13. data/lib/test_prof/event_prof/custom_events/sidekiq_inline.rb +1 -1
  14. data/lib/test_prof/event_prof/custom_events/sidekiq_jobs.rb +1 -1
  15. data/lib/test_prof/event_prof/minitest.rb +6 -0
  16. data/lib/test_prof/event_prof/profiler.rb +129 -0
  17. data/lib/test_prof/event_prof/rspec.rb +20 -11
  18. data/lib/test_prof/factory_all_stub.rb +32 -0
  19. data/lib/test_prof/factory_all_stub/factory_bot_patch.rb +13 -0
  20. data/lib/test_prof/factory_doctor/rspec.rb +3 -2
  21. data/lib/test_prof/factory_prof/printers/flamegraph.rb +9 -13
  22. data/lib/test_prof/recipes/active_record_shared_connection.rb +55 -0
  23. data/lib/test_prof/recipes/logging.rb +37 -0
  24. data/lib/test_prof/recipes/rspec/any_fixture.rb +4 -1
  25. data/lib/test_prof/recipes/rspec/factory_all_stub.rb +10 -0
  26. data/lib/test_prof/rspec_dissect/rspec.rb +4 -2
  27. data/lib/test_prof/rspec_stamp/rspec.rb +3 -2
  28. data/lib/test_prof/tag_prof.rb +4 -0
  29. data/lib/test_prof/tag_prof/printers/html.rb +24 -0
  30. data/lib/test_prof/tag_prof/printers/simple.rb +82 -0
  31. data/lib/test_prof/tag_prof/result.rb +38 -0
  32. data/lib/test_prof/tag_prof/rspec.rb +43 -40
  33. data/lib/test_prof/utils/html_builder.rb +21 -0
  34. data/lib/test_prof/version.rb +1 -1
  35. metadata +20 -24
  36. data/assets/logo.svg +0 -1
  37. data/assets/testprof.png +0 -0
  38. data/guides/.rubocop.yml +0 -1
  39. data/guides/any_fixture.md +0 -114
  40. data/guides/before_all.md +0 -98
  41. data/guides/event_prof.md +0 -177
  42. data/guides/factory_default.md +0 -111
  43. data/guides/factory_doctor.md +0 -119
  44. data/guides/factory_prof.md +0 -86
  45. data/guides/let_it_be.md +0 -97
  46. data/guides/rspec_dissect.md +0 -60
  47. data/guides/rspec_stamp.md +0 -52
  48. data/guides/rubocop.md +0 -48
  49. data/guides/ruby_prof.md +0 -63
  50. data/guides/stack_prof.md +0 -47
  51. data/guides/tag_prof.md +0 -51
  52. 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
- result = @profiler.results
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 #{@profiler.event}
64
+ EventProf results for #{profiler.event}
59
65
 
60
- Total time: #{@profiler.total_time.duration}
61
- Total events: #{@profiler.total_count}
66
+ Total time: #{profiler.total_time.duration}
67
+ Total events: #{profiler.total_count}
62
68
 
63
- Top #{@profiler.top_count} slowest suites (by #{@profiler.rank_by}):
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 #{@profiler.top_count} slowest tests (by #{@profiler.rank_by}):\n\n"
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 = @profiler.results
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 = TestProf::EventProf::RSpecListener.new
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 = TestProf::FactoryDoctor::RSpecListener.new
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 "json"
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 = generate_html(report_data)
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) { TestProf::AnyFixture.reset }
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 = TestProf::RSpecDissect::Listener.new
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 = TestProf::RSpecStamp::RSpecListener.new
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
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "test_prof/tag_prof/result"
4
+ require "test_prof/tag_prof/printers/simple"
5
+ require "test_prof/tag_prof/printers/html"
6
+
3
7
  module TestProf
4
8
  module TagProf # :nodoc:
5
9
  end
@@ -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