activesupport 7.2.3 → 8.0.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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -385
  3. data/README.rdoc +1 -1
  4. data/lib/active_support/backtrace_cleaner.rb +2 -2
  5. data/lib/active_support/benchmark.rb +21 -0
  6. data/lib/active_support/benchmarkable.rb +3 -2
  7. data/lib/active_support/broadcast_logger.rb +74 -61
  8. data/lib/active_support/cache/file_store.rb +14 -4
  9. data/lib/active_support/cache/mem_cache_store.rb +15 -13
  10. data/lib/active_support/cache/memory_store.rb +9 -5
  11. data/lib/active_support/cache/null_store.rb +2 -2
  12. data/lib/active_support/cache/redis_cache_store.rb +6 -3
  13. data/lib/active_support/cache/strategy/local_cache.rb +20 -56
  14. data/lib/active_support/cache.rb +17 -12
  15. data/lib/active_support/callbacks.rb +3 -5
  16. data/lib/active_support/class_attribute.rb +26 -0
  17. data/lib/active_support/code_generator.rb +9 -0
  18. data/lib/active_support/concurrency/share_lock.rb +0 -1
  19. data/lib/active_support/configuration_file.rb +15 -6
  20. data/lib/active_support/core_ext/benchmark.rb +6 -10
  21. data/lib/active_support/core_ext/class/attribute.rb +12 -21
  22. data/lib/active_support/core_ext/date/conversions.rb +2 -0
  23. data/lib/active_support/core_ext/date_and_time/compatibility.rb +2 -2
  24. data/lib/active_support/core_ext/date_time/conversions.rb +2 -4
  25. data/lib/active_support/core_ext/enumerable.rb +13 -20
  26. data/lib/active_support/core_ext/erb/util.rb +2 -2
  27. data/lib/active_support/core_ext/hash/except.rb +0 -12
  28. data/lib/active_support/core_ext/module/introspection.rb +0 -3
  29. data/lib/active_support/core_ext/object/json.rb +18 -14
  30. data/lib/active_support/core_ext/object/try.rb +2 -2
  31. data/lib/active_support/core_ext/range.rb +0 -1
  32. data/lib/active_support/core_ext/securerandom.rb +8 -24
  33. data/lib/active_support/core_ext/string/filters.rb +3 -3
  34. data/lib/active_support/core_ext/string/multibyte.rb +3 -3
  35. data/lib/active_support/core_ext/time/calculations.rb +14 -2
  36. data/lib/active_support/core_ext/time/compatibility.rb +1 -9
  37. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  38. data/lib/active_support/core_ext/time/zones.rb +1 -1
  39. data/lib/active_support/current_attributes.rb +7 -14
  40. data/lib/active_support/deprecation.rb +1 -1
  41. data/lib/active_support/encrypted_configuration.rb +20 -2
  42. data/lib/active_support/error_reporter.rb +27 -6
  43. data/lib/active_support/execution_wrapper.rb +1 -1
  44. data/lib/active_support/file_update_checker.rb +1 -1
  45. data/lib/active_support/gem_version.rb +4 -4
  46. data/lib/active_support/hash_with_indifferent_access.rb +31 -31
  47. data/lib/active_support/i18n_railtie.rb +19 -10
  48. data/lib/active_support/isolated_execution_state.rb +0 -1
  49. data/lib/active_support/json/decoding.rb +1 -1
  50. data/lib/active_support/json/encoding.rb +7 -25
  51. data/lib/active_support/lazy_load_hooks.rb +1 -1
  52. data/lib/active_support/message_encryptors.rb +2 -2
  53. data/lib/active_support/message_verifier.rb +0 -9
  54. data/lib/active_support/message_verifiers.rb +3 -5
  55. data/lib/active_support/messages/rotator.rb +0 -5
  56. data/lib/active_support/multibyte/chars.rb +1 -4
  57. data/lib/active_support/number_helper.rb +22 -0
  58. data/lib/active_support/railtie.rb +4 -0
  59. data/lib/active_support/tagged_logging.rb +5 -0
  60. data/lib/active_support/testing/assertions.rb +72 -21
  61. data/lib/active_support/testing/isolation.rb +0 -2
  62. data/lib/active_support/testing/parallelization/server.rb +2 -15
  63. data/lib/active_support/testing/parallelization/worker.rb +2 -2
  64. data/lib/active_support/testing/parallelization.rb +1 -12
  65. data/lib/active_support/testing/strict_warnings.rb +43 -0
  66. data/lib/active_support/testing/time_helpers.rb +2 -1
  67. data/lib/active_support/time_with_zone.rb +22 -13
  68. data/lib/active_support/values/time_zone.rb +17 -15
  69. data/lib/active_support/xml_mini.rb +0 -2
  70. data/lib/active_support.rb +10 -2
  71. metadata +27 -8
  72. data/lib/active_support/core_ext/range/sole.rb +0 -17
