test-prof 1.4.0.rc.1 → 1.4.0.rc.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2bb23a2e0630d64b02f4f755f801189a19a5d26179dcb5d9d9420b73a0b78319
4
- data.tar.gz: 56fe8596860ac93bb46adb6d6c9ff027de6eddf43eb3fbd28d69f2fddd76c1eb
3
+ metadata.gz: 55eaa3ce2f79b5cae7992ea0e45a499c28ae1d301b2f7c9e34796d587c0e6090
4
+ data.tar.gz: 2f5a07b3a9c78304c054e025d0e785a253d310c354005ca767a7ea0674033e0f
5
5
  SHA512:
6
- metadata.gz: 03f0304518182b46058f20f2a818067f264901278da60f0449cbc9ca37e3283bd093e737f7dff4312b3e4e99e3f24106d620ba90c6561b6218b3f99077785d53
7
- data.tar.gz: 37608d5a827f5caff39f1fdd0ad066fed97d9cf42c1da532dac6b0019f3372f89b0779daf4ad3e570435c3aea624789d61723c6155c147e9d140d27d9db5f071
6
+ metadata.gz: f26d5cdd70cd05d93fc0cc596302a72089c1718fd5b58084b5a219333ac6b0ba034d09c7a0e57840fa790f4198c7b8ba24f8dfdaa0d0b85a5f7404246b922b07
7
+ data.tar.gz: 22e6d9304af0c182f4bd1bf7d4b34a9bc8d5435b505100bebe9519cdac2f3d29ba47db3fe7de48179cc88c191cac3da531de4d3b6f506c2275dc52f45b832444
data/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## master (unreleased)
4
4
 
5
+ - Add new TPS (tests per second) profiler. ([@palkan][])
6
+
7
+ - FactoryDefault: add Fabrication support. ([@palkan][])
8
+
5
9
  - Drop support for **Ruby <2.7** and **Rails <6**.
6
10
 
7
11
  - FactoryDefault: Add `#get_factory_default`. [[@john-h-k][]]
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TestProf
4
+ module FactoryDefault # :nodoc: all
5
+ module FabricationPatch
6
+ module DefaultExt
7
+ def create_default(name, overrides = {}, &block)
8
+ obj = ::Fabricate.create(name, overrides, &block)
9
+ set_fabricate_default(name, obj)
10
+ end
11
+
12
+ def set_fabricate_default(name, obj, **opts)
13
+ FactoryDefault.register(
14
+ name, obj,
15
+ preserve_attributes: FactoryDefault.config.preserve_attributes,
16
+ preserve_traits: FactoryDefault.config.preserve_traits,
17
+ **opts
18
+ )
19
+ end
20
+
21
+ def get_fabricate_default(name, **overrides)
22
+ FactoryDefault.get(name, nil, overrides, skip_stats: true)
23
+ end
24
+
25
+ def skip_fabricate_default(&block)
26
+ FactoryDefault.disable!(&block)
27
+ end
28
+
29
+ def create(name, overrides = {}, &block)
30
+ self.fabrication_depth += 1
31
+ # We do not support defaults for objects created with attribute blocks
32
+ return super if block
33
+
34
+ return super if fabrication_depth < 2
35
+
36
+ FactoryDefault.get(name, nil, overrides, **{}) ||
37
+ FactoryDefault.profiler.instrument(name, nil, overrides) { super }
38
+ ensure
39
+ self.fabrication_depth -= 1
40
+ end
41
+
42
+ private
43
+
44
+ def fabrication_depth
45
+ Thread.current[:_fab_depth_] ||= 0
46
+ end
47
+
48
+ def fabrication_depth=(value)
49
+ Thread.current[:_fab_depth_] = value
50
+ end
51
+ end
52
+
53
+ def self.patch
54
+ TestProf.require "fabrication" do
55
+ ::Fabricate.singleton_class.prepend(DefaultExt)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -1,19 +1,69 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "test_prof/factory_bot"
4
+
3
5
  module TestProf
4
6
  module FactoryDefault # :nodoc: all
