test-prof 1.4.0 → 1.5.0
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 +44 -0
- data/lib/test_prof/any_fixture/dsl.rb +16 -4
- data/lib/test_prof/any_fixture/dump.rb +1 -1
- data/lib/test_prof/before_all/adapters/active_record.rb +31 -0
- data/lib/test_prof/before_all.rb +30 -7
- data/lib/test_prof/cops/plugin.rb +31 -0
- data/lib/test_prof/cops/rspec/aggregate_examples/its.rb +2 -2
- data/lib/test_prof/cops/rspec/aggregate_examples/line_range_helpers.rb +1 -1
- data/lib/test_prof/cops/rspec/aggregate_examples/matchers_with_side_effects.rb +1 -1
- data/lib/test_prof/cops/rspec/aggregate_examples/metadata_helpers.rb +1 -1
- data/lib/test_prof/cops/rspec/aggregate_examples/node_matchers.rb +1 -1
- data/lib/test_prof/cops/rspec/aggregate_examples.rb +12 -18
- data/lib/test_prof/core.rb +4 -0
- data/lib/test_prof/event_prof/custom_events/factory_create.rb +1 -1
- data/lib/test_prof/event_prof/rspec.rb +1 -0
- data/lib/test_prof/ext/active_record_refind.rb +1 -1
- data/lib/test_prof/ext/string_truncate.rb +1 -1
- data/lib/test_prof/factory_all_stub.rb +1 -1
- data/lib/test_prof/factory_bot.rb +3 -7
- data/lib/test_prof/factory_doctor/rspec.rb +1 -0
- data/lib/test_prof/factory_doctor.rb +3 -3
- data/lib/test_prof/factory_prof/factory_builders/factory_bot.rb +1 -1
- data/lib/test_prof/factory_prof/printers/simple.rb +35 -5
- data/lib/test_prof/factory_prof.rb +5 -3
- data/lib/test_prof/memory_prof/printer.rb +1 -0
- data/lib/test_prof/recipes/logging.rb +20 -0
- data/lib/test_prof/recipes/minitest/before_all.rb +1 -1
- data/lib/test_prof/rspec_dissect/rspec.rb +1 -0
- data/lib/test_prof/rspec_stamp/parser.rb +1 -1
- data/lib/test_prof/rspec_stamp.rb +7 -5
- data/lib/test_prof/rubocop.rb +1 -1
- data/lib/test_prof/stack_prof.rb +2 -2
- data/lib/test_prof/tag_prof/printers/simple.rb +2 -1
- data/lib/test_prof/tps_prof/reporter/text.rb +1 -0
- data/lib/test_prof/version.rb +1 -1
- metadata +7 -6
- data/lib/test_prof/cops/inject.rb +0 -25
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 119118a5840991b0d1bde28e49aca9f3f72f77f89ba57704c53ce2e8a023ad06
|
|
4
|
+
data.tar.gz: 6f124770cfeba6346f744dbf66a00b118c6c207711af9f18977abea153de4f7a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6827f8b914ce78da9cabff0dd04ef643c3571e22864a1c6c215028fd4a2e9f79dbbab0d69e7ac9184753460aee96a2202a9201f7bdf2177074b42de47e893ac4
|
|
7
|
+
data.tar.gz: 4beacf5a982c0fe64c1c363660f2985be9dd6470ac847eb10e5ef4cd3ddf3360681e8c09fb511d5a99418629b455755e93824768c21520d0c754a908a7e7080c
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
## master (unreleased)
|
|
4
4
|
|
|
5
|
+
## 1.5.0 (2025-12-04)
|
|
6
|
+
|
|
7
|
+
- Logging: support Rails 8.2 structured events based logging. ([@palkan][])
|
|
8
|
+
|
|
9
|
+
- Allow using AnyFixture DSL through module inclusion, not refinements. ([@palkan][])
|
|
10
|
+
|
|
11
|
+
In Rails 7.2+, refined `#fixture` no longer works since there is a same-called method. So, from now on we recommend including the DSL module, instead of _using_ it.
|
|
12
|
+
|
|
13
|
+
## 1.4.5. (2025-05-09) 🎇
|
|
14
|
+
|
|
15
|
+
- FactoryProf: Add truncate_names configuration parameter. ([@skaestle][])
|
|
16
|
+
|
|
17
|
+
- Update Rubocop setup to support new plugins system. ([@julianpasquale])
|
|
18
|
+
|
|
19
|
+
Now you can truncate long factory-names when using the simple output mode.
|
|
20
|
+
|
|
21
|
+
Set `FPROF_TRUNCATE_NAMES=1` env var or set it through `FactoryProf` configuration:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
TestProf::FactoryProf.configure do |config|
|
|
25
|
+
config.truncate_names = true
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 1.4.4 (2025-01-03)
|
|
30
|
+
|
|
31
|
+
- Fix _stamping_ specs with single quotes with RSpec Stamp. ([@elasticspoon][])
|
|
32
|
+
|
|
33
|
+
## 1.4.3 (2024-12-18)
|
|
34
|
+
|
|
35
|
+
- Fix handling new (lazy) connection pools in `before_all`. ([@palkan][])
|
|
36
|
+
|
|
37
|
+
- Updates Rubocop::Cop code to comply with most modern API. ([@aseroff][])
|
|
38
|
+
|
|
39
|
+
## 1.4.2 (2024-09-03) 🗓️
|
|
40
|
+
|
|
41
|
+
- Ignore default scopes in `ActiveRecord::Base#refind`. ([@palkan][])
|
|
42
|
+
|
|
43
|
+
## 1.4.1 (2024-08-23)
|
|
44
|
+
|
|
45
|
+
- Skips loading the ActiveRecord adapter for runs where RSpec --dry-run mode is enabled. ([@devinburnette][])
|
|
46
|
+
|
|
5
47
|
## 1.4.0 (2024-08-12)
|
|
6
48
|
|
|
7
49
|
- AnyFixture: Disable fixtures cache within _clean fixture_ context. Automatically _refind_ records when using `#fixture`. ([@palkan][])
|
|
@@ -434,3 +476,5 @@ See [changelog](https://github.com/test-prof/test-prof/blob/v0.8.0/CHANGELOG.md)
|
|
|
434
476
|
[@lioneldebauge]: https://github.com/lioneldebauge
|
|
435
477
|
[@lHydra]: https://github.com/lHydra
|
|
436
478
|
[@john-h-k]: https://github.com/john-h-k
|
|
479
|
+
[@devinburnette]: https://github.com/devinburnette
|
|
480
|
+
[@elasticspoon]: https://github.com/elasticspoon
|
|
@@ -9,10 +9,7 @@ module TestProf
|
|
|
9
9
|
module AnyFixture
|
|
10
10
|
# Adds "global" `fixture`, `before_fixtures_reset` and `after_fixtures_reset` methods (through refinement)
|
|
11
11
|
module DSL
|
|
12
|
-
|
|
13
|
-
# - https://bugs.ruby-lang.org/issues/13446
|
|
14
|
-
# - Rails added `Kernel.prepend` in 6.1: https://github.com/rails/rails/commit/3124007bd674dcdc9c3b5c6b2964dfb7a1a0733c
|
|
15
|
-
refine ::Object do
|
|
12
|
+
module Methods
|
|
16
13
|
def fixture(id, &block)
|
|
17
14
|
id = :"#{id}"
|
|
18
15
|
record = ::TestProf::AnyFixture.cached(id)
|
|
@@ -38,6 +35,21 @@ module TestProf
|
|
|
38
35
|
::TestProf::AnyFixture.after_fixtures_reset(&block)
|
|
39
36
|
end
|
|
40
37
|
end
|
|
38
|
+
|
|
39
|
+
def self.included(base)
|
|
40
|
+
base.include Methods
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Refine object, 'cause refining modules (Kernel) is vulnerable to prepend:
|
|
44
|
+
# - https://bugs.ruby-lang.org/issues/13446
|
|
45
|
+
# - Rails added `Kernel.prepend` in 6.1: https://github.com/rails/rails/commit/3124007bd674dcdc9c3b5c6b2964dfb7a1a0733c
|
|
46
|
+
refine ::Object do
|
|
47
|
+
if RUBY_VERSION >= "3.1.0"
|
|
48
|
+
import_methods Methods
|
|
49
|
+
else
|
|
50
|
+
include Methods
|
|
51
|
+
end
|
|
52
|
+
end
|
|
41
53
|
end
|
|
42
54
|
end
|
|
43
55
|
end
|
|
@@ -10,6 +10,7 @@ module TestProf
|
|
|
10
10
|
class << self
|
|
11
11
|
if ::ActiveRecord::Base.connection.pool.respond_to?(:pin_connection!)
|
|
12
12
|
def begin_transaction
|
|
13
|
+
subscribe!
|
|
13
14
|
::ActiveRecord::Base.connection_handler.connection_pool_list(:writing).each do |pool|
|
|
14
15
|
pool.pin_connection!(true)
|
|
15
16
|
end
|
|
@@ -19,6 +20,36 @@ module TestProf
|
|
|
19
20
|
::ActiveRecord::Base.connection_handler.connection_pool_list(:writing).each do |pool|
|
|
20
21
|
pool.unpin_connection!
|
|
21
22
|
end
|
|
23
|
+
unsubscribe!
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def subscribe!
|
|
27
|
+
Thread.current[:before_all_subscription_count] ||= 0
|
|
28
|
+
Thread.current[:before_all_subscription_count] += 1
|
|
29
|
+
|
|
30
|
+
return unless Thread.current[:before_all_subscription_count] == 1
|
|
31
|
+
|
|
32
|
+
Thread.current[:before_all_connection_subscriber] = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
|
|
33
|
+
connection_name = payload[:connection_name] if payload.key?(:connection_name)
|
|
34
|
+
shard = payload[:shard] if payload.key?(:shard)
|
|
35
|
+
next unless connection_name
|
|
36
|
+
|
|
37
|
+
pool = ::ActiveRecord::Base.connection_handler.retrieve_connection_pool(connection_name, shard: shard)
|
|
38
|
+
next unless pool && pool.role == :writing
|
|
39
|
+
|
|
40
|
+
pool.pin_connection!(true)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def unsubscribe!
|
|
45
|
+
return unless Thread.current[:before_all_subscription_count]
|
|
46
|
+
|
|
47
|
+
Thread.current[:before_all_subscription_count] -= 1
|
|
48
|
+
|
|
49
|
+
return unless Thread.current[:before_all_subscription_count] == 0 && Thread.current[:before_all_connection_subscriber]
|
|
50
|
+
|
|
51
|
+
ActiveSupport::Notifications.unsubscribe(Thread.current[:before_all_connection_subscriber])
|
|
52
|
+
Thread.current[:before_all_connection_subscriber] = nil
|
|
22
53
|
end
|
|
23
54
|
else
|
|
24
55
|
def all_connections
|
data/lib/test_prof/before_all.rb
CHANGED
|
@@ -14,8 +14,26 @@ module TestProf
|
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
# Used in dry-run mode
|
|
18
|
+
class NoopAdapter
|
|
19
|
+
class << self
|
|
20
|
+
def begin_transaction(...)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def rollback_transaction(...)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def setup_fixtures(...)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
17
31
|
class << self
|
|
18
|
-
|
|
32
|
+
attr_writer :adapter
|
|
33
|
+
|
|
34
|
+
def adapter
|
|
35
|
+
@adapter ||= default_adapter
|
|
36
|
+
end
|
|
19
37
|
|
|
20
38
|
def begin_transaction(scope = nil, metadata = [])
|
|
21
39
|
raise AdapterMissing if adapter.nil?
|
|
@@ -47,6 +65,17 @@ module TestProf
|
|
|
47
65
|
def configure
|
|
48
66
|
yield config
|
|
49
67
|
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def default_adapter
|
|
72
|
+
return NoopAdapter if TestProf.dry_run?
|
|
73
|
+
|
|
74
|
+
if defined?(::ActiveRecord::Base)
|
|
75
|
+
require "test_prof/before_all/adapters/active_record"
|
|
76
|
+
Adapters::ActiveRecord
|
|
77
|
+
end
|
|
78
|
+
end
|
|
50
79
|
end
|
|
51
80
|
|
|
52
81
|
class HookEntry # :nodoc:
|
|
@@ -138,12 +167,6 @@ module TestProf
|
|
|
138
167
|
end
|
|
139
168
|
end
|
|
140
169
|
|
|
141
|
-
if defined?(::ActiveRecord::Base)
|
|
142
|
-
require "test_prof/before_all/adapters/active_record"
|
|
143
|
-
|
|
144
|
-
TestProf::BeforeAll.adapter = TestProf::BeforeAll::Adapters::ActiveRecord
|
|
145
|
-
end
|
|
146
|
-
|
|
147
170
|
if defined?(::Isolator)
|
|
148
171
|
require "test_prof/before_all/isolator"
|
|
149
172
|
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lint_roller"
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module TestProf
|
|
7
|
+
# A plugin that integrates TestProf with RuboCop's plugin system.
|
|
8
|
+
class Plugin < LintRoller::Plugin
|
|
9
|
+
def about
|
|
10
|
+
LintRoller::About.new(
|
|
11
|
+
name: "test_prof",
|
|
12
|
+
version: VERSION,
|
|
13
|
+
homepage: "https://test-prof.evilmartians.io/misc/rubocop",
|
|
14
|
+
description: "RuboCop plugin to help you write more performant tests."
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def supported?(context)
|
|
19
|
+
context.engine == :rubocop
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def rules(_context)
|
|
23
|
+
LintRoller::Rules.new(
|
|
24
|
+
type: :path,
|
|
25
|
+
config_format: :rubocop,
|
|
26
|
+
value: Pathname.new(__dir__).join("../../../config/default.yml")
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module RuboCop
|
|
4
4
|
module Cop
|
|
5
5
|
module RSpec
|
|
6
|
-
class AggregateExamples < ::RuboCop::Cop::
|
|
6
|
+
class AggregateExamples < ::RuboCop::Cop::Base
|
|
7
7
|
# @example `its`
|
|
8
8
|
#
|
|
9
9
|
# # Supports regular `its` call with an attribute/method name,
|
|
@@ -64,7 +64,7 @@ module RuboCop
|
|
|
64
64
|
return super unless its?(example.send_node)
|
|
65
65
|
|
|
66
66
|
# First parameter to `its` is not metadata.
|
|
67
|
-
example.send_node.arguments[1
|
|
67
|
+
example.send_node.arguments[1..]
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def its?(node)
|
|
@@ -5,7 +5,7 @@ require "test_prof/cops/rspec/language"
|
|
|
5
5
|
module RuboCop
|
|
6
6
|
module Cop
|
|
7
7
|
module RSpec
|
|
8
|
-
class AggregateExamples < ::RuboCop::Cop::
|
|
8
|
+
class AggregateExamples < ::RuboCop::Cop::Base
|
|
9
9
|
# When aggregated, the expectations will fail when not supposed to or
|
|
10
10
|
# have a risk of not failing when expected to. One example is
|
|
11
11
|
# `validate_presence_of :comment` as it leaves an empty comment after
|
|
@@ -108,7 +108,8 @@ module RuboCop
|
|
|
108
108
|
# expect(number).to be_odd
|
|
109
109
|
# end
|
|
110
110
|
#
|
|
111
|
-
class AggregateExamples < ::RuboCop::Cop::
|
|
111
|
+
class AggregateExamples < ::RuboCop::Cop::Base
|
|
112
|
+
extend AutoCorrector
|
|
112
113
|
include LineRangeHelpers
|
|
113
114
|
include MetadataHelpers
|
|
114
115
|
include NodeMatchers
|
|
@@ -123,29 +124,22 @@ module RuboCop
|
|
|
123
124
|
def on_block(node)
|
|
124
125
|
example_group_with_several_examples(node) do |all_examples|
|
|
125
126
|
example_clusters(all_examples).each do |_, examples|
|
|
126
|
-
examples
|
|
127
|
+
examples.drop(1).each do |example|
|
|
127
128
|
add_offense(example,
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
message: message_for(example, examples[0])) do |corrector|
|
|
130
|
+
clusters = example_clusters_for_autocorrect(example)
|
|
131
|
+
clusters.each do |metadata, examples|
|
|
132
|
+
range = range_for_replace(examples)
|
|
133
|
+
replacement = aggregated_example(examples, metadata)
|
|
134
|
+
corrector.replace(range, replacement)
|
|
135
|
+
examples.drop(1).map { |example| drop_example(corrector, example) }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
130
138
|
end
|
|
131
139
|
end
|
|
132
140
|
end
|
|
133
141
|
end
|
|
134
142
|
|
|
135
|
-
def autocorrect(example_node)
|
|
136
|
-
clusters = example_clusters_for_autocorrect(example_node)
|
|
137
|
-
return if clusters.empty?
|
|
138
|
-
|
|
139
|
-
lambda do |corrector|
|
|
140
|
-
clusters.each do |metadata, examples|
|
|
141
|
-
range = range_for_replace(examples)
|
|
142
|
-
replacement = aggregated_example(examples, metadata)
|
|
143
|
-
corrector.replace(range, replacement)
|
|
144
|
-
examples[1..-1].map { |example| drop_example(corrector, example) }
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
143
|
private
|
|
150
144
|
|
|
151
145
|
# Clusters of examples in the same example group, on the same nesting
|
data/lib/test_prof/core.rb
CHANGED
|
@@ -75,6 +75,10 @@ module TestProf
|
|
|
75
75
|
defined?(::Spring::Application) && (disabled.nil? || disabled.empty? || disabled == "0")
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
+
def dry_run?
|
|
79
|
+
rspec? && ::RSpec.configuration.dry_run?
|
|
80
|
+
end
|
|
81
|
+
|
|
78
82
|
# Returns the current process time
|
|
79
83
|
def now
|
|
80
84
|
Process.clock_gettime_for_test_prof(Process::CLOCK_MONOTONIC)
|
|
@@ -29,7 +29,7 @@ TestProf::EventProf::CustomEvents.register("factory.create") do
|
|
|
29
29
|
TestProf.log(
|
|
30
30
|
:error,
|
|
31
31
|
<<~MSG
|
|
32
|
-
Failed to load factory_bot /
|
|
32
|
+
Failed to load factory_bot / fabrication.
|
|
33
33
|
|
|
34
34
|
Make sure that any of them is in your Gemfile.
|
|
35
35
|
MSG
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module TestProf # :nodoc: all
|
|
4
|
-
|
|
4
|
+
TestProf.require("active_support")
|
|
5
5
|
|
|
6
|
-
TestProf.require("
|
|
7
|
-
|
|
8
|
-
FACTORY_GIRL_NAMES.find do |name, cname|
|
|
9
|
-
TestProf.require(name) do
|
|
10
|
-
TestProf::FactoryBot = Object.const_get(cname)
|
|
11
|
-
end
|
|
6
|
+
TestProf.require("factory_bot") do
|
|
7
|
+
TestProf::FactoryBot = Object.const_get("::FactoryBot")
|
|
12
8
|
end
|
|
13
9
|
end
|
|
@@ -27,6 +27,7 @@ module TestProf
|
|
|
27
27
|
pg_attribute|
|
|
28
28
|
pg_namespace|
|
|
29
29
|
show\stables|
|
|
30
|
+
show\ssearch_path|
|
|
30
31
|
pragma|
|
|
31
32
|
sqlite_master/rollback|
|
|
32
33
|
\ATRUNCATE TABLE|
|
|
@@ -70,7 +71,7 @@ module TestProf
|
|
|
70
71
|
|
|
71
72
|
log :info, "FactoryDoctor enabled (event: \"#{config.event}\", threshold: #{config.threshold})"
|
|
72
73
|
|
|
73
|
-
# Monkey-patch FactoryBot
|
|
74
|
+
# Monkey-patch FactoryBot
|
|
74
75
|
TestProf::FactoryBot::FactoryRunner.prepend(FactoryBotPatch) if
|
|
75
76
|
defined?(TestProf::FactoryBot)
|
|
76
77
|
|
|
@@ -105,10 +106,9 @@ module TestProf
|
|
|
105
106
|
# Do not analyze code within the block
|
|
106
107
|
def ignore
|
|
107
108
|
@ignored = true
|
|
108
|
-
|
|
109
|
+
yield
|
|
109
110
|
ensure
|
|
110
111
|
@ignored = false
|
|
111
|
-
res
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
def ignore!
|
|
@@ -12,7 +12,7 @@ module TestProf
|
|
|
12
12
|
class FactoryBot
|
|
13
13
|
using TestProf::FactoryBotStrategy
|
|
14
14
|
|
|
15
|
-
# Monkey-patch FactoryBot
|
|
15
|
+
# Monkey-patch FactoryBot
|
|
16
16
|
def self.patch
|
|
17
17
|
TestProf::FactoryBot::FactoryRunner.prepend(FactoryBotPatch) if
|
|
18
18
|
defined? TestProf::FactoryBot
|
|
@@ -9,7 +9,7 @@ module TestProf::FactoryProf
|
|
|
9
9
|
using TestProf::FloatDuration
|
|
10
10
|
include TestProf::Logging
|
|
11
11
|
|
|
12
|
-
def dump(result, start_time:, threshold:)
|
|
12
|
+
def dump(result, start_time:, threshold:, truncate_names:)
|
|
13
13
|
return log(:info, "No factories detected") if result.raw_stats == {}
|
|
14
14
|
msgs = []
|
|
15
15
|
|
|
@@ -19,6 +19,12 @@ module TestProf::FactoryProf
|
|
|
19
19
|
total_time = result.stats.sum { |stat| stat[:top_level_time] }
|
|
20
20
|
total_uniq_factories = result.stats.map { |stat| stat[:name] }.uniq.count
|
|
21
21
|
|
|
22
|
+
table_indent = 3
|
|
23
|
+
variations_indent = 2
|
|
24
|
+
max_name_length = result.stats.map { _1[:name].length }.max
|
|
25
|
+
max_variation_length = result.stats.flat_map { _1[:variations] }.select(&:present?).map { _1[:name].length }.max || 0
|
|
26
|
+
name_column_length = truncate_names ? 20 : ([max_name_length, max_variation_length].max + variations_indent)
|
|
27
|
+
|
|
22
28
|
msgs <<
|
|
23
29
|
<<~MSG
|
|
24
30
|
Factories usage
|
|
@@ -27,14 +33,24 @@ module TestProf::FactoryProf
|
|
|
27
33
|
Total top-level: #{total_top_level_count}
|
|
28
34
|
Total time: #{total_time.duration} (out of #{total_run_time.duration})
|
|
29
35
|
Total uniq factories: #{total_uniq_factories}
|
|
30
|
-
|
|
31
|
-
name total top-level total time time per call top-level time
|
|
32
36
|
MSG
|
|
33
37
|
|
|
38
|
+
msgs << format(
|
|
39
|
+
"%#{table_indent}s%-#{name_column_length}s %8s %12s %13s %16s %17s",
|
|
40
|
+
"", "name", "total", "top-level", "total time", "time per call", "top-level time"
|
|
41
|
+
)
|
|
42
|
+
msgs << ""
|
|
43
|
+
|
|
34
44
|
result.stats.each do |stat|
|
|
35
45
|
next if stat[:total_count] < threshold
|
|
36
46
|
|
|
37
|
-
msgs <<
|
|
47
|
+
msgs << formatted(
|
|
48
|
+
table_indent,
|
|
49
|
+
name_column_length,
|
|
50
|
+
truncate_names,
|
|
51
|
+
stat
|
|
52
|
+
)
|
|
53
|
+
|
|
38
54
|
# move other variation ("[...]") to the end of the array
|
|
39
55
|
sorted_variations = stat[:variations].sort_by.with_index do |variation, i|
|
|
40
56
|
(variation[:name] == "[...]") ? stat[:variations].size + 1 : i
|
|
@@ -42,7 +58,12 @@ module TestProf::FactoryProf
|
|
|
42
58
|
sorted_variations.each do |variation_stat|
|
|
43
59
|
next if variation_stat[:total_count] < threshold
|
|
44
60
|
|
|
45
|
-
msgs <<
|
|
61
|
+
msgs << formatted(
|
|
62
|
+
table_indent + variations_indent,
|
|
63
|
+
name_column_length - variations_indent,
|
|
64
|
+
truncate_names,
|
|
65
|
+
variation_stat
|
|
66
|
+
)
|
|
46
67
|
end
|
|
47
68
|
end
|
|
48
69
|
|
|
@@ -51,6 +72,10 @@ module TestProf::FactoryProf
|
|
|
51
72
|
|
|
52
73
|
private
|
|
53
74
|
|
|
75
|
+
def formatted(indent_len, name_len, truncate_names, stat)
|
|
76
|
+
format(format_string(indent_len, name_len, truncate_names), *format_args(stat))
|
|
77
|
+
end
|
|
78
|
+
|
|
54
79
|
def format_args(stat)
|
|
55
80
|
time_per_call = stat[:total_time] / stat[:total_count]
|
|
56
81
|
format_args = [""]
|
|
@@ -58,6 +83,11 @@ module TestProf::FactoryProf
|
|
|
58
83
|
format_args << time_per_call
|
|
59
84
|
format_args << stat[:top_level_time]
|
|
60
85
|
end
|
|
86
|
+
|
|
87
|
+
def format_string(indent_len, name_len, truncate_names)
|
|
88
|
+
name_format = truncate_names ? "#{name_len}.#{name_len}" : name_len.to_s
|
|
89
|
+
"%#{indent_len}s%-#{name_format}s %8d %12d %12.4fs %15.4fs %16.4fs"
|
|
90
|
+
end
|
|
61
91
|
end
|
|
62
92
|
end
|
|
63
93
|
end
|
|
@@ -16,7 +16,8 @@ module TestProf
|
|
|
16
16
|
|
|
17
17
|
# FactoryProf configuration
|
|
18
18
|
class Configuration
|
|
19
|
-
attr_accessor :mode, :printer, :threshold, :include_variations, :variations_limit
|
|
19
|
+
attr_accessor :mode, :printer, :threshold, :include_variations, :variations_limit,
|
|
20
|
+
:truncate_names
|
|
20
21
|
|
|
21
22
|
def initialize
|
|
22
23
|
@mode = (ENV["FPROF"] == "flamegraph") ? :flamegraph : :simple
|
|
@@ -34,6 +35,7 @@ module TestProf
|
|
|
34
35
|
@threshold = ENV.fetch("FPROF_THRESHOLD", 0).to_i
|
|
35
36
|
@include_variations = ENV["FPROF_VARS"] == "1"
|
|
36
37
|
@variations_limit = ENV.fetch("FPROF_VARIATIONS_LIMIT", 2).to_i
|
|
38
|
+
@truncate_names = ENV["FPROF_TRUNCATE_NAMES"] == "1"
|
|
37
39
|
end
|
|
38
40
|
|
|
39
41
|
# Whether we want to generate flamegraphs
|
|
@@ -120,7 +122,7 @@ module TestProf
|
|
|
120
122
|
def print(started_at)
|
|
121
123
|
printer = config.printer
|
|
122
124
|
|
|
123
|
-
printer.dump(result, start_time: started_at, threshold: config.threshold)
|
|
125
|
+
printer.dump(result, start_time: started_at, threshold: config.threshold, truncate_names: config.truncate_names)
|
|
124
126
|
end
|
|
125
127
|
|
|
126
128
|
def start
|
|
@@ -158,7 +160,7 @@ module TestProf
|
|
|
158
160
|
|
|
159
161
|
def variation_name(variation)
|
|
160
162
|
return "-" if variation.empty?
|
|
161
|
-
variations_count = variation.to_s.scan(
|
|
163
|
+
variations_count = variation.to_s.scan(/\w+/).size
|
|
162
164
|
return "[...]" if variations_count > config.variations_limit
|
|
163
165
|
|
|
164
166
|
variation
|
|
@@ -84,16 +84,30 @@ module TestProf
|
|
|
84
84
|
|
|
85
85
|
# Enable verbose Rails logging within a block
|
|
86
86
|
def with_logging
|
|
87
|
+
if ::ActiveSupport.respond_to?(:event_reporter)
|
|
88
|
+
@was_events_debug_mode = ActiveSupport.event_reporter.debug_mode?
|
|
89
|
+
::ActiveSupport.event_reporter.debug_mode = true
|
|
90
|
+
end
|
|
87
91
|
*loggers = LoggingHelpers.swap_logger(LoggingHelpers.all_loggables)
|
|
88
92
|
yield
|
|
89
93
|
ensure
|
|
94
|
+
if ::ActiveSupport.respond_to?(:event_reporter)
|
|
95
|
+
::ActiveSupport.event_reporter.debug_mode = @was_events_debug_mode
|
|
96
|
+
end
|
|
90
97
|
LoggingHelpers.restore_logger(loggers, LoggingHelpers.all_loggables)
|
|
91
98
|
end
|
|
92
99
|
|
|
93
100
|
def with_ar_logging
|
|
101
|
+
if ::ActiveSupport.respond_to?(:event_reporter)
|
|
102
|
+
@was_events_debug_mode = ActiveSupport.event_reporter.debug_mode?
|
|
103
|
+
::ActiveSupport.event_reporter.debug_mode = true
|
|
104
|
+
end
|
|
94
105
|
*loggers = LoggingHelpers.swap_logger(LoggingHelpers.ar_loggables)
|
|
95
106
|
yield
|
|
96
107
|
ensure
|
|
108
|
+
if ::ActiveSupport.respond_to?(:event_reporter)
|
|
109
|
+
::ActiveSupport.event_reporter.debug_mode = @was_events_debug_mode
|
|
110
|
+
end
|
|
97
111
|
LoggingHelpers.restore_logger(loggers, LoggingHelpers.ar_loggables)
|
|
98
112
|
end
|
|
99
113
|
end
|
|
@@ -124,10 +138,16 @@ end
|
|
|
124
138
|
|
|
125
139
|
TestProf.activate("LOG", "all") do
|
|
126
140
|
TestProf.log :info, "Rails verbose logging enabled"
|
|
141
|
+
if ::ActiveSupport.respond_to?(:event_reporter)
|
|
142
|
+
::ActiveSupport.event_reporter.debug_mode = true
|
|
143
|
+
end
|
|
127
144
|
TestProf::Rails::LoggingHelpers.swap_logger!(TestProf::Rails::LoggingHelpers.all_loggables)
|
|
128
145
|
end
|
|
129
146
|
|
|
130
147
|
TestProf.activate("LOG", "ar") do
|
|
131
148
|
TestProf.log :info, "Active Record verbose logging enabled"
|
|
149
|
+
if ::ActiveSupport.respond_to?(:event_reporter)
|
|
150
|
+
::ActiveSupport.event_reporter.debug_mode = true
|
|
151
|
+
end
|
|
132
152
|
TestProf::Rails::LoggingHelpers.swap_logger!(TestProf::Rails::LoggingHelpers.ar_loggables)
|
|
133
153
|
end
|
|
@@ -104,7 +104,7 @@ module TestProf
|
|
|
104
104
|
if base.respond_to?(:parallelize_teardown)
|
|
105
105
|
base.parallelize_teardown do
|
|
106
106
|
last_klass = ::Minitest.previous_klass
|
|
107
|
-
if last_klass&.respond_to?(:parallelized) && last_klass
|
|
107
|
+
if last_klass&.respond_to?(:parallelized) && last_klass.parallelized
|
|
108
108
|
last_klass.before_all_executor&.deactivate!
|
|
109
109
|
end
|
|
110
110
|
end
|
|
@@ -153,14 +153,16 @@ module TestProf
|
|
|
153
153
|
end
|
|
154
154
|
end
|
|
155
155
|
|
|
156
|
-
replacement =
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
replacement = proc do
|
|
157
|
+
$1 + "#{parsed.fname}#{need_parens ? "(" : " "}" \
|
|
158
|
+
"#{[desc, tags_str, htags_str].compact.join(", ")}" \
|
|
159
|
+
"#{need_parens ? ") " : " "}" + $3
|
|
160
|
+
end
|
|
159
161
|
|
|
160
162
|
if config.dry_run?
|
|
161
|
-
log :info, "Patched: #{example.sub(EXAMPLE_RXP, replacement)}"
|
|
163
|
+
log :info, "Patched: #{example.sub(EXAMPLE_RXP, &replacement)}"
|
|
162
164
|
else
|
|
163
|
-
example.sub!(EXAMPLE_RXP, replacement)
|
|
165
|
+
example.sub!(EXAMPLE_RXP, &replacement)
|
|
164
166
|
end
|
|
165
167
|
true
|
|
166
168
|
end
|
data/lib/test_prof/rubocop.rb
CHANGED
data/lib/test_prof/stack_prof.rb
CHANGED
|
@@ -72,7 +72,7 @@ module TestProf
|
|
|
72
72
|
|
|
73
73
|
@locked = true
|
|
74
74
|
|
|
75
|
-
log :info, "StackProf#{
|
|
75
|
+
log :info, "StackProf#{" (raw)" if config.raw?} enabled globally: " \
|
|
76
76
|
"mode – #{config.mode}, target – #{config.target}"
|
|
77
77
|
|
|
78
78
|
at_exit { dump("total") } if config.suite?
|
|
@@ -125,7 +125,7 @@ module TestProf
|
|
|
125
125
|
|
|
126
126
|
def build_path(name)
|
|
127
127
|
TestProf.artifact_path(
|
|
128
|
-
"stack-prof-report-#{config.mode}#{
|
|
128
|
+
"stack-prof-report-#{config.mode}#{"-raw" if config.raw}-#{name}.dump"
|
|
129
129
|
)
|
|
130
130
|
end
|
|
131
131
|
|
|
@@ -7,6 +7,7 @@ module TestProf::TagProf
|
|
|
7
7
|
module Simple # :nodoc: all
|
|
8
8
|
class << self
|
|
9
9
|
include TestProf::Logging
|
|
10
|
+
|
|
10
11
|
using TestProf::FloatDuration
|
|
11
12
|
|
|
12
13
|
def dump(result)
|
|
@@ -35,7 +36,7 @@ module TestProf::TagProf
|
|
|
35
36
|
)
|
|
36
37
|
end
|
|
37
38
|
|
|
38
|
-
header << format(
|
|
39
|
+
header << format( # rubocop:disable Style/RedundantFormat
|
|
39
40
|
"%6s %6s %6s %12s",
|
|
40
41
|
"total", "%total", "%time", "avg"
|
|
41
42
|
)
|
data/lib/test_prof/version.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
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vladimir Dementyev
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2025-12-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -135,7 +135,7 @@ files:
|
|
|
135
135
|
- lib/test_prof/before_all.rb
|
|
136
136
|
- lib/test_prof/before_all/adapters/active_record.rb
|
|
137
137
|
- lib/test_prof/before_all/isolator.rb
|
|
138
|
-
- lib/test_prof/cops/
|
|
138
|
+
- lib/test_prof/cops/plugin.rb
|
|
139
139
|
- lib/test_prof/cops/rspec/aggregate_examples.rb
|
|
140
140
|
- lib/test_prof/cops/rspec/aggregate_examples/its.rb
|
|
141
141
|
- lib/test_prof/cops/rspec/aggregate_examples/line_range_helpers.rb
|
|
@@ -241,7 +241,8 @@ metadata:
|
|
|
241
241
|
homepage_uri: https://test-prof.evilmartians.io/
|
|
242
242
|
source_code_uri: https://github.com/test-prof/test-prof
|
|
243
243
|
funding_uri: https://github.com/sponsors/test-prof
|
|
244
|
-
|
|
244
|
+
default_lint_roller_plugin: RuboCop::TestProf::Plugin
|
|
245
|
+
post_install_message:
|
|
245
246
|
rdoc_options: []
|
|
246
247
|
require_paths:
|
|
247
248
|
- lib
|
|
@@ -257,7 +258,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
257
258
|
version: '0'
|
|
258
259
|
requirements: []
|
|
259
260
|
rubygems_version: 3.4.19
|
|
260
|
-
signing_key:
|
|
261
|
+
signing_key:
|
|
261
262
|
specification_version: 4
|
|
262
263
|
summary: Ruby applications tests profiling tools
|
|
263
264
|
test_files: []
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# This is shamelessly borrowed from RuboCop RSpec
|
|
4
|
-
# https://github.com/rubocop-hq/rubocop-rspec/blob/master/lib/rubocop/rspec/inject.rb
|
|
5
|
-
module TestProf
|
|
6
|
-
module Cops
|
|
7
|
-
# Because RuboCop doesn't yet support plugins, we have to monkey patch in a
|
|
8
|
-
# bit of our configuration.
|
|
9
|
-
module Inject
|
|
10
|
-
PROJECT_ROOT = Pathname.new(__dir__).parent.parent.parent.expand_path.freeze
|
|
11
|
-
CONFIG_DEFAULT = PROJECT_ROOT.join("config", "default.yml").freeze
|
|
12
|
-
|
|
13
|
-
def self.defaults!
|
|
14
|
-
path = CONFIG_DEFAULT.to_s
|
|
15
|
-
hash = RuboCop::ConfigLoader.send(:load_yaml_configuration, path)
|
|
16
|
-
config = RuboCop::Config.new(hash, path)
|
|
17
|
-
puts "configuration from #{path}" if RuboCop::ConfigLoader.debug?
|
|
18
|
-
config = RuboCop::ConfigLoader.merge_with_default(config, path)
|
|
19
|
-
RuboCop::ConfigLoader.instance_variable_set(:@default_configuration, config)
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
TestProf::Cops::Inject.defaults!
|