test-prof 0.11.3 → 1.0.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +122 -447
- data/LICENSE.txt +1 -1
- data/README.md +9 -13
- data/config/default.yml +0 -15
- data/config/rubocop-rspec.yml +6 -0
- data/lib/minitest/test_prof_plugin.rb +3 -0
- data/lib/test_prof/any_fixture.rb +116 -7
- data/lib/test_prof/any_fixture/dump.rb +207 -0
- data/lib/test_prof/any_fixture/dump/base_adapter.rb +43 -0
- data/lib/test_prof/any_fixture/dump/digest.rb +29 -0
- data/lib/test_prof/any_fixture/dump/postgresql.rb +91 -0
- data/lib/test_prof/any_fixture/dump/sqlite.rb +42 -0
- data/lib/test_prof/before_all.rb +9 -4
- data/lib/test_prof/before_all/adapters/active_record.rb +14 -5
- data/lib/test_prof/cops/rspec/aggregate_examples.rb +2 -2
- data/lib/test_prof/cops/rspec/aggregate_examples/its.rb +1 -1
- 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/event_prof/instrumentations/active_support.rb +22 -4
- data/lib/test_prof/recipes/minitest/before_all.rb +48 -23
- data/lib/test_prof/recipes/minitest/sample.rb +6 -10
- data/lib/test_prof/recipes/rspec/before_all.rb +10 -10
- data/lib/test_prof/recipes/rspec/let_it_be.rb +111 -13
- data/lib/test_prof/recipes/rspec/sample.rb +4 -2
- data/lib/test_prof/rubocop.rb +0 -1
- data/lib/test_prof/stack_prof.rb +3 -0
- data/lib/test_prof/version.rb +1 -1
- metadata +23 -21
- data/lib/test_prof/cops/rspec/aggregate_failures.rb +0 -26
- data/lib/test_prof/ext/active_record_3.rb +0 -27
- data/lib/test_prof/recipes/active_record_one_love.rb +0 -6
- data/lib/test_prof/recipes/active_record_shared_connection.rb +0 -77
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
[![Cult Of Martians](http://cultofmartians.com/assets/badges/badge.svg)](
|
2
|
-
[![Gem Version](https://badge.fury.io/rb/test-prof.svg)](https://rubygems.org/gems/test-prof) [![Build](https://github.com/
|
3
|
-
[![JRuby Build](https://github.com/
|
4
|
-
[![Code Triagers Badge](https://www.codetriage.com/
|
1
|
+
[![Cult Of Martians](http://cultofmartians.com/assets/badges/badge.svg)](https://cultofmartians.com)
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/test-prof.svg)](https://rubygems.org/gems/test-prof) [![Build](https://github.com/test-prof/test-prof/workflows/Build/badge.svg)](https://github.com/test-prof/test-prof/actions)
|
3
|
+
[![JRuby Build](https://github.com/test-prof/test-prof/workflows/JRuby%20Build/badge.svg)](https://github.com/test-prof/test-prof/actions)
|
4
|
+
[![Code Triagers Badge](https://www.codetriage.com/test-prof/test-prof/badges/users.svg)](https://www.codetriage.com/test-prof/test-prof)
|
5
5
|
[![Documentation](https://img.shields.io/badge/docs-link-brightgreen.svg)](https://test-prof.evilmartians.io)
|
6
6
|
|
7
7
|
# Ruby Tests Profiling Toolbox
|
@@ -47,11 +47,11 @@ TestProf toolbox aims to help you identify bottlenecks in your test suite. It co
|
|
47
47
|
## Who uses TestProf
|
48
48
|
|
49
49
|
- [Discourse](https://github.com/discourse/discourse) reduced [~27% of their test suite time](https://twitter.com/samsaffron/status/1125602558024699904)
|
50
|
-
- [Gitlab](https://gitlab.com/gitlab-org/gitlab-ce) reduced [39% of their API tests time](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14370)
|
50
|
+
- [Gitlab](https://gitlab.com/gitlab-org/gitlab-ce) reduced [39% of their API tests time](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14370) and [improved factories usage](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/26810)
|
51
51
|
- [CodeTriage](https://github.com/codetriage/codetriage)
|
52
52
|
- [Dev.to](https://github.com/thepracticaldev/dev.to)
|
53
53
|
- [Open Project](https://github.com/opf/openproject)
|
54
|
-
- [...and others](https://github.com/
|
54
|
+
- [...and others](https://github.com/test-prof/test-prof/issues/73)
|
55
55
|
|
56
56
|
## Resources
|
57
57
|
|
@@ -83,7 +83,7 @@ And that's it)
|
|
83
83
|
|
84
84
|
Supported Ruby versions:
|
85
85
|
|
86
|
-
- Ruby (MRI) >= 2.
|
86
|
+
- Ruby (MRI) >= 2.5.0 (**NOTE:** for Ruby 2.2 use TestProf < 0.7.0, Ruby 2.3 use TestProf ~> 0.7.0, Ruby 2.4 use TestProf <0.12.0)
|
87
87
|
|
88
88
|
- JRuby >= 9.1.0.0 (**NOTE:** refinements-dependent features might require 9.2.7+)
|
89
89
|
|
@@ -95,16 +95,12 @@ Check out our [docs][].
|
|
95
95
|
|
96
96
|
## What's next?
|
97
97
|
|
98
|
-
Have an idea? [Propose](https://github.com/
|
98
|
+
Have an idea? [Propose](https://github.com/test-prof/test-prof/issues/new) a feature request!
|
99
99
|
|
100
|
-
Already using TestProf? [Share your story!](https://github.com/
|
100
|
+
Already using TestProf? [Share your story!](https://github.com/test-prof/test-prof/issues/73)
|
101
101
|
|
102
102
|
## License
|
103
103
|
|
104
104
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
105
105
|
|
106
106
|
[docs]: https://test-prof.evilmartians.io
|
107
|
-
|
108
|
-
## Security Contact
|
109
|
-
|
110
|
-
To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
|
data/config/default.yml
CHANGED
@@ -18,18 +18,3 @@ RSpec/AggregateExamples:
|
|
18
18
|
- validate_length_of
|
19
19
|
- validate_inclusion_of
|
20
20
|
- validates_exclusion_of
|
21
|
-
|
22
|
-
# TODO: remove this one we hit 1.0
|
23
|
-
RSpec/AggregateFailures:
|
24
|
-
Description: Checks if example group contains two or more aggregatable examples.
|
25
|
-
Enabled: false
|
26
|
-
StyleGuide: https://rspec.rubystyle.guide/#expectation-per-example
|
27
|
-
AddAggregateFailuresMetadata: true
|
28
|
-
MatchersWithSideEffects:
|
29
|
-
- allow_value
|
30
|
-
- allow_values
|
31
|
-
- validate_presence_of
|
32
|
-
- validate_absence_of
|
33
|
-
- validate_length_of
|
34
|
-
- validate_inclusion_of
|
35
|
-
- validates_exclusion_of
|
@@ -12,6 +12,7 @@ module Minitest # :nodoc:
|
|
12
12
|
opts[:top_count] = ENV["EVENT_PROF_TOP"].to_i if ENV["EVENT_PROF_TOP"]
|
13
13
|
opts[:per_example] = true if ENV["EVENT_PROF_EXAMPLES"]
|
14
14
|
opts[:fdoc] = true if ENV["FDOC"]
|
15
|
+
opts[:sample] = true if ENV["SAMPLE"] || ENV["SAMPLE_GROUPS"]
|
15
16
|
end
|
16
17
|
end
|
17
18
|
end
|
@@ -39,5 +40,7 @@ module Minitest # :nodoc:
|
|
39
40
|
|
40
41
|
reporter << TestProf::EventProfReporter.new(options[:io], options) if options[:event]
|
41
42
|
reporter << TestProf::FactoryDoctorReporter.new(options[:io], options) if options[:fdoc]
|
43
|
+
|
44
|
+
::TestProf::MinitestSample.call if options[:sample]
|
42
45
|
end
|
43
46
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "test_prof/ext/float_duration"
|
4
|
+
require "test_prof/any_fixture/dump"
|
4
5
|
|
5
6
|
module TestProf
|
6
7
|
# Make DB fixtures from blocks.
|
@@ -9,6 +10,58 @@ module TestProf
|
|
9
10
|
|
10
11
|
using FloatDuration
|
11
12
|
|
13
|
+
# AnyFixture configuration
|
14
|
+
class Configuration
|
15
|
+
attr_accessor :reporting_enabled, :dumps_dir, :dump_sequence_start,
|
16
|
+
:import_dump_via_cli, :dump_matching_queries, :force_matching_dumps
|
17
|
+
attr_reader :default_dump_watch_paths
|
18
|
+
|
19
|
+
alias reporting_enabled? reporting_enabled
|
20
|
+
alias import_dump_via_cli? import_dump_via_cli
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@reporting_enabled = ENV["ANYFIXTURE_REPORT"] == "1"
|
24
|
+
@dumps_dir = "any_dumps"
|
25
|
+
@default_dump_watch_paths = %w[
|
26
|
+
db/schema.rb
|
27
|
+
db/structure.sql
|
28
|
+
]
|
29
|
+
@dump_sequence_start = 123_654
|
30
|
+
@dump_matching_queries = /^$/
|
31
|
+
@import_dump_via_cli = ENV["ANYFIXTURE_IMPORT_DUMP_CLI"] == "1"
|
32
|
+
@before_dump = []
|
33
|
+
@after_dump = []
|
34
|
+
@force_matching_dumps =
|
35
|
+
if ENV["ANYFIXTURE_FORCE_DUMP"] == "1"
|
36
|
+
/.*/
|
37
|
+
elsif ENV["ANYFIXTURE_FORCE_DUMP"]
|
38
|
+
/#{ENV["ANYFIXTURE_FORCE_DUMP"]}/
|
39
|
+
else
|
40
|
+
/^$/
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def before_dump(&block)
|
45
|
+
if block_given?
|
46
|
+
@before_dump << block
|
47
|
+
else
|
48
|
+
@before_dump
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def after_dump(&block)
|
53
|
+
if block_given?
|
54
|
+
@after_dump << block
|
55
|
+
else
|
56
|
+
@after_dump
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def dump_sequence_random_start
|
61
|
+
rand(dump_sequence_start..(dump_sequence_start * 2))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
12
65
|
class Cache # :nodoc:
|
13
66
|
attr_reader :store, :stats
|
14
67
|
|
@@ -40,22 +93,74 @@ module TestProf
|
|
40
93
|
class << self
|
41
94
|
include Logging
|
42
95
|
|
43
|
-
|
96
|
+
def config
|
97
|
+
@config ||= Configuration.new
|
98
|
+
end
|
99
|
+
|
100
|
+
def configure
|
101
|
+
yield config
|
102
|
+
end
|
103
|
+
|
104
|
+
# Backward compatibility
|
105
|
+
def reporting_enabled=(val)
|
106
|
+
warn "AnyFixture.reporting_enabled is deprecated and will be removed in 1.1. Use AnyFixture.config.reporting_enabled instead"
|
107
|
+
config.reporting_enabled = val
|
108
|
+
end
|
44
109
|
|
45
|
-
def reporting_enabled
|
46
|
-
reporting_enabled
|
110
|
+
def reporting_enabled
|
111
|
+
warn "AnyFixture.reporting_enabled is deprecated and will be removed in 1.1. Use AnyFixture.config.reporting_enabled instead"
|
112
|
+
config.reporting_enabled
|
47
113
|
end
|
48
114
|
|
115
|
+
alias reporting_enabled? reporting_enabled
|
116
|
+
|
49
117
|
# Register a block of code as a fixture,
|
50
118
|
# returns the result of the block execution
|
51
119
|
def register(id)
|
52
|
-
|
120
|
+
cached(id) do
|
53
121
|
ActiveSupport::Notifications.subscribed(method(:subscriber), "sql.active_record") do
|
54
122
|
yield
|
55
123
|
end
|
56
124
|
end
|
57
125
|
end
|
58
126
|
|
127
|
+
def cached(id)
|
128
|
+
cache.fetch(id) { yield }
|
129
|
+
end
|
130
|
+
|
131
|
+
# Create and register new SQL dump.
|
132
|
+
# Use `watch` to provide additional paths to watch for
|
133
|
+
# dump re-generation
|
134
|
+
def register_dump(name, clean: true, **options)
|
135
|
+
called_from = caller_locations(1, 1).first.path
|
136
|
+
watch = options.delete(:watch) || [called_from]
|
137
|
+
cache_key = options.delete(:cache_key)
|
138
|
+
skip = options.delete(:skip_if)
|
139
|
+
|
140
|
+
id = "sql/#{name}"
|
141
|
+
|
142
|
+
register_method = clean ? :register : :cached
|
143
|
+
|
144
|
+
public_send(register_method, id) do
|
145
|
+
dump = Dump.new(name, watch: watch, cache_key: cache_key)
|
146
|
+
|
147
|
+
unless dump.force?
|
148
|
+
next if skip&.call(dump: dump)
|
149
|
+
|
150
|
+
next dump.within_prepared_env(import: true, **options) { dump.load } if dump.exists?
|
151
|
+
end
|
152
|
+
|
153
|
+
subscriber = ActiveSupport::Notifications.subscribe("sql.active_record", dump.subscriber)
|
154
|
+
res = dump.within_prepared_env(**options) { yield }
|
155
|
+
|
156
|
+
dump.commit!
|
157
|
+
|
158
|
+
res
|
159
|
+
ensure
|
160
|
+
ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
59
164
|
# Clean all affected tables (but do not reset cache)
|
60
165
|
def clean
|
61
166
|
disable_referential_integrity do
|
@@ -76,7 +181,13 @@ module TestProf
|
|
76
181
|
|
77
182
|
def subscriber(_event, _start, _finish, _id, data)
|
78
183
|
matches = data.fetch(:sql).match(INSERT_RXP)
|
79
|
-
|
184
|
+
return unless matches
|
185
|
+
|
186
|
+
table_name = matches[1]
|
187
|
+
|
188
|
+
return if /sqlite_sequence/.match?(table_name)
|
189
|
+
|
190
|
+
tables_cache[table_name] = true
|
80
191
|
end
|
81
192
|
|
82
193
|
def report_stats
|
@@ -148,7 +259,5 @@ module TestProf
|
|
148
259
|
connection.disable_referential_integrity { yield }
|
149
260
|
end
|
150
261
|
end
|
151
|
-
|
152
|
-
self.reporting_enabled = ENV["ANYFIXTURE_REPORT"] == "1"
|
153
262
|
end
|
154
263
|
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_prof/any_fixture/dump/digest"
|
4
|
+
|
5
|
+
require "set"
|
6
|
+
|
7
|
+
module TestProf
|
8
|
+
module AnyFixture
|
9
|
+
MODIFY_RXP = /^(INSERT INTO|UPDATE|DELETE FROM) ([\S]+)/i.freeze
|
10
|
+
ANY_FIXTURE_RXP = /(\/\*|\-\-).*\bany_fixture:dump/.freeze
|
11
|
+
ANY_FIXTURE_IGNORE_RXP = /(\/\*|\-\-).*\bany_fixture:ignore/.freeze
|
12
|
+
|
13
|
+
using(Module.new do
|
14
|
+
refine Object do
|
15
|
+
def to_digest
|
16
|
+
to_s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
refine NilClass do
|
21
|
+
def to_digest
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
refine Hash do
|
27
|
+
def to_digest
|
28
|
+
map { |k, v| [k.to_digest, v.to_digest].compact.join("_") }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
refine Array do
|
33
|
+
def to_digest
|
34
|
+
map { |v| v.to_digest }.compact.join("-")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end)
|
38
|
+
|
39
|
+
class Dump
|
40
|
+
class Subscriber
|
41
|
+
attr_reader :path, :tmp_path
|
42
|
+
|
43
|
+
def initialize(path, adapter)
|
44
|
+
@path = path
|
45
|
+
@adapter = adapter
|
46
|
+
@tmp_path = path + ".tmp"
|
47
|
+
@reset_pk = Set.new
|
48
|
+
@file = File.open(tmp_path, "w")
|
49
|
+
end
|
50
|
+
|
51
|
+
def start(_event, _id, payload)
|
52
|
+
sql = payload.fetch(:sql)
|
53
|
+
return if sql.match?(ANY_FIXTURE_IGNORE_RXP)
|
54
|
+
|
55
|
+
matches = sql.match(MODIFY_RXP)
|
56
|
+
return unless matches
|
57
|
+
|
58
|
+
reset_pk!(matches[2]) if /insert/i.match?(matches[1])
|
59
|
+
end
|
60
|
+
|
61
|
+
def finish(_event, _id, payload)
|
62
|
+
sql = payload.fetch(:sql)
|
63
|
+
return unless trackable_sql?(sql)
|
64
|
+
|
65
|
+
sql = payload[:binds].any? ? adapter.compile_sql(sql, quoted(payload[:binds])) : +sql
|
66
|
+
|
67
|
+
sql.tr!("\n", " ")
|
68
|
+
|
69
|
+
file.write(sql + ";\n")
|
70
|
+
end
|
71
|
+
|
72
|
+
def commit
|
73
|
+
file.close
|
74
|
+
|
75
|
+
FileUtils.mv(tmp_path, path)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
attr_reader :file, :reset_pk, :adapter
|
81
|
+
|
82
|
+
def reset_pk!(table_name)
|
83
|
+
return if /sqlite_sequence/.match?(table_name)
|
84
|
+
|
85
|
+
return if reset_pk.include?(table_name)
|
86
|
+
|
87
|
+
adapter.reset_sequence!(table_name, AnyFixture.config.dump_sequence_random_start)
|
88
|
+
reset_pk << table_name
|
89
|
+
end
|
90
|
+
|
91
|
+
def trackable_sql?(sql)
|
92
|
+
return false if sql.match?(ANY_FIXTURE_IGNORE_RXP)
|
93
|
+
|
94
|
+
sql.match?(MODIFY_RXP) || sql.match?(ANY_FIXTURE_RXP) || sql.match?(AnyFixture.config.dump_matching_queries)
|
95
|
+
end
|
96
|
+
|
97
|
+
def quoted(val)
|
98
|
+
if val.is_a?(Array)
|
99
|
+
val.map { |v| quoted(v) }
|
100
|
+
elsif val.is_a?(ActiveRecord::Relation::QueryAttribute)
|
101
|
+
quoted(val.value_for_database)
|
102
|
+
else
|
103
|
+
ActiveRecord::Base.connection.quote(val)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
attr_reader :name, :digest, :path, :subscriber, :success
|
109
|
+
alias success? success
|
110
|
+
|
111
|
+
def initialize(name, watch: [], cache_key: nil)
|
112
|
+
@name = name
|
113
|
+
@digest = [
|
114
|
+
Digest.call(*watch),
|
115
|
+
cache_key.to_digest
|
116
|
+
].compact.join("-")
|
117
|
+
|
118
|
+
@path = build_path(name, digest)
|
119
|
+
|
120
|
+
@success = false
|
121
|
+
|
122
|
+
@adapter =
|
123
|
+
case ActiveRecord::Base.connection.adapter_name
|
124
|
+
when /sqlite/i
|
125
|
+
require "test_prof/any_fixture/dump/sqlite"
|
126
|
+
SQLite.new
|
127
|
+
when /postgresql/i
|
128
|
+
require "test_prof/any_fixture/dump/postgresql"
|
129
|
+
PostgreSQL.new
|
130
|
+
else
|
131
|
+
raise ArgumentError,
|
132
|
+
"Your current database adapter (#{ActiveRecord::Base.connection.adapter_name}) " \
|
133
|
+
"is currently not supported. So far, we only support SQLite and PostgreSQL"
|
134
|
+
end
|
135
|
+
|
136
|
+
@subscriber = Subscriber.new(path, adapter)
|
137
|
+
end
|
138
|
+
|
139
|
+
def exists?
|
140
|
+
File.exist?(path)
|
141
|
+
end
|
142
|
+
|
143
|
+
def force?
|
144
|
+
AnyFixture.config.force_matching_dumps.match?(name)
|
145
|
+
end
|
146
|
+
|
147
|
+
def load
|
148
|
+
return import_via_active_record unless AnyFixture.config.import_dump_via_cli?
|
149
|
+
|
150
|
+
adapter.import(path) || import_via_active_record
|
151
|
+
end
|
152
|
+
|
153
|
+
def commit!
|
154
|
+
subscriber.commit
|
155
|
+
end
|
156
|
+
|
157
|
+
def within_prepared_env(before: nil, after: nil, import: false)
|
158
|
+
run_before_callbacks(callback: before, dump: self, import: false)
|
159
|
+
yield.tap do
|
160
|
+
@success = true
|
161
|
+
end
|
162
|
+
ensure
|
163
|
+
run_after_callbacks(callback: after, dump: self, import: false)
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
attr_reader :adapter
|
169
|
+
|
170
|
+
def import_via_active_record
|
171
|
+
conn = ActiveRecord::Base.connection
|
172
|
+
|
173
|
+
File.open(path).each_line do |query|
|
174
|
+
next if query.empty?
|
175
|
+
|
176
|
+
conn.execute query
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def build_path(name, digest)
|
181
|
+
dir = TestProf.artifact_path(
|
182
|
+
File.join(AnyFixture.config.dumps_dir)
|
183
|
+
)
|
184
|
+
|
185
|
+
FileUtils.mkdir_p(dir)
|
186
|
+
|
187
|
+
File.join(dir, "#{name}-#{digest}.sql")
|
188
|
+
end
|
189
|
+
|
190
|
+
def run_before_callbacks(callback:, **options)
|
191
|
+
# First, call config-defined setup callbacks
|
192
|
+
AnyFixture.config.before_dump.each { |clbk| clbk.call(**options) }
|
193
|
+
# Then, adapter-defined callbacks
|
194
|
+
adapter.setup_env unless options[:import]
|
195
|
+
# Finally, user-provided callback
|
196
|
+
callback&.call(**options)
|
197
|
+
end
|
198
|
+
|
199
|
+
def run_after_callbacks(callback:, **options)
|
200
|
+
# The order is vice versa to setup
|
201
|
+
callback&.call(**options)
|
202
|
+
adapter.teardown_env unless options[:import]
|
203
|
+
AnyFixture.config.after_dump.each { |clbk| clbk.call(**options) }
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|