activesupport 8.0.5.2 → 8.1.0.beta1
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 +220 -278
- data/lib/active_support/backtrace_cleaner.rb +71 -0
- data/lib/active_support/cache/mem_cache_store.rb +14 -17
- data/lib/active_support/cache/redis_cache_store.rb +37 -31
- data/lib/active_support/cache/strategy/local_cache.rb +16 -7
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
- data/lib/active_support/cache.rb +69 -6
- data/lib/active_support/configurable.rb +28 -0
- data/lib/active_support/continuous_integration.rb +145 -0
- data/lib/active_support/core_ext/benchmark.rb +0 -1
- data/lib/active_support/core_ext/class/attribute.rb +6 -8
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -1
- data/lib/active_support/core_ext/enumerable.rb +2 -2
- data/lib/active_support/core_ext/erb/util.rb +3 -3
- data/lib/active_support/core_ext/object/json.rb +9 -2
- data/lib/active_support/core_ext/object/to_query.rb +5 -0
- data/lib/active_support/core_ext/range.rb +0 -1
- data/lib/active_support/core_ext/string/inflections.rb +1 -1
- data/lib/active_support/core_ext/string/multibyte.rb +10 -1
- data/lib/active_support/core_ext/string/output_safety.rb +20 -15
- data/lib/active_support/current_attributes/test_helper.rb +2 -2
- data/lib/active_support/current_attributes.rb +13 -10
- data/lib/active_support/delegation.rb +42 -25
- data/lib/active_support/deprecation/reporting.rb +4 -2
- data/lib/active_support/deprecation.rb +1 -1
- data/lib/active_support/editor.rb +70 -0
- data/lib/active_support/error_reporter.rb +50 -6
- data/lib/active_support/event_reporter/test_helper.rb +32 -0
- data/lib/active_support/event_reporter.rb +570 -0
- data/lib/active_support/evented_file_update_checker.rb +5 -1
- data/lib/active_support/execution_context.rb +64 -7
- data/lib/active_support/file_update_checker.rb +8 -6
- data/lib/active_support/gem_version.rb +3 -3
- data/lib/active_support/gzip.rb +1 -0
- data/lib/active_support/hash_with_indifferent_access.rb +27 -7
- data/lib/active_support/i18n_railtie.rb +1 -2
- data/lib/active_support/inflector/inflections.rb +32 -17
- data/lib/active_support/inflector/methods.rb +21 -5
- data/lib/active_support/inflector/transliterate.rb +6 -8
- data/lib/active_support/isolated_execution_state.rb +9 -17
- data/lib/active_support/json/decoding.rb +2 -2
- data/lib/active_support/json/encoding.rb +103 -14
- data/lib/active_support/log_subscriber.rb +2 -0
- data/lib/active_support/message_encryptors.rb +52 -0
- data/lib/active_support/message_pack/extensions.rb +6 -1
- data/lib/active_support/message_verifiers.rb +52 -0
- data/lib/active_support/messages/rotation_coordinator.rb +9 -0
- data/lib/active_support/messages/rotator.rb +5 -0
- data/lib/active_support/multibyte/chars.rb +8 -1
- data/lib/active_support/multibyte.rb +4 -0
- data/lib/active_support/notifications/instrumenter.rb +1 -1
- data/lib/active_support/number_helper/number_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +2 -17
- data/lib/active_support/railtie.rb +26 -12
- data/lib/active_support/syntax_error_proxy.rb +3 -0
- data/lib/active_support/test_case.rb +61 -12
- data/lib/active_support/testing/assertions.rb +37 -14
- data/lib/active_support/testing/autorun.rb +0 -5
- data/lib/active_support/testing/error_reporter_assertions.rb +18 -1
- data/lib/active_support/testing/event_reporter_assertions.rb +217 -0
- data/lib/active_support/testing/notification_assertions.rb +92 -0
- data/lib/active_support/testing/parallelization/server.rb +2 -15
- data/lib/active_support/testing/parallelization/worker.rb +5 -7
- data/lib/active_support/testing/parallelization.rb +14 -12
- data/lib/active_support/testing/tests_without_assertions.rb +1 -1
- data/lib/active_support/testing/time_helpers.rb +7 -3
- data/lib/active_support/time_with_zone.rb +19 -5
- data/lib/active_support/values/time_zone.rb +8 -1
- data/lib/active_support/xml_mini.rb +1 -4
- data/lib/active_support.rb +11 -0
- metadata +11 -6
- data/lib/active_support/core_ext/range/each.rb +0 -24
|
@@ -6,6 +6,7 @@ require "active_support/testing/setup_and_teardown"
|
|
|
6
6
|
require "active_support/testing/tests_without_assertions"
|
|
7
7
|
require "active_support/testing/assertions"
|
|
8
8
|
require "active_support/testing/error_reporter_assertions"
|
|
9
|
+
require "active_support/testing/event_reporter_assertions"
|
|
9
10
|
require "active_support/testing/deprecation"
|
|
10
11
|
require "active_support/testing/declarative"
|
|
11
12
|
require "active_support/testing/isolation"
|
|
@@ -15,13 +16,29 @@ require "active_support/testing/constant_stubbing"
|
|
|
15
16
|
require "active_support/testing/file_fixtures"
|
|
16
17
|
require "active_support/testing/parallelization"
|
|
17
18
|
require "active_support/testing/parallelize_executor"
|
|
19
|
+
require "active_support/testing/notification_assertions"
|
|
18
20
|
require "concurrent/utility/processor_counter"
|
|
19
21
|
|
|
20
22
|
module ActiveSupport
|
|
21
23
|
class TestCase < ::Minitest::Test
|
|
22
24
|
Assertion = Minitest::Assertion
|
|
23
25
|
|
|
26
|
+
# Class variable to store the parallel worker ID
|
|
27
|
+
@@parallel_worker_id = nil
|
|
28
|
+
|
|
24
29
|
class << self
|
|
30
|
+
# Returns the current parallel worker ID if tests are running in parallel,
|
|
31
|
+
# nil otherwise.
|
|
32
|
+
#
|
|
33
|
+
# ActiveSupport::TestCase.parallel_worker_id # => 2
|
|
34
|
+
def parallel_worker_id
|
|
35
|
+
@@parallel_worker_id
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def parallel_worker_id=(value) # :nodoc:
|
|
39
|
+
@@parallel_worker_id = value
|
|
40
|
+
end
|
|
41
|
+
|
|
25
42
|
# Sets the order in which test cases are run.
|
|
26
43
|
#
|
|
27
44
|
# ActiveSupport::TestCase.test_order = :random # => :random
|
|
@@ -45,20 +62,14 @@ module ActiveSupport
|
|
|
45
62
|
ActiveSupport.test_order ||= :random
|
|
46
63
|
end
|
|
47
64
|
|
|
48
|
-
if Minitest.respond_to? :run_order # MT6 API change
|
|
49
|
-
def run_order # :nodoc:
|
|
50
|
-
test_order
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
65
|
# Parallelizes the test suite.
|
|
55
66
|
#
|
|
56
67
|
# Takes a +workers+ argument that controls how many times the process
|
|
57
68
|
# is forked. For each process a new database will be created suffixed
|
|
58
69
|
# with the worker number.
|
|
59
70
|
#
|
|
60
|
-
# test-
|
|
61
|
-
# test-
|
|
71
|
+
# test-database_0
|
|
72
|
+
# test-database_1
|
|
62
73
|
#
|
|
63
74
|
# If <tt>ENV["PARALLEL_WORKERS"]</tt> is set the workers argument will be ignored
|
|
64
75
|
# and the environment variable will be used instead. This is useful for CI
|
|
@@ -84,14 +95,45 @@ module ActiveSupport
|
|
|
84
95
|
# Because parallelization presents an overhead, it is only enabled when the
|
|
85
96
|
# number of tests to run is above the +threshold+ param. The default value is
|
|
86
97
|
# 50, and it's configurable via +config.active_support.test_parallelization_threshold+.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
98
|
+
#
|
|
99
|
+
# If you want to skip Rails default creation of one database per process in favor of
|
|
100
|
+
# writing your own implementation, you can set +parallelize_databases+, or configure it
|
|
101
|
+
# via +config.active_support.parallelize_test_databases+.
|
|
102
|
+
#
|
|
103
|
+
# parallelize(workers: :number_of_processors, parallelize_databases: false)
|
|
104
|
+
#
|
|
105
|
+
# Note that your test suite may deadlock if you attempt to use only one database
|
|
106
|
+
# with multiple processes.
|
|
107
|
+
def parallelize(workers: :number_of_processors, with: :processes, threshold: ActiveSupport.test_parallelization_threshold, parallelize_databases: ActiveSupport.parallelize_test_databases)
|
|
108
|
+
case
|
|
109
|
+
when ENV["PARALLEL_WORKERS"]
|
|
110
|
+
workers = ENV["PARALLEL_WORKERS"].to_i
|
|
111
|
+
when workers == :number_of_processors
|
|
112
|
+
workers = (Concurrent.available_processor_count || Concurrent.processor_count).floor
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
if with == :processes
|
|
116
|
+
ActiveSupport.parallelize_test_databases = parallelize_databases
|
|
117
|
+
end
|
|
90
118
|
|
|
91
119
|
Minitest.parallel_executor = ActiveSupport::Testing::ParallelizeExecutor.new(size: workers, with: with, threshold: threshold)
|
|
92
120
|
end
|
|
93
121
|
|
|
94
|
-
#
|
|
122
|
+
# Before fork hook for parallel testing. This can be used to run anything
|
|
123
|
+
# before the processes are forked.
|
|
124
|
+
#
|
|
125
|
+
# In your +test_helper.rb+ add the following:
|
|
126
|
+
#
|
|
127
|
+
# class ActiveSupport::TestCase
|
|
128
|
+
# parallelize_before_fork do
|
|
129
|
+
# # run this before fork
|
|
130
|
+
# end
|
|
131
|
+
# end
|
|
132
|
+
def parallelize_before_fork(&block)
|
|
133
|
+
ActiveSupport::Testing::Parallelization.before_fork_hook(&block)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Setup hook for parallel testing. This can be used if you have multiple
|
|
95
137
|
# databases or any behavior that needs to be run after the process is forked
|
|
96
138
|
# but before the tests run.
|
|
97
139
|
#
|
|
@@ -147,11 +189,18 @@ module ActiveSupport
|
|
|
147
189
|
|
|
148
190
|
alias_method :method_name, :name
|
|
149
191
|
|
|
192
|
+
# Returns the current parallel worker ID if tests are running in parallel
|
|
193
|
+
def parallel_worker_id
|
|
194
|
+
self.class.parallel_worker_id
|
|
195
|
+
end
|
|
196
|
+
|
|
150
197
|
include ActiveSupport::Testing::TaggedLogging
|
|
151
198
|
prepend ActiveSupport::Testing::SetupAndTeardown
|
|
152
199
|
prepend ActiveSupport::Testing::TestsWithoutAssertions
|
|
153
200
|
include ActiveSupport::Testing::Assertions
|
|
154
201
|
include ActiveSupport::Testing::ErrorReporterAssertions
|
|
202
|
+
include ActiveSupport::Testing::EventReporterAssertions
|
|
203
|
+
include ActiveSupport::Testing::NotificationAssertions
|
|
155
204
|
include ActiveSupport::Testing::Deprecation
|
|
156
205
|
include ActiveSupport::Testing::ConstantStubbing
|
|
157
206
|
include ActiveSupport::Testing::TimeHelpers
|
|
@@ -71,19 +71,19 @@ module ActiveSupport
|
|
|
71
71
|
# post :delete, params: { id: ... }
|
|
72
72
|
# end
|
|
73
73
|
#
|
|
74
|
-
# An array of expressions can
|
|
74
|
+
# An array of expressions can be passed in and evaluated.
|
|
75
75
|
#
|
|
76
76
|
# assert_difference [ 'Article.count', 'Post.count' ], 2 do
|
|
77
77
|
# post :create, params: { article: {...} }
|
|
78
78
|
# end
|
|
79
79
|
#
|
|
80
|
-
# A hash of expressions/numeric differences can
|
|
80
|
+
# A hash of expressions/numeric differences can be passed in and evaluated.
|
|
81
81
|
#
|
|
82
|
-
# assert_difference
|
|
82
|
+
# assert_difference({ 'Article.count' => 1, 'Notification.count' => 2 }) do
|
|
83
83
|
# post :create, params: { article: {...} }
|
|
84
84
|
# end
|
|
85
85
|
#
|
|
86
|
-
# A lambda or a
|
|
86
|
+
# A lambda, a list of lambdas or a hash of lambdas/numeric differences can be passed in and evaluated:
|
|
87
87
|
#
|
|
88
88
|
# assert_difference ->{ Article.count }, 2 do
|
|
89
89
|
# post :create, params: { article: {...} }
|
|
@@ -93,6 +93,10 @@ module ActiveSupport
|
|
|
93
93
|
# post :create, params: { article: {...} }
|
|
94
94
|
# end
|
|
95
95
|
#
|
|
96
|
+
# assert_difference ->{ Article.count } => 1, ->{ Notification.count } => 2 do
|
|
97
|
+
# post :create, params: { article: {...} }
|
|
98
|
+
# end
|
|
99
|
+
#
|
|
96
100
|
# An error message can be specified.
|
|
97
101
|
#
|
|
98
102
|
# assert_difference 'Article.count', -1, 'An Article should be destroyed' do
|
|
@@ -120,8 +124,7 @@ module ActiveSupport
|
|
|
120
124
|
actual = exp.call
|
|
121
125
|
rich_message = -> do
|
|
122
126
|
code_string = code.respond_to?(:call) ? _callable_to_source_string(code) : code
|
|
123
|
-
error = "`#{code_string}` didn't change by #{diff}, but by #{actual - before_value}
|
|
124
|
-
error = "#{error}\n#{diff before_value + diff, actual}" if Minitest::VERSION > "6"
|
|
127
|
+
error = "`#{code_string}` didn't change by #{diff}, but by #{actual - before_value}"
|
|
125
128
|
error = "#{message}.\n#{error}" if message
|
|
126
129
|
error
|
|
127
130
|
end
|
|
@@ -182,12 +185,24 @@ module ActiveSupport
|
|
|
182
185
|
#
|
|
183
186
|
# The keyword arguments +:from+ and +:to+ can be given to specify the
|
|
184
187
|
# expected initial value and the expected value after the block was
|
|
185
|
-
# executed.
|
|
188
|
+
# executed. The comparison is done using case equality (===), which means
|
|
189
|
+
# you can specify patterns or classes:
|
|
186
190
|
#
|
|
191
|
+
# # Exact value match
|
|
187
192
|
# assert_changes :@object, from: nil, to: :foo do
|
|
188
193
|
# @object = :foo
|
|
189
194
|
# end
|
|
190
195
|
#
|
|
196
|
+
# # Case equality
|
|
197
|
+
# assert_changes -> { user.token }, to: /\w{32}/ do
|
|
198
|
+
# user.generate_token
|
|
199
|
+
# end
|
|
200
|
+
#
|
|
201
|
+
# # Type check
|
|
202
|
+
# assert_changes -> { current_error }, from: nil, to: RuntimeError do
|
|
203
|
+
# raise "Oops"
|
|
204
|
+
# end
|
|
205
|
+
#
|
|
191
206
|
# An error message can be specified.
|
|
192
207
|
#
|
|
193
208
|
# assert_changes -> { Status.all_good? }, 'Expected the status to be bad' do
|
|
@@ -213,7 +228,7 @@ module ActiveSupport
|
|
|
213
228
|
rich_message = -> do
|
|
214
229
|
code_string = expression.respond_to?(:call) ? _callable_to_source_string(expression) : expression
|
|
215
230
|
error = "`#{code_string}` didn't change"
|
|
216
|
-
error = "#{error}. It was already #{to.inspect}
|
|
231
|
+
error = "#{error}. It was already #{to.inspect}" if before == to
|
|
217
232
|
error = "#{message}.\n#{error}" if message
|
|
218
233
|
error
|
|
219
234
|
end
|
|
@@ -239,12 +254,24 @@ module ActiveSupport
|
|
|
239
254
|
# end
|
|
240
255
|
#
|
|
241
256
|
# Provide the optional keyword argument +:from+ to specify the expected
|
|
242
|
-
# initial value.
|
|
257
|
+
# initial value. The comparison is done using case equality (===), which means
|
|
258
|
+
# you can specify patterns or classes:
|
|
243
259
|
#
|
|
260
|
+
# # Exact value match
|
|
244
261
|
# assert_no_changes -> { Status.all_good? }, from: true do
|
|
245
262
|
# post :create, params: { status: { ok: true } }
|
|
246
263
|
# end
|
|
247
264
|
#
|
|
265
|
+
# # Case equality
|
|
266
|
+
# assert_no_changes -> { user.token }, from: /\w{32}/ do
|
|
267
|
+
# user.touch
|
|
268
|
+
# end
|
|
269
|
+
#
|
|
270
|
+
# # Type check
|
|
271
|
+
# assert_no_changes -> { current_error }, from: RuntimeError do
|
|
272
|
+
# retry_operation
|
|
273
|
+
# end
|
|
274
|
+
#
|
|
248
275
|
# An error message can be specified.
|
|
249
276
|
#
|
|
250
277
|
# assert_no_changes -> { Status.all_good? }, 'Expected the status to be good' do
|
|
@@ -269,9 +296,8 @@ module ActiveSupport
|
|
|
269
296
|
|
|
270
297
|
rich_message = -> do
|
|
271
298
|
code_string = expression.respond_to?(:call) ? _callable_to_source_string(expression) : expression
|
|
272
|
-
error = "`#{code_string}` changed
|
|
299
|
+
error = "`#{code_string}` changed"
|
|
273
300
|
error = "#{message}.\n#{error}" if message
|
|
274
|
-
error = "#{error}\n#{diff before, after}" if Minitest::VERSION > "6"
|
|
275
301
|
error
|
|
276
302
|
end
|
|
277
303
|
|
|
@@ -320,9 +346,6 @@ module ActiveSupport
|
|
|
320
346
|
lines[0] = lines[0].byteslice(location[1]...)
|
|
321
347
|
source = lines.join.strip
|
|
322
348
|
|
|
323
|
-
# Strip stabby lambda from Ruby 4.1+
|
|
324
|
-
source = source.sub(/^->\s*/, "")
|
|
325
|
-
|
|
326
349
|
# We ignore procs defined with do/end as they are likely multi-line anyway.
|
|
327
350
|
if source.start_with?("{")
|
|
328
351
|
source.delete_suffix!("}")
|
|
@@ -2,9 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
require "minitest"
|
|
4
4
|
|
|
5
|
-
# This respond_to check handles tests running sub-processes in an
|
|
6
|
-
# unbundled environment, which triggers MT5 usage. This conditional may
|
|
7
|
-
# be removable after the version bump, though it currently safeguards
|
|
8
|
-
# against issues in environments with multiple versions installed.
|
|
9
|
-
Minitest.load :rails if Minitest.respond_to? :load
|
|
10
5
|
Minitest.autorun
|
|
@@ -44,7 +44,7 @@ module ActiveSupport
|
|
|
44
44
|
ActiveSupport.error_reporter.subscribe(self)
|
|
45
45
|
@subscribed = true
|
|
46
46
|
else
|
|
47
|
-
|
|
47
|
+
flunk("No error reporter is configured")
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
end
|
|
@@ -102,6 +102,23 @@ module ActiveSupport
|
|
|
102
102
|
assert(false, message)
|
|
103
103
|
end
|
|
104
104
|
end
|
|
105
|
+
|
|
106
|
+
# Captures reported errors from within the block that match the given
|
|
107
|
+
# error class.
|
|
108
|
+
#
|
|
109
|
+
# reports = capture_error_reports(IOError) do
|
|
110
|
+
# Rails.error.report(IOError.new("Oops"))
|
|
111
|
+
# Rails.error.report(IOError.new("Oh no"))
|
|
112
|
+
# Rails.error.report(StandardError.new)
|
|
113
|
+
# end
|
|
114
|
+
#
|
|
115
|
+
# assert_equal 2, reports.size
|
|
116
|
+
# assert_equal "Oops", reports.first.error.message
|
|
117
|
+
# assert_equal "Oh no", reports.last.error.message
|
|
118
|
+
def capture_error_reports(error_class = StandardError, &block)
|
|
119
|
+
reports = ErrorCollector.record(&block)
|
|
120
|
+
reports.select { |r| error_class === r.error }
|
|
121
|
+
end
|
|
105
122
|
end
|
|
106
123
|
end
|
|
107
124
|
end
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveSupport
|
|
4
|
+
module Testing
|
|
5
|
+
# Provides test helpers for asserting on ActiveSupport::EventReporter events.
|
|
6
|
+
module EventReporterAssertions
|
|
7
|
+
module EventCollector # :nodoc:
|
|
8
|
+
@subscribed = false
|
|
9
|
+
@mutex = Mutex.new
|
|
10
|
+
|
|
11
|
+
class Event # :nodoc:
|
|
12
|
+
attr_reader :event_data
|
|
13
|
+
|
|
14
|
+
def initialize(event_data)
|
|
15
|
+
@event_data = event_data
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def inspect
|
|
19
|
+
"#{event_data[:name]} (payload: #{event_data[:payload].inspect}, tags: #{event_data[:tags].inspect})"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def matches?(name, payload, tags)
|
|
23
|
+
return false unless name.to_s == event_data[:name]
|
|
24
|
+
|
|
25
|
+
if payload && payload.is_a?(Hash)
|
|
26
|
+
return false unless matches_hash?(payload, :payload)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
return false unless matches_hash?(tags, :tags)
|
|
30
|
+
true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
def matches_hash?(expected_hash, event_key)
|
|
35
|
+
expected_hash.all? do |k, v|
|
|
36
|
+
if v.is_a?(Regexp)
|
|
37
|
+
event_data.dig(event_key, k).to_s.match?(v)
|
|
38
|
+
else
|
|
39
|
+
event_data.dig(event_key, k) == v
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class << self
|
|
46
|
+
def emit(event)
|
|
47
|
+
event_recorders&.each do |events|
|
|
48
|
+
events << Event.new(event)
|
|
49
|
+
end
|
|
50
|
+
true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def record
|
|
54
|
+
subscribe
|
|
55
|
+
events = []
|
|
56
|
+
event_recorders << events
|
|
57
|
+
begin
|
|
58
|
+
yield
|
|
59
|
+
events
|
|
60
|
+
ensure
|
|
61
|
+
event_recorders.delete_if { |r| events.equal?(r) }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
def subscribe
|
|
67
|
+
return if @subscribed
|
|
68
|
+
|
|
69
|
+
@mutex.synchronize do
|
|
70
|
+
unless @subscribed
|
|
71
|
+
if ActiveSupport.event_reporter
|
|
72
|
+
ActiveSupport.event_reporter.subscribe(self)
|
|
73
|
+
@subscribed = true
|
|
74
|
+
else
|
|
75
|
+
raise Minitest::Assertion, "No event reporter is configured"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def event_recorders
|
|
82
|
+
ActiveSupport::IsolatedExecutionState[:active_support_event_reporter_assertions] ||= []
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Asserts that the block does not cause an event to be reported to +Rails.event+.
|
|
88
|
+
#
|
|
89
|
+
# If no name is provided, passes if evaluated code in the yielded block reports no events.
|
|
90
|
+
#
|
|
91
|
+
# assert_no_event_reported do
|
|
92
|
+
# service_that_does_not_report_events.perform
|
|
93
|
+
# end
|
|
94
|
+
#
|
|
95
|
+
# If a name is provided, passes if evaluated code in the yielded block reports no events
|
|
96
|
+
# with that name.
|
|
97
|
+
#
|
|
98
|
+
# assert_no_event_reported("user.created") do
|
|
99
|
+
# service_that_does_not_report_events.perform
|
|
100
|
+
# end
|
|
101
|
+
def assert_no_event_reported(name = nil, payload: {}, tags: {}, &block)
|
|
102
|
+
events = EventCollector.record(&block)
|
|
103
|
+
|
|
104
|
+
if name.nil?
|
|
105
|
+
assert_predicate(events, :empty?)
|
|
106
|
+
else
|
|
107
|
+
matching_event = events.find { |event| event.matches?(name, payload, tags) }
|
|
108
|
+
if matching_event
|
|
109
|
+
message = "Expected no '#{name}' event to be reported, but found:\n " \
|
|
110
|
+
"#{matching_event.inspect}"
|
|
111
|
+
flunk(message)
|
|
112
|
+
end
|
|
113
|
+
assert(true)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Asserts that the block causes an event with the given name to be reported
|
|
118
|
+
# to +Rails.event+.
|
|
119
|
+
#
|
|
120
|
+
# Passes if the evaluated code in the yielded block reports a matching event.
|
|
121
|
+
#
|
|
122
|
+
# assert_event_reported("user.created") do
|
|
123
|
+
# Rails.event.notify("user.created", { id: 123 })
|
|
124
|
+
# end
|
|
125
|
+
#
|
|
126
|
+
# To test further details about the reported event, you can specify payload and tag matchers.
|
|
127
|
+
#
|
|
128
|
+
# assert_event_reported("user.created",
|
|
129
|
+
# payload: { id: 123, name: "John Doe" },
|
|
130
|
+
# tags: { request_id: /[0-9]+/ }
|
|
131
|
+
# ) do
|
|
132
|
+
# Rails.event.tagged(request_id: "123") do
|
|
133
|
+
# Rails.event.notify("user.created", { id: 123, name: "John Doe" })
|
|
134
|
+
# end
|
|
135
|
+
# end
|
|
136
|
+
#
|
|
137
|
+
# The matchers support partial matching - only the specified keys need to match.
|
|
138
|
+
#
|
|
139
|
+
# assert_event_reported("user.created", payload: { id: 123 }) do
|
|
140
|
+
# Rails.event.notify("user.created", { id: 123, name: "John Doe" })
|
|
141
|
+
# end
|
|
142
|
+
def assert_event_reported(name, payload: nil, tags: {}, &block)
|
|
143
|
+
events = EventCollector.record(&block)
|
|
144
|
+
|
|
145
|
+
if events.empty?
|
|
146
|
+
flunk("Expected an event to be reported, but there were no events reported.")
|
|
147
|
+
elsif (event = events.find { |event| event.matches?(name, payload, tags) })
|
|
148
|
+
assert(true)
|
|
149
|
+
event.event_data
|
|
150
|
+
else
|
|
151
|
+
message = "Expected an event to be reported matching:\n " \
|
|
152
|
+
"name: #{name}\n " \
|
|
153
|
+
"payload: #{payload.inspect}\n " \
|
|
154
|
+
"tags: #{tags.inspect}\n" \
|
|
155
|
+
"but none of the #{events.size} reported events matched:\n " \
|
|
156
|
+
"#{events.map(&:inspect).join("\n ")}"
|
|
157
|
+
flunk(message)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Asserts that the provided events were reported, regardless of order.
|
|
162
|
+
#
|
|
163
|
+
# assert_events_reported([
|
|
164
|
+
# { name: "user.created", payload: { id: 123 } },
|
|
165
|
+
# { name: "email.sent", payload: { to: "user@example.com" } }
|
|
166
|
+
# ]) do
|
|
167
|
+
# create_user_and_send_welcome_email
|
|
168
|
+
# end
|
|
169
|
+
#
|
|
170
|
+
# Supports the same payload and tag matching as +assert_event_reported+.
|
|
171
|
+
#
|
|
172
|
+
# assert_events_reported([
|
|
173
|
+
# {
|
|
174
|
+
# name: "process.started",
|
|
175
|
+
# payload: { id: 123 },
|
|
176
|
+
# tags: { request_id: /[0-9]+/ }
|
|
177
|
+
# },
|
|
178
|
+
# { name: "process.completed" }
|
|
179
|
+
# ]) do
|
|
180
|
+
# Rails.event.tagged(request_id: "456") do
|
|
181
|
+
# start_and_complete_process(123)
|
|
182
|
+
# end
|
|
183
|
+
# end
|
|
184
|
+
def assert_events_reported(expected_events, &block)
|
|
185
|
+
events = EventCollector.record(&block)
|
|
186
|
+
|
|
187
|
+
if events.empty? && expected_events.size > 0
|
|
188
|
+
flunk("Expected #{expected_events.size} events to be reported, but there were no events reported.")
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
events_copy = events.dup
|
|
192
|
+
|
|
193
|
+
expected_events.each do |expected_event|
|
|
194
|
+
name = expected_event[:name]
|
|
195
|
+
payload = expected_event[:payload] || {}
|
|
196
|
+
tags = expected_event[:tags] || {}
|
|
197
|
+
|
|
198
|
+
matching_event_index = events_copy.find_index { |event| event.matches?(name, payload, tags) }
|
|
199
|
+
|
|
200
|
+
if matching_event_index
|
|
201
|
+
events_copy.delete_at(matching_event_index)
|
|
202
|
+
else
|
|
203
|
+
message = "Expected an event to be reported matching:\n " \
|
|
204
|
+
"name: #{name.inspect}\n " \
|
|
205
|
+
"payload: #{payload.inspect}\n " \
|
|
206
|
+
"tags: #{tags.inspect}\n" \
|
|
207
|
+
"but none of the #{events.size} reported events matched:\n " \
|
|
208
|
+
"#{events.map(&:inspect).join("\n ")}"
|
|
209
|
+
flunk(message)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
assert(true)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveSupport
|
|
4
|
+
module Testing
|
|
5
|
+
module NotificationAssertions
|
|
6
|
+
# Assert a notification was emitted with a given +pattern+ and optional +payload+.
|
|
7
|
+
#
|
|
8
|
+
# You can assert that a notification was emitted by passing a pattern, which accepts
|
|
9
|
+
# either a string or regexp, an optional payload, and a block. While the block
|
|
10
|
+
# is executed, if a matching notification is emitted, the assertion will pass
|
|
11
|
+
# and the notification will be returned.
|
|
12
|
+
#
|
|
13
|
+
# Note that the payload is matched as a subset, meaning that the notification must
|
|
14
|
+
# contain at least the specified keys and values, but may contain additional ones.
|
|
15
|
+
#
|
|
16
|
+
# assert_notification("post.submitted", title: "Cool Post") do
|
|
17
|
+
# post.submit(title: "Cool Post", body: "Cool Body") # => emits matching notification
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# Using the returned notification, you can make more customized assertions.
|
|
21
|
+
#
|
|
22
|
+
# notification = assert_notification("post.submitted", title: "Cool Post") do
|
|
23
|
+
# ActiveSupport::Notifications.instrument("post.submitted", title: "Cool Post", body: Body.new("Cool Body"))
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# assert_instance_of(Body, notification.payload[:body])
|
|
27
|
+
#
|
|
28
|
+
def assert_notification(pattern, payload = nil, &block)
|
|
29
|
+
notifications = capture_notifications(pattern, &block)
|
|
30
|
+
assert_not_empty(notifications, "No #{pattern} notifications were found")
|
|
31
|
+
|
|
32
|
+
return notifications.first if payload.nil?
|
|
33
|
+
|
|
34
|
+
notification = notifications.find { |notification| notification.payload.slice(*payload.keys) == payload }
|
|
35
|
+
assert_not_nil(notification, "No #{pattern} notification with payload #{payload} was found")
|
|
36
|
+
|
|
37
|
+
notification
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Assert the number of notifications emitted with a given +pattern+.
|
|
41
|
+
#
|
|
42
|
+
# You can assert the number of notifications emitted by passing a pattern, which accepts
|
|
43
|
+
# either a string or regexp, a count, and a block. While the block is executed,
|
|
44
|
+
# the number of matching notifications emitted will be counted. After the block's
|
|
45
|
+
# execution completes, the assertion will pass if the count matches.
|
|
46
|
+
#
|
|
47
|
+
# assert_notifications_count("post.submitted", 1) do
|
|
48
|
+
# post.submit(title: "Cool Post") # => emits matching notification
|
|
49
|
+
# end
|
|
50
|
+
#
|
|
51
|
+
def assert_notifications_count(pattern, count, &block)
|
|
52
|
+
actual_count = capture_notifications(pattern, &block).count
|
|
53
|
+
assert_equal(count, actual_count, "Expected #{count} instead of #{actual_count} notifications for #{pattern}")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Assert no notifications were emitted for a given +pattern+.
|
|
57
|
+
#
|
|
58
|
+
# You can assert no notifications were emitted by passing a pattern, which accepts
|
|
59
|
+
# either a string or regexp, and a block. While the block is executed, if no
|
|
60
|
+
# matching notifications are emitted, the assertion will pass.
|
|
61
|
+
#
|
|
62
|
+
# assert_no_notifications("post.submitted") do
|
|
63
|
+
# post.destroy # => emits non-matching notification
|
|
64
|
+
# end
|
|
65
|
+
#
|
|
66
|
+
def assert_no_notifications(pattern = nil, &block)
|
|
67
|
+
notifications = capture_notifications(pattern, &block)
|
|
68
|
+
error_message = if pattern
|
|
69
|
+
"Expected no notifications for #{pattern} but found #{notifications.size}"
|
|
70
|
+
else
|
|
71
|
+
"Expected no notifications but found #{notifications.size}"
|
|
72
|
+
end
|
|
73
|
+
assert_empty(notifications, error_message)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Capture emitted notifications, optionally filtered by a +pattern+.
|
|
77
|
+
#
|
|
78
|
+
# You can capture emitted notifications, optionally filtered by a pattern,
|
|
79
|
+
# which accepts either a string or regexp, and a block.
|
|
80
|
+
#
|
|
81
|
+
# notifications = capture_notifications("post.submitted") do
|
|
82
|
+
# post.submit(title: "Cool Post") # => emits matching notification
|
|
83
|
+
# end
|
|
84
|
+
#
|
|
85
|
+
def capture_notifications(pattern = nil, &block)
|
|
86
|
+
notifications = []
|
|
87
|
+
ActiveSupport::Notifications.subscribed(->(n) { notifications << n }, pattern, &block)
|
|
88
|
+
notifications
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -14,7 +14,6 @@ module ActiveSupport
|
|
|
14
14
|
def initialize
|
|
15
15
|
@queue = Queue.new
|
|
16
16
|
@active_workers = Concurrent::Map.new
|
|
17
|
-
@worker_pids = Concurrent::Map.new
|
|
18
17
|
@in_flight = Concurrent::Map.new
|
|
19
18
|
end
|
|
20
19
|
|
|
@@ -41,24 +40,12 @@ module ActiveSupport
|
|
|
41
40
|
end
|
|
42
41
|
end
|
|
43
42
|
|
|
44
|
-
def start_worker(worker_id
|
|
43
|
+
def start_worker(worker_id)
|
|
45
44
|
@active_workers[worker_id] = true
|
|
46
|
-
@worker_pids[worker_id] = worker_pid
|
|
47
45
|
end
|
|
48
46
|
|
|
49
|
-
def stop_worker(worker_id
|
|
47
|
+
def stop_worker(worker_id)
|
|
50
48
|
@active_workers.delete(worker_id)
|
|
51
|
-
@worker_pids.delete(worker_id)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def remove_dead_workers(dead_pids)
|
|
55
|
-
dead_pids.each do |dead_pid|
|
|
56
|
-
worker_id = @worker_pids.key(dead_pid)
|
|
57
|
-
if worker_id
|
|
58
|
-
@active_workers.delete(worker_id)
|
|
59
|
-
@worker_pids.delete(worker_id)
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
49
|
end
|
|
63
50
|
|
|
64
51
|
def active_workers?
|
|
@@ -18,7 +18,7 @@ module ActiveSupport
|
|
|
18
18
|
DRb.stop_service
|
|
19
19
|
|
|
20
20
|
@queue = DRbObject.new_with_uri(@url)
|
|
21
|
-
@queue.start_worker(@id
|
|
21
|
+
@queue.start_worker(@id)
|
|
22
22
|
|
|
23
23
|
begin
|
|
24
24
|
after_fork
|
|
@@ -29,7 +29,7 @@ module ActiveSupport
|
|
|
29
29
|
set_process_title("(stopping)")
|
|
30
30
|
|
|
31
31
|
run_cleanup
|
|
32
|
-
@queue.stop_worker(@id
|
|
32
|
+
@queue.stop_worker(@id)
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
@@ -47,11 +47,7 @@ module ActiveSupport
|
|
|
47
47
|
set_process_title("#{klass}##{method}")
|
|
48
48
|
|
|
49
49
|
result = klass.with_info_handler reporter do
|
|
50
|
-
|
|
51
|
-
Minitest.run_one_method klass, method
|
|
52
|
-
else
|
|
53
|
-
klass.new(method).run
|
|
54
|
-
end
|
|
50
|
+
Minitest.run_one_method(klass, method)
|
|
55
51
|
end
|
|
56
52
|
|
|
57
53
|
safe_record(reporter, result)
|
|
@@ -82,6 +78,8 @@ module ActiveSupport
|
|
|
82
78
|
end
|
|
83
79
|
|
|
84
80
|
def after_fork
|
|
81
|
+
ActiveSupport::TestCase.parallel_worker_id = @number
|
|
82
|
+
|
|
85
83
|
Parallelization.after_fork_hooks.each do |cb|
|
|
86
84
|
cb.call(@number)
|
|
87
85
|
end
|