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