@@ -120,7 +120,7 @@ module ActiveSupport
120
120
  # healthy to consider this edge case because with mtimes in the future
121
121
  # reloading is not triggered.
122
122
  def max_mtime(paths)
123
- time_now = Time.at(0, Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond), :nanosecond)
123
+ time_now = Time.now
124
124
  max_mtime = nil
125
125
 
126
126
  # Time comparisons are performed with #compare_without_coercion because
@@ -7,10 +7,10 @@ module ActiveSupport
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 7
11
- MINOR = 2
12
- TINY = 3
13
- PRE = nil
10
+ MAJOR = 8
11
+ MINOR = 0
12
+ TINY = 0
13
+ PRE = "beta1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -262,7 +262,9 @@ module ActiveSupport
262
262
  # hash[:a][:c] # => "c"
263
263
  # dup[:a][:c] # => "c"
264
264
  def dup
265
- copy_defaults(self.class.new(self))
265
+ self.class.new(self).tap do |new_hash|
266
+ set_defaults(new_hash)
267
+ end
266
268
  end
267
269
 
268
270
  # This method has the same semantics of +update+, except it does not
@@ -340,26 +342,21 @@ module ActiveSupport
340
342
  NOT_GIVEN = Object.new # :nodoc:
341
343
 
342
344
  def transform_keys(hash = NOT_GIVEN, &block)
343
- if NOT_GIVEN.equal?(hash)
344
- if block_given?
345
- self.class.new(super(&block))
346
- else
347
- to_enum(:transform_keys)
348
- end
349
- else
350
- self.class.new(super)
351
- end
345
+ return to_enum(:transform_keys) if NOT_GIVEN.equal?(hash) && !block_given?
346
+ dup.tap { |h| h.transform_keys!(hash, &block) }
352
347
  end
353
348
 
354
349
  def transform_keys!(hash = NOT_GIVEN, &block)
355
- if NOT_GIVEN.equal?(hash)
356
- if block_given?
357
- replace(copy_defaults(transform_keys(&block)))
358
- else
359
- return to_enum(:transform_keys!)
360
- end
350
+ return to_enum(:transform_keys!) if NOT_GIVEN.equal?(hash) && !block_given?
351
+
352
+ if hash.nil?
353
+ super
354
+ elsif NOT_GIVEN.equal?(hash)
355
+ keys.each { |key| self[yield(key)] = delete(key) }
356
+ elsif block_given?
357
+ keys.each { |key| self[hash[key] || yield(key)] = delete(key) }
361
358
  else
362
- replace(copy_defaults(transform_keys(hash, &block)))
359
+ keys.each { |key| self[hash[key] || key] = delete(key) }
363
360
  end
364
361
 
365
362
  self
@@ -381,13 +378,10 @@ module ActiveSupport
381
378
 
382
379
  # Convert to a regular hash with string keys.
383
380
  def to_hash