5
- module RunnerExt
6
- refine TestProf::FactoryBot::FactoryRunner do
7
- attr_reader :name, :traits, :overrides
7
+ module FactoryBotPatch
8
+ if defined?(TestProf::FactoryBot::FactoryRunner)
9
+ module RunnerExt
10
+ refine TestProf::FactoryBot::FactoryRunner do
11
+ attr_reader :name, :traits, :overrides
12
+ end
13
+ end
14
+
15
+ using RunnerExt
16
+ end
17
+
18
+ module StrategyExt
19
+ def association(runner)
20
+ FactoryDefault.get(runner.name, runner.traits, runner.overrides, **{}) ||
21
+ FactoryDefault.profiler.instrument(runner.name, runner.traits, runner.overrides) { super }
22
+ end
23
+ end
24
+
25
+ module SyntaxExt
26
+ def create_default(name, *args, &block)
27
+ options = args.extract_options!
28
+ default_options = {}
29
+ default_options[:preserve_traits] = options.delete(:preserve_traits) if options.key?(:preserve_traits)
30
+ default_options[:preserve_attributes] = options.delete(:preserve_attributes) if options.key?(:preserve_attributes)
31
+
32
+ obj = TestProf::FactoryBot.create(name, *args, options, &block)
33
+
34
+ # Factory with traits
35
+ name = [name, *args] if args.any?
36
+
37
+ set_factory_default(name, obj, **default_options)
38
+ end
39
+
40
+ def set_factory_default(*name, obj, preserve_traits: FactoryDefault.config.preserve_traits, preserve_attributes: FactoryDefault.config.preserve_attributes, **other)
41
+ name = name.first if name.size == 1
42
+ FactoryDefault.register(
43
+ name, obj,
44
+ preserve_traits: preserve_traits,
45
+ preserve_attributes: preserve_attributes,
46
+ **other
47
+ )
48
+ end
49
+
50
+ def get_factory_default(name, *traits, **overrides)
51
+ FactoryDefault.get(name, traits, overrides, skip_stats: true)
52
+ end
53
+
54
+ def skip_factory_default(&block)
55
+ FactoryDefault.disable!(&block)
56
+ end
8
57
  end
9
- end
10
58
 
11
- using RunnerExt
59
+ def self.patch
60
+ return unless defined?(TestProf::FactoryBot)
12
61
 
13
- module StrategyExt
14
- def association(runner)
15
- FactoryDefault.get(runner.name, runner.traits, runner.overrides, **{}) ||
16
- FactoryDefault.profiler.instrument(runner.name, runner.traits, runner.overrides) { super }
62
+ TestProf::FactoryBot::Syntax::Methods.include SyntaxExt
63
+ TestProf::FactoryBot.extend SyntaxExt
64
+ TestProf::FactoryBot::Strategy::Create.prepend StrategyExt
65
+ TestProf::FactoryBot::Strategy::Build.prepend StrategyExt
66
+ TestProf::FactoryBot::Strategy::Stub.prepend StrategyExt
17
67
  end
18
68
  end
19
69
  end
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "test_prof/core"
4
- require "test_prof/factory_bot"
4
+
5
5
  require "test_prof/factory_default/factory_bot_patch"
6
+ require "test_prof/factory_default/fabrication_patch"
7
+
6
8
  require "test_prof/ext/float_duration"
7
9
  require "test_prof/ext/active_record_refind" if defined?(::ActiveRecord::Base)
8
10
 
@@ -144,40 +146,6 @@ module TestProf
144
146
  end
145
147
  end
146
148
 
147
- module DefaultSyntax # :nodoc:
148
- def create_default(name, *args, &block)
149
- options = args.extract_options!
150
- default_options = {}
151
- default_options[:preserve_traits] = options.delete(:preserve_traits) if options.key?(:preserve_traits)
152
- default_options[:preserve_attributes] = options.delete(:preserve_attributes) if options.key?(:preserve_attributes)
153
-
154
- obj = TestProf::FactoryBot.create(name, *args, options, &block)
155
-
156
- # Factory with traits
157
- name = [name, *args] if args.any?
158
-
159
- set_factory_default(name, obj, **default_options)
160
- end
161
-
162
- def set_factory_default(*name, obj, preserve_traits: FactoryDefault.config.preserve_traits, preserve_attributes: FactoryDefault.config.preserve_attributes, **other)
163
- name = name.first if name.size == 1
164
- FactoryDefault.register(
165
- name, obj,
166
- preserve_traits: preserve_traits,
167
- preserve_attributes: preserve_attributes,
168
- **other
169
- )
170
- end
171
-
172
- def get_factory_default(name, *traits, **overrides)
173
- FactoryDefault.get(name, traits, overrides, skip_stats: true)
174
- end
175
-
176
- def skip_factory_default(&block)
177
- FactoryDefault.disable!(&block)
178
- end
179
- end
180
-
181
149
  class Configuration
182
150
  attr_accessor :preserve_traits, :preserve_attributes,
183
151
  :report_summary, :report_stats,
