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

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