384
- _new_hash = Hash.new
385
- copy_defaults(_new_hash)
386
-
387
- each do |key, value|
388
- _new_hash[key] = convert_value(value, conversion: :to_hash)
389
- end
390
- _new_hash
381
+ copy = Hash[self]
382
+ copy.transform_values! { |v| convert_value_to_hash(v) }
383
+ set_defaults(copy)
384
+ copy
391
385
  end
392
386
 
393
387
  def to_proc
@@ -401,11 +395,7 @@ module ActiveSupport
401
395
 
402
396
  def convert_value(value, conversion: nil)
403
397
  if value.is_a? Hash
404
- if conversion == :to_hash
405
- value.to_hash
406
- else
407
- value.nested_under_indifferent_access
408
- end
398
+ value.nested_under_indifferent_access
409
399
  elsif value.is_a?(Array)
410
400
  if conversion != :assignment || value.frozen?
411
401
  value = value.dup
@@ -416,13 +406,23 @@ module ActiveSupport
416
406
  end
417
407
  end
418
408
 
419
- def copy_defaults(target)
409
+ def convert_value_to_hash(value)
410
+ if value.is_a? Hash
411
+ value.to_hash
412
+ elsif value.is_a?(Array)
413
+ value.map { |e| convert_value_to_hash(e) }
414
+ else
415
+ value
416
+ end
417
+ end
418
+
419
+
420
+ def set_defaults(target)
420
421
  if default_proc
421
422
  target.default_proc = default_proc.dup
422
423
  else
423
424
  target.default = default
424
425
  end
425
- target
426
426
  end
427
427
 
428
428
  def update_with_single_argument(other_hash, block)
@@ -14,15 +14,18 @@ module I18n
14
14
 
15
15
  config.eager_load_namespaces << I18n
16
16
 
17
- # Set the i18n configuration after initialization since a lot of
18
- # configuration is still usually done in application initializers.
19
- config.after_initialize do |app|
17
+ # Make sure i18n is ready before eager loading, in case any eager loaded
18
+ # code needs it.
19
+ config.before_eager_load do |app|
20
20
  I18n::Railtie.initialize_i18n(app)
21
21
  end
22
22
 
23
- # Trigger i18n config before any eager loading has happened
24
- # so it's ready if any classes require it when eager loaded.
25
- config.before_eager_load do |app|
23
+ # i18n initialization needs to run after application initialization, since
24
+ # initializers may configure i18n.
25
+ #
26
+ # If the application eager loaded, this was done on before_eager_load. The
27
+ # hook is still OK, though, because initialize_i18n is idempotent.
28
+ config.after_initialize do |app|
26
29
  I18n::Railtie.initialize_i18n(app)
27
30
  end
28
31
 
@@ -49,7 +52,8 @@ module I18n
49
52
  when :load_path
50
53
  I18n.load_path += value
51
54
  when :raise_on_missing_translations
52
- setup_raise_on_missing_translations_config(app)
55
+ strict = value == :strict
56
+ setup_raise_on_missing_translations_config(app, strict)
53
57
  else
54
58
  I18n.public_send("#{setting}=", value)
55
59
  end
@@ -62,8 +66,9 @@ module I18n
62
66
 
63
67
  if app.config.reloading_enabled?
64
68
  directories = watched_dirs_with_extensions(reloadable_paths)
65
- reloader = app.config.file_watcher.new(I18n.load_path.dup, directories) do
66
- I18n.load_path.keep_if { |p| File.exist?(p) }
69
+ root_load_paths = I18n.load_path.select { |path| path.start_with?(Rails.root.to_s) }
70
+ reloader = app.config.file_watcher.new(root_load_paths, directories) do
71
+ I18n.load_path.delete_if { |p| p.start_with?(Rails.root.to_s) && !File.exist?(p) }
67
72
  I18n.load_path |= reloadable_paths.flat_map(&:existent)
68
73
  end
69
74
 
@@ -77,11 +82,15 @@ module I18n
77
82
  @i18n_inited = true
78
83
  end
79
84
 