@@ -202,11 +170,8 @@ module TestProf
202
170
  attr_reader :stats, :profiler
203
171
 
204
172
  def init
205
- TestProf::FactoryBot::Syntax::Methods.include DefaultSyntax
206
- TestProf::FactoryBot.extend DefaultSyntax
207
- TestProf::FactoryBot::Strategy::Create.prepend StrategyExt
208
- TestProf::FactoryBot::Strategy::Build.prepend StrategyExt
209
- TestProf::FactoryBot::Strategy::Stub.prepend StrategyExt
173
+ FactoryBotPatch.patch
174
+ FabricationPatch.patch
210
175
 
211
176
  @profiler = config.profiling_enabled? ? Profiler.new : NoopProfiler.new
212
177
  @enabled = ENV["FACTORY_DEFAULT_DISABLED"] != "1"
@@ -114,7 +114,7 @@ module TestProf
114
114
  base.singleton_class.prepend(Module.new do
115
115
  def parallelize(workers: :number_of_processors, with: :processes)
116
116
  # super.parallelize returns nil when no parallelization is set up
117
- if super(workers: workers, with: with).nil?
117
+ if super.nil?
118
118
  return
119
119
  end
120
120
 
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_prof/utils/sized_ordered_set"
4
+
5
+ module TestProf
6
+ module TPSProf
7
+ class Profiler
8
+ attr_reader :top_count, :groups, :total_count, :total_time, :threshold
9
+
10
+ def initialize(top_count, threshold: 10)
11
+ @threshold = threshold
12
+ @top_count = top_count
13
+ @total_count = 0
14
+ @total_time = 0.0
15
+ @groups = Utils::SizedOrderedSet.new(top_count, sort_by: :tps)
16
+ end
17
+
18
+ def group_started(id)
19
+ @current_group = id
20
+ @examples_count = 0
21
+ @examples_time = 0.0
22
+ @group_started_at = TestProf.now
23
+ end
24
+
25
+ def group_finished(id)
26
+ return unless @examples_count >= threshold
27
+
28
+ # Context-time
29
+ group_time = (TestProf.now - @group_started_at) - @examples_time
30
+ run_time = @examples_time + group_time
31
+
32
+ groups << {
33
+ id: id,
34
+ run_time: run_time,
35
+ group_time: group_time,
36
+ count: @examples_count,
37
+ tps: -(@examples_count / run_time).round(2)
38
+ }
39
+ end
40
+
41
+ def example_started(id)
42
+ @example_started_at = TestProf.now
43
+ end
44
+
45
+ def example_finished(id)
46
+ @examples_count += 1
47
+ @total_count += 1
48
+
49
+ time = (TestProf.now - @example_started_at)
50
+ @examples_time += time
51
+ @total_time += time
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_prof/ext/float_duration"
4
+ require "test_prof/ext/string_truncate"
5
+
6
+ module TestProf
7
+ module TPSProf
8
+ module Reporter
9
+ class Text
10
+ include Logging
11
+ using FloatDuration
12
+ using StringTruncate
13
+
14
+ def print(profiler)
15
+ groups = profiler.groups
16
+
17
+ total_tps = (profiler.total_count / profiler.total_time).round(2)
18
+
19
+ msgs = []
20
+
21
+ msgs <<
22
+ <<~MSG
23
+ Total TPS (tests per second): #{total_tps}
24
+
25
+ Top #{profiler.top_count} slowest suites by TPS (tests per second) (min examples per group: #{profiler.threshold}):
26
+
27
+ MSG
28
+
29
+ groups.each do |group|
30
+ description = group[:id].top_level_description
31
+ location = group[:id].metadata[:location]
32
+ time = group[:run_time]
33
+ group_time = group[:group_time]
34
+ count = group[:count]
35
+ tps = -group[:tps]
36
+
37
+ msgs <<
38
+ <<~GROUP
39
+ #{description.truncate} (#{location}) – #{tps} TPS (#{time.duration} / #{count}), group time: #{group_time.duration}
40
+ GROUP
41
+ end
42
+
43
+ log :info, msgs.join
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TestProf
4
+ module TPSProf
5
+ class RSpecListener # :nodoc:
6
+ include Logging
7
+
8
+ NOTIFICATIONS = %i[
9
+ example_group_started
10
+ example_group_finished
11
+ example_started
12
+ example_finished
13
+ ].freeze
14
+
15
+ attr_reader :reporter, :profiler
16
+
17
+ def initialize
18
+ @profiler = Profiler.new(TPSProf.config.top_count, threshold: TPSProf.config.threshold)
19
+ @reporter = TPSProf.config.reporter
20
+
21
+ log :info, "TPSProf enabled (top-#{TPSProf.config.top_count})"
22
+ end
23
+
24
+ def example_group_started(notification)
25
+ return unless notification.group.top_level?
26
+ profiler.group_started notification.group
27
+ end
28
+
29
+ def example_group_finished(notification)
30
+ return unless notification.group.top_level?
31
+ profiler.group_finished notification.group
32
+ end
33
+
34
+ def example_started(notification)
35
+ profiler.example_started notification.example
36
+ end
37
+
38
+ def example_finished(notification)
39
+ profiler.example_finished notification.example
40
+ end
41
+
42
+ def print
43
+ reporter.print(profiler)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ # Register TPSProf listener
50
+ TestProf.activate("TPS_PROF") do
51
+ RSpec.configure do |config|
52
+ listener = nil
53
+
54
+ config.before(:suite) do
55
+ listener = TestProf::TPSProf::RSpecListener.new
56
+ config.reporter.register_listener(
57
+ listener, *TestProf::TPSProf::RSpecListener::NOTIFICATIONS
58
+ )
59
+ end
60
+
61
+ config.after(:suite) { listener&.print }
62
+ end
63
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_prof/tps_prof/profiler"
4
+ require "test_prof/tps_prof/reporter/text"
5
+
6
+ module TestProf
7
+ # TPSProf shows top-N example group based on their tests-per-second value.
8
+ #
9
+ # Example:
10
+ #
11
+ # TPS_PROF=10 rspec ...
12
+ #
13
+ module TPSProf
14
+ class Configuration
15
+ attr_accessor :top_count, :threshold, :reporter
16
+
17
+ def initialize
18
+ @top_count = ENV["TPS_PROF"].to_i
19
+ @top_count = 10 if @top_count == 1
20
+ @threshold = ENV.fetch("TPS_PROF_MIN", 10).to_i
21
+ @reporter = resolve_reporter(ENV["TPS_PROF_FORMAT"])
22
+ end
23
+
24
+ private
25
+
26
+ def resolve_reporter(format)
27
+ # TODO: support other formats
28
+ TPSProf::Reporter::Text.new
29
+ end
30
+ end
31
+
32
+ class << self
33
+ def config
34
+ @config ||= Configuration.new
35
+ end
36
+
37
+ def configure
38
+ yield config
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ require "test_prof/tps_prof/rspec" if TestProf.rspec?
45
+ # TODO: Minitest support
46
+ # require "test_prof/tps_prof/minitest" if TestProf.minitest?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TestProf
4
- VERSION = "1.4.0.rc.1"
4
+ VERSION = "1.4.0.rc.3"
5
5
  end
data/lib/test_prof.rb CHANGED
@@ -12,5 +12,6 @@ require "test_prof/factory_prof"
12
12
  require "test_prof/memory_prof"
13
13
  require "test_prof/rspec_stamp"
14
14
  require "test_prof/tag_prof"
15
+ require "test_prof/tps_prof"
15
16
  require "test_prof/rspec_dissect" if TestProf.rspec?
16
17
  require "test_prof/factory_all_stub"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: test-prof
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0.rc.1
4
+ version: 1.4.0.rc.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-13 00:00:00.000000000 Z
11
+ date: 2024-07-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -164,6 +164,7 @@ files:
164
164
  - lib/test_prof/factory_all_stub/factory_bot_patch.rb
165
165
  - lib/test_prof/factory_bot.rb
166
166
  - lib/test_prof/factory_default.rb
167
+ - lib/test_prof/factory_default/fabrication_patch.rb
167
168
  - lib/test_prof/factory_default/factory_bot_patch.rb
168
169
  - lib/test_prof/factory_doctor.rb
169
170
  - lib/test_prof/factory_doctor/fabrication_patch.rb
@@ -218,6 +219,10 @@ files:
218
219
  - lib/test_prof/tag_prof/printers/simple.rb
219
220
  - lib/test_prof/tag_prof/result.rb
220
221
  - lib/test_prof/tag_prof/rspec.rb
222
+ - lib/test_prof/tps_prof.rb
223
+ - lib/test_prof/tps_prof/profiler.rb
224
+ - lib/test_prof/tps_prof/reporter/text.rb
225
+ - lib/test_prof/tps_prof/rspec.rb
221
226
  - lib/test_prof/utils.rb
222
227
  - lib/test_prof/utils/html_builder.rb
223
228
  - lib/test_prof/utils/rspec.rb