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.
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