80
- def self.setup_raise_on_missing_translations_config(app)
85
+ def self.setup_raise_on_missing_translations_config(app, strict)
81
86
  ActiveSupport.on_load(:action_view) do
82
87
  ActionView::Helpers::TranslationHelper.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations
83
88
  end
84
89
 
90
+ ActiveSupport.on_load(:active_model_translation) do
91
+ ActiveModel::Translation.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations if strict
92
+ end
93
+
85
94
  if app.config.i18n.raise_on_missing_translations &&
86
95
  I18n.exception_handler.is_a?(I18n::ExceptionHandler) # Only override the i18n gem's default exception handler.
87
96
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "fiber"
4
3
 
5
4
  module ActiveSupport
6
5
  module IsolatedExecutionState # :nodoc:
@@ -18,7 +18,7 @@ module ActiveSupport
18
18
  # See http://www.json.org for more info.
19
19
  #
20
20
  # ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}")
21
- # # => {"team" => "rails", "players" => "36"}
21
+ # => {"team" => "rails", "players" => "36"}
22
22
  def decode(json)
23
23
  data = ::JSON.parse(json, quirks_mode: true)
24
24
 
@@ -13,30 +13,12 @@ module ActiveSupport
13
13
  end
14
14
 
15
15
  module JSON
16
+ # Dumps objects in JSON (JavaScript Object Notation).
17
+ # See http://www.json.org for more info.
18
+ #
19
+ # ActiveSupport::JSON.encode({ team: 'rails', players: '36' })
20
+ # # => "{\"team\":\"rails\",\"players\":\"36\"}"
16
21
  class << self
17
- # Dumps objects in JSON (JavaScript Object Notation).
18
- # See http://www.json.org for more info.
19
- #
20
- # ActiveSupport::JSON.encode({ team: 'rails', players: '36' })
21
- # # => "{\"team\":\"rails\",\"players\":\"36\"}"
22
- #
23
- # Generates JSON that is safe to include in JavaScript as it escapes
24
- # U+2028 (Line Separator) and U+2029 (Paragraph Separator):
25
- #
26
- # ActiveSupport::JSON.encode({ key: "\u2028" })
27
- # # => "{\"key\":\"\\u2028\"}"
28
- #
29
- # By default, it also generates JSON that is safe to include in HTML, as
30
- # it escapes <tt><</tt>, <tt>></tt>, and <tt>&</tt>:
31
- #
32
- # ActiveSupport::JSON.encode({ key: "<>&" })
33
- # # => "{\"key\":\"\\u003c\\u003e\\u0026\"}"
34
- #
35
- # This can be changed with the +escape_html_entities+ option, or the
36
- # global escape_html_entities_in_json configuration option.
37
- #
38
- # ActiveSupport::JSON.encode({ key: "<>&" }, escape_html_entities: false)
39
- # # => "{\"key\":\"<>&\"}"
40
22
  def encode(value, options = nil)
41
23
  Encoding.json_encoder.new(options).encode(value)
42
24
  end
@@ -54,14 +36,14 @@ module ActiveSupport
54
36
  # Encode the given object into a JSON string
55
37
  def encode(value)
56
38
  unless options.empty?
57
- value = value.as_json(options.dup)
39
+ value = value.as_json(options.dup.freeze)
58
40
  end
59
41
  json = stringify(jsonify(value))
60
42
 
61
43
  # Rails does more escaping than the JSON gem natively does (we
62
44
  # escape \u2028 and \u2029 and optionally >, <, & to work around
63
45
  # certain browser problems).
64
- if Encoding.escape_html_entities_in_json
46
+ if @options.fetch(:escape_html_entities, Encoding.escape_html_entities_in_json)
65
47
  json.gsub!(">", '\u003e')
66
48
  json.gsub!("<", '\u003c')
67
49
  json.gsub!("&", '\u0026')
@@ -53,7 +53,7 @@ module ActiveSupport
53
53
  # loaded. If the component has already loaded, the block is executed
