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 +4 -4
- data/CHANGELOG.md +4 -0
- data/lib/test_prof/factory_default/fabrication_patch.rb +60 -0
- data/lib/test_prof/factory_default/factory_bot_patch.rb +59 -9
- data/lib/test_prof/factory_default.rb +5 -40
- data/lib/test_prof/recipes/minitest/before_all.rb +1 -1
- data/lib/test_prof/tps_prof/profiler.rb +55 -0
- data/lib/test_prof/tps_prof/reporter/text.rb +48 -0
- data/lib/test_prof/tps_prof/rspec.rb +63 -0
- data/lib/test_prof/tps_prof.rb +46 -0
- data/lib/test_prof/version.rb +1 -1
- data/lib/test_prof.rb +1 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 55eaa3ce2f79b5cae7992ea0e45a499c28ae1d301b2f7c9e34796d587c0e6090
|
4
|
+
data.tar.gz: 2f5a07b3a9c78304c054e025d0e785a253d310c354005ca767a7ea0674033e0f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
6
|
-
|
7
|
-
|
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
|
-
|
59
|
+
def self.patch
|
60
|
+
return unless defined?(TestProf::FactoryBot)
|
12
61
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
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
|
-
|
206
|
-
|
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
|
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?
|
data/lib/test_prof/version.rb
CHANGED
data/lib/test_prof.rb
CHANGED
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.
|
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-
|
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
|