activesupport 7.1.6 → 8.1.1
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 +256 -1133
- data/README.rdoc +1 -1
- data/lib/active_support/array_inquirer.rb +1 -1
- data/lib/active_support/backtrace_cleaner.rb +81 -3
- data/lib/active_support/benchmark.rb +21 -0
- data/lib/active_support/benchmarkable.rb +3 -2
- data/lib/active_support/broadcast_logger.rb +65 -78
- data/lib/active_support/cache/file_store.rb +29 -14
- data/lib/active_support/cache/mem_cache_store.rb +42 -102
- data/lib/active_support/cache/memory_store.rb +11 -6
- data/lib/active_support/cache/null_store.rb +2 -2
- data/lib/active_support/cache/redis_cache_store.rb +58 -46
- data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
- data/lib/active_support/cache/strategy/local_cache.rb +72 -27
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
- data/lib/active_support/cache.rb +146 -86
- data/lib/active_support/callbacks.rb +102 -126
- data/lib/active_support/class_attribute.rb +33 -0
- data/lib/active_support/code_generator.rb +9 -0
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +8 -62
- data/lib/active_support/concurrency/share_lock.rb +0 -1
- data/lib/active_support/concurrency/thread_monitor.rb +55 -0
- data/lib/active_support/configurable.rb +34 -0
- data/lib/active_support/configuration_file.rb +15 -6
- data/lib/active_support/continuous_integration.rb +145 -0
- data/lib/active_support/core_ext/array/conversions.rb +3 -5
- data/lib/active_support/core_ext/array.rb +7 -7
- data/lib/active_support/core_ext/benchmark.rb +4 -14
- data/lib/active_support/core_ext/big_decimal.rb +1 -1
- data/lib/active_support/core_ext/class/attribute.rb +26 -19
- data/lib/active_support/core_ext/class/subclasses.rb +15 -35
- data/lib/active_support/core_ext/class.rb +2 -2
- data/lib/active_support/core_ext/date/blank.rb +4 -0
- data/lib/active_support/core_ext/date/conversions.rb +2 -2
- data/lib/active_support/core_ext/date.rb +5 -5
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -9
- data/lib/active_support/core_ext/date_time/blank.rb +4 -0
- data/lib/active_support/core_ext/date_time/compatibility.rb +3 -5
- data/lib/active_support/core_ext/date_time/conversions.rb +4 -6
- data/lib/active_support/core_ext/date_time.rb +5 -5
- data/lib/active_support/core_ext/digest/uuid.rb +6 -0
- data/lib/active_support/core_ext/digest.rb +1 -1
- data/lib/active_support/core_ext/enumerable.rb +25 -8
- data/lib/active_support/core_ext/erb/util.rb +10 -5
- data/lib/active_support/core_ext/file.rb +1 -1
- data/lib/active_support/core_ext/hash/deep_merge.rb +1 -0
- data/lib/active_support/core_ext/hash/except.rb +0 -12
- data/lib/active_support/core_ext/hash/keys.rb +4 -4
- data/lib/active_support/core_ext/hash.rb +8 -8
- data/lib/active_support/core_ext/integer.rb +3 -3
- data/lib/active_support/core_ext/kernel.rb +3 -3
- data/lib/active_support/core_ext/module/attr_internal.rb +16 -6
- data/lib/active_support/core_ext/module/delegation.rb +20 -163
- data/lib/active_support/core_ext/module/deprecation.rb +1 -4
- data/lib/active_support/core_ext/module/introspection.rb +3 -0
- data/lib/active_support/core_ext/module.rb +11 -11
- data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
- data/lib/active_support/core_ext/numeric.rb +3 -3
- data/lib/active_support/core_ext/object/blank.rb +45 -1
- data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
- data/lib/active_support/core_ext/object/json.rb +24 -11
- data/lib/active_support/core_ext/object/to_query.rb +7 -1
- data/lib/active_support/core_ext/object/try.rb +2 -2
- data/lib/active_support/core_ext/object/with.rb +5 -3
- data/lib/active_support/core_ext/object.rb +13 -13
- data/lib/active_support/core_ext/pathname/blank.rb +4 -0
- data/lib/active_support/core_ext/pathname.rb +2 -2
- data/lib/active_support/core_ext/range/overlap.rb +4 -4
- data/lib/active_support/core_ext/range/sole.rb +17 -0
- data/lib/active_support/core_ext/range.rb +4 -4
- data/lib/active_support/core_ext/securerandom.rb +4 -4
- data/lib/active_support/core_ext/string/conversions.rb +1 -1
- data/lib/active_support/core_ext/string/filters.rb +4 -4
- data/lib/active_support/core_ext/string/multibyte.rb +13 -4
- data/lib/active_support/core_ext/string/output_safety.rb +19 -19
- data/lib/active_support/core_ext/string.rb +13 -13
- data/lib/active_support/core_ext/symbol.rb +1 -1
- data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
- data/lib/active_support/core_ext/time/calculations.rb +25 -30
- data/lib/active_support/core_ext/time/compatibility.rb +2 -3
- data/lib/active_support/core_ext/time/conversions.rb +2 -2
- data/lib/active_support/core_ext/time/zones.rb +1 -1
- data/lib/active_support/core_ext/time.rb +5 -5
- data/lib/active_support/core_ext.rb +1 -2
- data/lib/active_support/current_attributes/test_helper.rb +2 -2
- data/lib/active_support/current_attributes.rb +58 -50
- data/lib/active_support/delegation.rb +200 -0
- data/lib/active_support/dependencies/autoload.rb +0 -12
- data/lib/active_support/dependencies/interlock.rb +11 -5
- data/lib/active_support/dependencies.rb +6 -2
- data/lib/active_support/deprecation/constant_accessor.rb +47 -26
- data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
- data/lib/active_support/deprecation/reporting.rb +5 -17
- data/lib/active_support/deprecation.rb +8 -5
- data/lib/active_support/descendants_tracker.rb +9 -87
- data/lib/active_support/duration/iso8601_parser.rb +2 -2
- data/lib/active_support/duration/iso8601_serializer.rb +1 -2
- data/lib/active_support/duration.rb +25 -16
- data/lib/active_support/editor.rb +70 -0
- data/lib/active_support/encrypted_configuration.rb +20 -2
- data/lib/active_support/encrypted_file.rb +1 -1
- data/lib/active_support/error_reporter.rb +121 -6
- data/lib/active_support/event_reporter/test_helper.rb +32 -0
- data/lib/active_support/event_reporter.rb +592 -0
- data/lib/active_support/evented_file_update_checker.rb +5 -3
- data/lib/active_support/execution_context.rb +64 -7
- data/lib/active_support/execution_wrapper.rb +1 -2
- data/lib/active_support/file_update_checker.rb +9 -7
- data/lib/active_support/fork_tracker.rb +2 -38
- data/lib/active_support/gem_version.rb +2 -2
- data/lib/active_support/gzip.rb +1 -0
- data/lib/active_support/hash_with_indifferent_access.rb +66 -45
- data/lib/active_support/html_safe_translation.rb +3 -0
- data/lib/active_support/i18n_railtie.rb +19 -11
- data/lib/active_support/inflector/inflections.rb +31 -15
- data/lib/active_support/inflector/transliterate.rb +6 -8
- data/lib/active_support/isolated_execution_state.rb +12 -17
- data/lib/active_support/json/decoding.rb +6 -4
- data/lib/active_support/json/encoding.rb +157 -21
- data/lib/active_support/lazy_load_hooks.rb +1 -1
- data/lib/active_support/log_subscriber.rb +2 -18
- data/lib/active_support/logger.rb +15 -2
- data/lib/active_support/logger_thread_safe_level.rb +4 -9
- data/lib/active_support/message_encryptors.rb +54 -2
- data/lib/active_support/message_pack/extensions.rb +20 -2
- data/lib/active_support/message_verifier.rb +21 -0
- data/lib/active_support/message_verifiers.rb +57 -3
- data/lib/active_support/messages/rotation_coordinator.rb +9 -0
- data/lib/active_support/messages/rotator.rb +10 -0
- data/lib/active_support/multibyte/chars.rb +14 -4
- data/lib/active_support/multibyte.rb +4 -0
- data/lib/active_support/notifications/fanout.rb +68 -50
- data/lib/active_support/notifications/instrumenter.rb +22 -19
- data/lib/active_support/notifications.rb +28 -27
- data/lib/active_support/number_helper/number_converter.rb +2 -2
- data/lib/active_support/number_helper.rb +22 -0
- data/lib/active_support/option_merger.rb +2 -2
- data/lib/active_support/ordered_options.rb +53 -15
- data/lib/active_support/railtie.rb +36 -20
- data/lib/active_support/string_inquirer.rb +1 -1
- data/lib/active_support/structured_event_subscriber.rb +99 -0
- data/lib/active_support/subscriber.rb +1 -5
- data/lib/active_support/syntax_error_proxy.rb +3 -0
- data/lib/active_support/tagged_logging.rb +5 -1
- data/lib/active_support/test_case.rb +63 -6
- data/lib/active_support/testing/assertions.rb +113 -27
- data/lib/active_support/testing/constant_stubbing.rb +30 -8
- data/lib/active_support/testing/deprecation.rb +5 -12
- data/lib/active_support/testing/error_reporter_assertions.rb +18 -1
- data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
- data/lib/active_support/testing/isolation.rb +19 -9
- data/lib/active_support/testing/method_call_assertions.rb +2 -16
- data/lib/active_support/testing/notification_assertions.rb +92 -0
- data/lib/active_support/testing/parallelization/server.rb +18 -2
- data/lib/active_support/testing/parallelization/worker.rb +4 -2
- data/lib/active_support/testing/parallelization.rb +25 -1
- data/lib/active_support/testing/tests_without_assertions.rb +19 -0
- data/lib/active_support/testing/time_helpers.rb +11 -6
- data/lib/active_support/time_with_zone.rb +39 -26
- data/lib/active_support/values/time_zone.rb +26 -17
- data/lib/active_support/xml_mini.rb +14 -4
- data/lib/active_support.rb +22 -9
- metadata +31 -17
- data/lib/active_support/core_ext/range/each.rb +0 -24
- data/lib/active_support/deprecation/instance_delegator.rb +0 -65
- data/lib/active_support/proxy_object.rb +0 -17
- data/lib/active_support/ruby_features.rb +0 -7
- data/lib/active_support/testing/strict_warnings.rb +0 -39
|
@@ -19,7 +19,7 @@ module ActiveSupport
|
|
|
19
19
|
#
|
|
20
20
|
# assert_not foo, 'foo should be false'
|
|
21
21
|
def assert_not(object, message = nil)
|
|
22
|
-
message ||= "Expected #{mu_pp(object)} to be nil or false"
|
|
22
|
+
message ||= -> { "Expected #{mu_pp(object)} to be nil or false" }
|
|
23
23
|
assert !object, message
|
|
24
24
|
end
|
|
25
25
|
|
|
@@ -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
|
|
@@ -118,9 +122,13 @@ module ActiveSupport
|
|
|
118
122
|
|
|
119
123
|
expressions.zip(exps, before) do |(code, diff), exp, before_value|
|
|
120
124
|
actual = exp.call
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
rich_message = -> do
|
|
126
|
+
code_string = code.respond_to?(:call) ? _callable_to_source_string(code) : code
|
|
127
|
+
error = "`#{code_string}` didn't change by #{diff}, but by #{actual - before_value}"
|
|
128
|
+
error = "#{message}.\n#{error}" if message
|
|
129
|
+
error
|
|
130
|
+
end
|
|
131
|
+
assert_equal(before_value + diff, actual, rich_message)
|
|
124
132
|
end
|
|
125
133
|
|
|
126
134
|
retval
|
|
@@ -177,12 +185,24 @@ module ActiveSupport
|
|
|
177
185
|
#
|
|
178
186
|
# The keyword arguments +:from+ and +:to+ can be given to specify the
|
|
179
187
|
# expected initial value and the expected value after the block was
|
|
180
|
-
# executed.
|
|
188
|
+
# executed. The comparison is done using case equality (===), which means
|
|
189
|
+
# you can specify patterns or classes:
|
|
181
190
|
#
|
|
191
|
+
# # Exact value match
|
|
182
192
|
# assert_changes :@object, from: nil, to: :foo do
|
|
183
193
|
# @object = :foo
|
|
184
194
|
# end
|
|
185
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
|
+
#
|
|
186
206
|
# An error message can be specified.
|
|
187
207
|
#
|
|
188
208
|
# assert_changes -> { Status.all_good? }, 'Expected the status to be bad' do
|
|
@@ -195,22 +215,32 @@ module ActiveSupport
|
|
|
195
215
|
retval = _assert_nothing_raised_or_warn("assert_changes", &block)
|
|
196
216
|
|
|
197
217
|
unless from == UNTRACKED
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
218
|
+
rich_message = -> do
|
|
219
|
+
error = "Expected change from #{from.inspect}, got #{before.inspect}"
|
|
220
|
+
error = "#{message}.\n#{error}" if message
|
|
221
|
+
error
|
|
222
|
+
end
|
|
223
|
+
assert from === before, rich_message
|
|
201
224
|
end
|
|
202
225
|
|
|
203
226
|
after = exp.call
|
|
204
227
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
228
|
+
rich_message = -> do
|
|
229
|
+
code_string = expression.respond_to?(:call) ? _callable_to_source_string(expression) : expression
|
|
230
|
+
error = "`#{code_string}` didn't change"
|
|
231
|
+
error = "#{error}. It was already #{to.inspect}" if before == to
|
|
232
|
+
error = "#{message}.\n#{error}" if message
|
|
233
|
+
error
|
|
234
|
+
end
|
|
235
|
+
refute_equal before, after, rich_message
|
|
209
236
|
|
|
210
237
|
unless to == UNTRACKED
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
238
|
+
rich_message = -> do
|
|
239
|
+
error = "Expected change to #{to.inspect}, got #{after.inspect}\n"
|
|
240
|
+
error = "#{message}.\n#{error}" if message
|
|
241
|
+
error
|
|
242
|
+
end
|
|
243
|
+
assert to === after, rich_message
|
|
214
244
|
end
|
|
215
245
|
|
|
216
246
|
retval
|
|
@@ -224,12 +254,24 @@ module ActiveSupport
|
|
|
224
254
|
# end
|
|
225
255
|
#
|
|
226
256
|
# Provide the optional keyword argument +:from+ to specify the expected
|
|
227
|
-
# initial value.
|
|
257
|
+
# initial value. The comparison is done using case equality (===), which means
|
|
258
|
+
# you can specify patterns or classes:
|
|
228
259
|
#
|
|
260
|
+
# # Exact value match
|
|
229
261
|
# assert_no_changes -> { Status.all_good? }, from: true do
|
|
230
262
|
# post :create, params: { status: { ok: true } }
|
|
231
263
|
# end
|
|
232
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
|
+
#
|
|
233
275
|
# An error message can be specified.
|
|
234
276
|
#
|
|
235
277
|
# assert_no_changes -> { Status.all_good? }, 'Expected the status to be good' do
|
|
@@ -242,20 +284,27 @@ module ActiveSupport
|
|
|
242
284
|
retval = _assert_nothing_raised_or_warn("assert_no_changes", &block)
|
|
243
285
|
|
|
244
286
|
unless from == UNTRACKED
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
287
|
+
rich_message = -> do
|
|
288
|
+
error = "Expected initial value of #{from.inspect}, got #{before.inspect}"
|
|
289
|
+
error = "#{message}.\n#{error}" if message
|
|
290
|
+
error
|
|
291
|
+
end
|
|
292
|
+
assert from === before, rich_message
|
|
248
293
|
end
|
|
249
294
|
|
|
250
295
|
after = exp.call
|
|
251
296
|
|
|
252
|
-
|
|
253
|
-
|
|
297
|
+
rich_message = -> do
|
|
298
|
+
code_string = expression.respond_to?(:call) ? _callable_to_source_string(expression) : expression
|
|
299
|
+
error = "`#{code_string}` changed"
|
|
300
|
+
error = "#{message}.\n#{error}" if message
|
|
301
|
+
error
|
|
302
|
+
end
|
|
254
303
|
|
|
255
304
|
if before.nil?
|
|
256
|
-
assert_nil after,
|
|
305
|
+
assert_nil after, rich_message
|
|
257
306
|
else
|
|
258
|
-
assert_equal before, after,
|
|
307
|
+
assert_equal before, after, rich_message
|
|
259
308
|
end
|
|
260
309
|
|
|
261
310
|
retval
|
|
@@ -276,6 +325,43 @@ module ActiveSupport
|
|
|
276
325
|
|
|
277
326
|
raise
|
|
278
327
|
end
|
|
328
|
+
|
|
329
|
+
def _callable_to_source_string(callable)
|
|
330
|
+
if defined?(RubyVM::InstructionSequence) && callable.is_a?(Proc)
|
|
331
|
+
iseq = RubyVM::InstructionSequence.of(callable)
|
|
332
|
+
source =
|
|
333
|
+
if iseq.script_lines
|
|
334
|
+
iseq.script_lines.join("\n")
|
|
335
|
+
elsif File.readable?(iseq.absolute_path)
|
|
336
|
+
File.read(iseq.absolute_path)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
return callable unless source
|
|
340
|
+
|
|
341
|
+
location = iseq.to_a[4][:code_location]
|
|
342
|
+
return callable unless location
|
|
343
|
+
|
|
344
|
+
lines = source.lines[(location[0] - 1)..(location[2] - 1)]
|
|
345
|
+
lines[-1] = lines[-1].byteslice(...location[3])
|
|
346
|
+
lines[0] = lines[0].byteslice(location[1]...)
|
|
347
|
+
source = lines.join.strip
|
|
348
|
+
|
|
349
|
+
# We ignore procs defined with do/end as they are likely multi-line anyway.
|
|
350
|
+
if source.start_with?("{")
|
|
351
|
+
source.delete_suffix!("}")
|
|
352
|
+
source.delete_prefix!("{")
|
|
353
|
+
source.strip!
|
|
354
|
+
# It won't read nice if the callable contains multiple
|
|
355
|
+
# lines, and it should be a rare occurrence anyway.
|
|
356
|
+
# Same if it takes arguments.
|
|
357
|
+
if !source.include?("\n") && !source.start_with?("|")
|
|
358
|
+
return source
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
callable
|
|
364
|
+
end
|
|
279
365
|
end
|
|
280
366
|
end
|
|
281
367
|
end
|
|
@@ -15,17 +15,39 @@ module ActiveSupport
|
|
|
15
15
|
# Using this method rather than forcing <tt>World::List::Import::LARGE_IMPORT_THRESHOLD = 5000</tt> prevents
|
|
16
16
|
# warnings from being thrown, and ensures that the old value is returned after the test has completed.
|
|
17
17
|
#
|
|
18
|
+
# If the constant doesn't already exists, but you need it set for the duration of the block
|
|
19
|
+
# you can do so by passing `exists: false`.
|
|
20
|
+
#
|
|
21
|
+
# stub_const(object, :SOME_CONST, 1, exists: false) do
|
|
22
|
+
# assert_equal 1, SOME_CONST
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
18
25
|
# Note: Stubbing a const will stub it across all threads. So if you have concurrent threads
|
|
19
26
|
# (like separate test suites running in parallel) that all depend on the same constant, it's possible
|
|
20
27
|
# divergent stubbing will trample on each other.
|
|
21
|
-
def stub_const(mod, constant, new_value)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
def stub_const(mod, constant, new_value, exists: true)
|
|
29
|
+
if exists
|
|
30
|
+
begin
|
|
31
|
+
old_value = mod.const_get(constant, false)
|
|
32
|
+
mod.send(:remove_const, constant)
|
|
33
|
+
mod.const_set(constant, new_value)
|
|
34
|
+
yield
|
|
35
|
+
ensure
|
|
36
|
+
mod.send(:remove_const, constant)
|
|
37
|
+
mod.const_set(constant, old_value)
|
|
38
|
+
end
|
|
39
|
+
else
|
|
40
|
+
if mod.const_defined?(constant)
|
|
41
|
+
raise NameError, "already defined constant #{constant} in #{mod.name}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
begin
|
|
45
|
+
mod.const_set(constant, new_value)
|
|
46
|
+
yield
|
|
47
|
+
ensure
|
|
48
|
+
mod.send(:remove_const, constant)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
29
51
|
end
|
|
30
52
|
end
|
|
31
53
|
end
|
|
@@ -29,10 +29,11 @@ module ActiveSupport
|
|
|
29
29
|
# end
|
|
30
30
|
def assert_deprecated(match = nil, deprecator = nil, &block)
|
|
31
31
|
match, deprecator = nil, match if match.is_a?(ActiveSupport::Deprecation)
|
|
32
|
+
|
|
32
33
|
unless deprecator
|
|
33
|
-
|
|
34
|
-
deprecator = ActiveSupport::Deprecation._instance
|
|
34
|
+
raise ArgumentError, "No deprecator given"
|
|
35
35
|
end
|
|
36
|
+
|
|
36
37
|
result, warnings = collect_deprecations(deprecator, &block)
|
|
37
38
|
assert !warnings.empty?, "Expected a deprecation warning within the block but received none"
|
|
38
39
|
if match
|
|
@@ -51,11 +52,7 @@ module ActiveSupport
|
|
|
51
52
|
# assert_not_deprecated(ActiveSupport::Deprecation.new) do
|
|
52
53
|
# CustomDeprecator.warn "message" # passes assertion, different deprecator
|
|
53
54
|
# end
|
|
54
|
-
def assert_not_deprecated(deprecator
|
|
55
|
-
unless deprecator
|
|
56
|
-
ActiveSupport.deprecator.warn("assert_not_deprecated without a deprecator is deprecated")
|
|
57
|
-
deprecator = ActiveSupport::Deprecation._instance
|
|
58
|
-
end
|
|
55
|
+
def assert_not_deprecated(deprecator, &block)
|
|
59
56
|
result, deprecations = collect_deprecations(deprecator, &block)
|
|
60
57
|
assert deprecations.empty?, "Expected no deprecation warning within the block but received #{deprecations.size}: \n #{deprecations * "\n "}"
|
|
61
58
|
result
|
|
@@ -69,11 +66,7 @@ module ActiveSupport
|
|
|
69
66
|
# ActiveSupport::Deprecation.new.warn "other message"
|
|
70
67
|
# :result
|
|
71
68
|
# end # => [:result, ["message"]]
|
|
72
|
-
def collect_deprecations(deprecator
|
|
73
|
-
unless deprecator
|
|
74
|
-
ActiveSupport.deprecator.warn("collect_deprecations without a deprecator is deprecated")
|
|
75
|
-
deprecator = ActiveSupport::Deprecation._instance
|
|
76
|
-
end
|
|
69
|
+
def collect_deprecations(deprecator)
|
|
77
70
|
old_behavior = deprecator.behavior
|
|
78
71
|
deprecations = []
|
|
79
72
|
deprecator.behavior = Proc.new do |message, callstack|
|
|
@@ -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,227 @@
|
|
|
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.inspect}\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
|
+
|
|
216
|
+
# Allows debug events to be reported to +Rails.event+ for the duration of a given block.
|
|
217
|
+
#
|
|
218
|
+
# with_debug_event_reporting do
|
|
219
|
+
# service_that_reports_debug_events.perform
|
|
220
|
+
# end
|
|
221
|
+
#
|
|
222
|
+
def with_debug_event_reporting(&block)
|
|
223
|
+
ActiveSupport.event_reporter.with_debug(&block)
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "active_support/testing/parallelize_executor"
|
|
4
|
+
|
|
3
5
|
module ActiveSupport
|
|
4
6
|
module Testing
|
|
5
7
|
module Isolation
|
|
6
|
-
|
|
8
|
+
SubprocessCrashed = Class.new(StandardError)
|
|
7
9
|
|
|
8
10
|
def self.included(klass) # :nodoc:
|
|
9
11
|
klass.class_eval do
|
|
10
|
-
parallelize_me!
|
|
12
|
+
parallelize_me! unless Minitest.parallel_executor.is_a?(ActiveSupport::Testing::ParallelizeExecutor)
|
|
11
13
|
end
|
|
12
14
|
end
|
|
13
15
|
|
|
@@ -16,10 +18,17 @@ module ActiveSupport
|
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
def run
|
|
19
|
-
serialized = run_in_isolation do
|
|
21
|
+
status, serialized = run_in_isolation do
|
|
20
22
|
super
|
|
21
23
|
end
|
|
22
24
|
|
|
25
|
+
unless status&.success?
|
|
26
|
+
error = SubprocessCrashed.new("Subprocess exited with an error: #{status.inspect}\noutput: #{serialized.inspect}")
|
|
27
|
+
error.set_backtrace(caller)
|
|
28
|
+
self.failures << Minitest::UnexpectedError.new(error)
|
|
29
|
+
return defined?(Minitest::Result) ? Minitest::Result.from(self) : dup
|
|
30
|
+
end
|
|
31
|
+
|
|
23
32
|
Marshal.load(serialized)
|
|
24
33
|
end
|
|
25
34
|
|
|
@@ -50,13 +59,13 @@ module ActiveSupport
|
|
|
50
59
|
end
|
|
51
60
|
|
|
52
61
|
write.puts [result].pack("m")
|
|
53
|
-
exit!
|
|
62
|
+
exit!(0)
|
|
54
63
|
end
|
|
55
64
|
|
|
56
65
|
write.close
|
|
57
66
|
result = read.read
|
|
58
|
-
Process.wait2(pid)
|
|
59
|
-
result.unpack1("m")
|
|
67
|
+
_, status = Process.wait2(pid)
|
|
68
|
+
return status, result.unpack1("m")
|
|
60
69
|
end
|
|
61
70
|
end
|
|
62
71
|
end
|
|
@@ -75,7 +84,7 @@ module ActiveSupport
|
|
|
75
84
|
File.open(ENV["ISOLATION_OUTPUT"], "w") do |file|
|
|
76
85
|
file.puts [Marshal.dump(test_result)].pack("m")
|
|
77
86
|
end
|
|
78
|
-
exit!
|
|
87
|
+
exit!(0)
|
|
79
88
|
else
|
|
80
89
|
Tempfile.open("isolation") do |tmpfile|
|
|
81
90
|
env = {
|
|
@@ -93,13 +102,14 @@ module ActiveSupport
|
|
|
93
102
|
|
|
94
103
|
child = IO.popen([env, Gem.ruby, *load_path_args, $0, *ORIG_ARGV, test_opts])
|
|
95
104
|
|
|
105
|
+
status = nil
|
|
96
106
|
begin
|
|
97
|
-
Process.
|
|
107
|
+
_, status = Process.wait2(child.pid)
|
|
98
108
|
rescue Errno::ECHILD # The child process may exit before we wait
|
|
99
109
|
nil
|
|
100
110
|
end
|
|
101
111
|
|
|
102
|
-
return tmpfile.read.unpack1("m")
|
|
112
|
+
return status, tmpfile.read.unpack1("m")
|
|
103
113
|
end
|
|
104
114
|
end
|
|
105
115
|
end
|
|
@@ -30,22 +30,8 @@ module ActiveSupport
|
|
|
30
30
|
assert_called(object, method_name, message, times: 0, &block)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
# the Minitest 5.16 / Ruby 3.0 kwargs transition. It can go away
|
|
36
|
-
# when we drop support for Ruby 2.7.
|
|
37
|
-
if Minitest::Mock.instance_method(:expect).parameters.map(&:first).include?(:keyrest)
|
|
38
|
-
def expect_called_with(mock, args, returns: false, **kwargs)
|
|
39
|
-
mock.expect(:call, returns, args, **kwargs)
|
|
40
|
-
end
|
|
41
|
-
else
|
|
42
|
-
def expect_called_with(mock, args, returns: false, **kwargs)
|
|
43
|
-
if !kwargs.empty?
|
|
44
|
-
mock.expect(:call, returns, [*args, kwargs])
|
|
45
|
-
else
|
|
46
|
-
mock.expect(:call, returns, args)
|
|
47
|
-
end
|
|
48
|
-
end
|
|
33
|
+
def expect_called_with(mock, args, returns: false, **kwargs)
|
|
34
|
+
mock.expect(:call, returns, args, **kwargs)
|
|
49
35
|
end
|
|
50
36
|
|
|
51
37
|
def assert_called_on_instance_of(klass, method_name, message = nil, times: 1, returns: nil)
|