54
54
  # immediately.
55
55
  #
56
- # ==== Options
56
+ # Options:
57
57
  #
58
58
  # * <tt>:yield</tt> - Yields the object that run_load_hooks to +block+.
59
59
  # * <tt>:run_once</tt> - Given +block+ will run only once.
@@ -28,8 +28,8 @@ module ActiveSupport
28
28
  # <tt>transitional = false</tt>.
29
29
 
30
30
  ##
31
- # :singleton-method: new
32
- # :call-seq: new(&secret_generator)
31
+ # :method: initialize
32
+ # :call-seq: initialize(&secret_generator)
33
33
  #
34
34
  # Initializes a new instance. +secret_generator+ must accept a salt and a
35
35
  # +secret_length+ kwarg, and return a suitable secret (string) or secrets
@@ -154,8 +154,6 @@ module ActiveSupport
154
154
  # not URL-safe. In other words, they can contain "+" and "/". If you want to
155
155
  # generate URL-safe strings (in compliance with "Base 64 Encoding with URL
156
156
  # and Filename Safe Alphabet" in RFC 4648), you can pass +true+.
157
- # Note that MessageVerifier will always accept both URL-safe and URL-unsafe
158
- # encoded messages, to allow a smooth transition between the two settings.
159
157
  #
160
158
  # [+:force_legacy_metadata_serializer+]
161
159
  # Whether to use the legacy metadata serializer, which serializes the
@@ -320,13 +318,6 @@ module ActiveSupport
320
318
  end
321
319
 
322
320
  private
323
- def decode(encoded, url_safe: @url_safe)
324
- catch :invalid_message_format do
325
- return super
326
- end
327
- super(encoded, url_safe: !url_safe)
328
- end
329
-
330
321
  def sign_encoded(encoded)
331
322
  digest = generate_digest(encoded)
332
323
  encoded << SEPARATOR << digest
@@ -28,8 +28,8 @@ module ActiveSupport
28
28
  # <tt>transitional = false</tt>.
29
29
 
30
30
  ##
31
- # :singleton-method: new
32
- # :call-seq: new(&secret_generator)
31
+ # :method: initialize
32
+ # :call-seq: initialize(&secret_generator)
33
33
  #
34
34
  # Initializes a new instance. +secret_generator+ must accept a salt, and
35
35
  # return a suitable secret (string). +secret_generator+ may also accept
@@ -59,9 +59,7 @@ module ActiveSupport
59
59
 
60
60
  ##
61
61
  # :method: rotate
62
- # :call-seq:
63
- # rotate(**options)
64
- # rotate(&block)
62
+ # :call-seq: rotate(**options)
65
63
  #
66
64
  # Adds +options+ to the list of option sets. Messages will be signed using
67
65
  # the first set in the list. When verifying, however, each set will be
@@ -15,11 +15,6 @@ module ActiveSupport
15
15
  fall_back_to build_rotation(*args, **options)
16
16
  end
17
17
 
18
- def on_rotation(&on_rotation)
19
- @on_rotation = on_rotation
20
- self
21
- end
22
-
23
18
  def fall_back_to(fallback)
24
19
  @rotations << fallback
25
20
  self
@@ -55,10 +55,7 @@ module ActiveSupport # :nodoc:
55
55
  # Creates a new Chars instance by wrapping _string_.
56
56
  def initialize(string)
57
57
  @wrapped_string = string
58
- if string.encoding != Encoding::UTF_8
59
- @wrapped_string = @wrapped_string.dup
60
- @wrapped_string.force_encoding(Encoding::UTF_8)
61
- end
58
+ @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
62
59
  end
63
60
 
64
61
  # Forward all undefined methods to the wrapped string.
@@ -1,6 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveSupport
4
+ # = Number Helper
5
+ #
6
+ # Provides methods for formatting numbers into currencies, percentages,
7
+ # phone numbers, and more.
8
+ #
9
+ # Example usage in a class:
10
+ # class Topic
11
+ # include ActiveSupport::NumberHelper
12
+ #
13
+ # def price
14
+ # number_to_currency(@price)
15
+ # end
16
+ # end
17
+ #
18
+ # Example usage in a module:
19
+ # require "active_support/number_helper"
20
+ #
21
+ # module NumberFormatting
22
+ # def format_price(price)
23
+ # ActiveSupport::NumberHelper.number_to_currency(price)
24
+ # end
25
+ # end
4
26
  module NumberHelper
5
27
  extend ActiveSupport::Autoload
6
28
 
@@ -96,6 +96,10 @@ module ActiveSupport
96
96
  config.eager_load_namespaces << TZInfo
97
97
  end
98
98
 
99
+ initializer "active_support.to_time_preserves_timezone" do |app|
100
+ ActiveSupport.to_time_preserves_timezone = app.config.active_support.to_time_preserves_timezone
101
+ end
102
+
99
103
  # Sets the default week start
100
104
  # If assigned value is not a valid day symbol (e.g. :sunday, :monday, ...), an exception will be raised.
101
105
  initializer "active_support.initialize_beginning_of_week" do |app|
@@ -113,6 +113,11 @@ module ActiveSupport
113
113
  end
114
114
  end
115
115
 
116
+ # Returns an `ActiveSupport::Logger` that has already been wrapped with tagged logging concern.
117
+ def self.logger(*args, **kwargs)
118
+ new ActiveSupport::Logger.new(*args, **kwargs)
119
+ end
120
+
116
121
  def self.new(logger)
117
122
  logger = logger.clone
118
123
 
@@ -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
 
@@ -118,9 +118,13 @@ module ActiveSupport
118
118
 
119
119
  expressions.zip(exps, before) do |(code, diff), exp, before_value|
120
120
  actual = exp.call
121
- error = "#{code.inspect} didn't change by #{diff}, but by #{actual - before_value}"
122
- error = "#{message}.\n#{error}" if message
123
- assert_equal(before_value + diff, actual, error)
121
+ rich_message = -> do
122
+ 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 = "#{message}.\n#{error}" if message
125
+ error
126
+ end
127
+ assert_equal(before_value + diff, actual, rich_message)
124
128
  end
125
129
 
126
130
  retval
@@ -195,22 +199,32 @@ module ActiveSupport
195
199
  retval = _assert_nothing_raised_or_warn("assert_changes", &block)
196
200
 
197
201
  unless from == UNTRACKED
198
- error = "Expected change from #{from.inspect}, got #{before.inspect}"
199
- error = "#{message}.\n#{error}" if message
200
- assert from === before, error
202
+ rich_message = -> do
203
+ error = "Expected change from #{from.inspect}, got #{before.inspect}"
204
+ error = "#{message}.\n#{error}" if message
205
+ error
206
+ end
207
+ assert from === before, rich_message
201
208
  end
202
209
 
203
210
  after = exp.call
204
211
 
205
- error = "#{expression.inspect} didn't change"
206
- error = "#{error}. It was already #{to.inspect}" if before == to
207
- error = "#{message}.\n#{error}" if message
208
- refute_equal before, after, error
212
+ rich_message = -> do
213
+ code_string = expression.respond_to?(:call) ? _callable_to_source_string(expression) : expression
214
+ error = "`#{code_string}` didn't change"
215
+ error = "#{error}. It was already #{to.inspect}" if before == to
216
+ error = "#{message}.\n#{error}" if message
217
+ error
218
+ end
219
+ refute_equal before, after, rich_message
209
220
 
210
221
  unless to == UNTRACKED
211
- error = "Expected change to #{to.inspect}, got #{after.inspect}\n"
212
- error = "#{message}.\n#{error}" if message
213
- assert to === after, error
222
+ rich_message = -> do
223
+ error = "Expected change to #{to.inspect}, got #{after.inspect}\n"
224
+ error = "#{message}.\n#{error}" if message
225
+ error
226
+ end
227
+ assert to === after, rich_message
214
228
  end
215
229
 
216
230
  retval
@@ -242,20 +256,27 @@ module ActiveSupport
242
256
  retval = _assert_nothing_raised_or_warn("assert_no_changes", &block)
243
257
 
244
258
  unless from == UNTRACKED
245
- error = "Expected initial value of #{from.inspect}, got #{before.inspect}"
246
- error = "#{message}.\n#{error}" if message
247
- assert from === before, error
259
+ rich_message = -> do
260
+ error = "Expected initial value of #{from.inspect}, got #{before.inspect}"
261
+ error = "#{message}.\n#{error}" if message
262
+ error
263
+ end
264
+ assert from === before, rich_message
248
265
  end
249
266
 
250
267
  after = exp.call
251
268
 
252
- error = "#{expression.inspect} changed"
253
- error = "#{message}.\n#{error}" if message
269
+ rich_message = -> do
270
+ code_string = expression.respond_to?(:call) ? _callable_to_source_string(expression) : expression
271
+ error = "`#{code_string}` changed"
272
+ error = "#{message}.\n#{error}" if message
273
+ error
274
+ end
254
275
 
255
276
  if before.nil?
256
- assert_nil after, error
277
+ assert_nil after, rich_message
257
278
  else
258
- assert_equal before, after, error
279
+ assert_equal before, after, rich_message
259
280
  end
260
281
 
261
282
  retval
@@ -276,6 +297,36 @@ module ActiveSupport
276
297
 
277
298
  raise
278
299
  end
300
+
301
+ def _callable_to_source_string(callable)
302
+ if defined?(RubyVM::AbstractSyntaxTree) && callable.is_a?(Proc)
303
+ ast = begin
304
+ RubyVM::AbstractSyntaxTree.of(callable, keep_script_lines: true)
305
+ rescue SystemCallError
306
+ # Failed to get the source somehow
307
+ return callable
308
+ end
309
+ return callable unless ast
310
+
311
+ source = ast.source
312
+ source.strip!
313
+
314
+ # We ignore procs defined with do/end as they are likely multi-line anyway.
315
+ if source.start_with?("{")
316
+ source.delete_suffix!("}")
317
+ source.delete_prefix!("{")
318
+ source.strip!
319
+ # It won't read nice if the callable contains multiple
320
+ # lines, and it should be a rare occurence anyway.
321
+ # Same if it takes arguments.
322
+ if !source.include?("\n") && !source.start_with?("|")
323
+ return source
324
+ end
325
+ end
326
+ end
327
+
328
+ callable
329
+ end
279
330
  end
280
331
  end
281
332
  end
@@ -5,8 +5,6 @@ require "active_support/testing/parallelize_executor"
5
5
  module ActiveSupport
6
6
  module Testing
7
7
  module Isolation
8
- require "thread"
9
-
10
8
  SubprocessCrashed = Class.new(StandardError)
11
9
 
12
10
  def self.included(klass) # :nodoc:
@@ -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, worker_pid)
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, worker_pid)
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, Process.pid)
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, Process.pid)
32
+ @queue.stop_worker(@id)
33
33
  end
34
34
  end
35
35
 
@@ -47,19 +47,8 @@ module ActiveSupport
47
47
  end
48
48
 
49
49
  def shutdown
50
- dead_worker_pids = @worker_pool.filter_map do |pid|
51
- Process.waitpid(pid, Process::WNOHANG)
52
- rescue Errno::ECHILD
53
- pid
54
- end
55
- @queue_server.remove_dead_workers(dead_worker_pids)
56
-
57
50
  @queue_server.shutdown
58
- @worker_pool.each do |pid|
59
- Process.waitpid(pid)
60
- rescue Errno::ECHILD
61
- nil
62
- end
51
+ @worker_pool.each { |pid| Process.waitpid pid }
63
52
  end
64
53
  end
65
54
  end