activesupport 7.2.2.1 → 8.0.5

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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +277 -151
  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 +61 -74
  8. data/lib/active_support/cache/file_store.rb +14 -4
  9. data/lib/active_support/cache/mem_cache_store.rb +17 -16
  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 +7 -4
  13. data/lib/active_support/cache/strategy/local_cache.rb +56 -20
  14. data/lib/active_support/cache.rb +19 -14
  15. data/lib/active_support/callbacks.rb +8 -5
  16. data/lib/active_support/class_attribute.rb +33 -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/array/conversions.rb +3 -3
  21. data/lib/active_support/core_ext/benchmark.rb +7 -9
  22. data/lib/active_support/core_ext/class/attribute.rb +26 -20
  23. data/lib/active_support/core_ext/date/conversions.rb +2 -0
  24. data/lib/active_support/core_ext/date_and_time/compatibility.rb +2 -2
  25. data/lib/active_support/core_ext/date_time/conversions.rb +4 -2
  26. data/lib/active_support/core_ext/enumerable.rb +25 -8
  27. data/lib/active_support/core_ext/erb/util.rb +2 -2
  28. data/lib/active_support/core_ext/hash/deep_merge.rb +1 -0
  29. data/lib/active_support/core_ext/hash/except.rb +0 -12
  30. data/lib/active_support/core_ext/module/attr_internal.rb +3 -4
  31. data/lib/active_support/core_ext/module/introspection.rb +3 -0
  32. data/lib/active_support/core_ext/object/json.rb +16 -10
  33. data/lib/active_support/core_ext/object/to_query.rb +2 -1
  34. data/lib/active_support/core_ext/object/try.rb +2 -2
  35. data/lib/active_support/core_ext/range/overlap.rb +3 -3
  36. data/lib/active_support/core_ext/range/sole.rb +17 -0
  37. data/lib/active_support/core_ext/range.rb +1 -0
  38. data/lib/active_support/core_ext/securerandom.rb +24 -8
  39. data/lib/active_support/core_ext/string/filters.rb +3 -3
  40. data/lib/active_support/core_ext/string/inflections.rb +1 -1
  41. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  42. data/lib/active_support/core_ext/string/output_safety.rb +3 -1
  43. data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
  44. data/lib/active_support/core_ext/time/calculations.rb +14 -2
  45. data/lib/active_support/core_ext/time/compatibility.rb +9 -1
  46. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  47. data/lib/active_support/current_attributes.rb +14 -7
  48. data/lib/active_support/delegation.rb +25 -44
  49. data/lib/active_support/dependencies.rb +0 -1
  50. data/lib/active_support/deprecation/reporting.rb +0 -19
  51. data/lib/active_support/deprecation.rb +1 -1
  52. data/lib/active_support/duration.rb +14 -10
  53. data/lib/active_support/encrypted_configuration.rb +20 -2
  54. data/lib/active_support/error_reporter.rb +36 -3
  55. data/lib/active_support/evented_file_update_checker.rb +0 -1
  56. data/lib/active_support/execution_wrapper.rb +1 -1
  57. data/lib/active_support/file_update_checker.rb +1 -1
  58. data/lib/active_support/gem_version.rb +4 -4
  59. data/lib/active_support/hash_with_indifferent_access.rb +34 -31
  60. data/lib/active_support/i18n_railtie.rb +19 -11
  61. data/lib/active_support/inflector/inflections.rb +2 -1
  62. data/lib/active_support/inflector/methods.rb +3 -3
  63. data/lib/active_support/isolated_execution_state.rb +4 -4
  64. data/lib/active_support/json/decoding.rb +4 -2
  65. data/lib/active_support/json/encoding.rb +25 -7
  66. data/lib/active_support/lazy_load_hooks.rb +1 -1
  67. data/lib/active_support/logger_thread_safe_level.rb +6 -3
  68. data/lib/active_support/message_encryptors.rb +2 -2
  69. data/lib/active_support/message_pack/extensions.rb +1 -1
  70. data/lib/active_support/message_verifier.rb +9 -0
  71. data/lib/active_support/message_verifiers.rb +5 -3
  72. data/lib/active_support/messages/rotator.rb +5 -0
  73. data/lib/active_support/multibyte/chars.rb +4 -1
  74. data/lib/active_support/notifications/fanout.rb +0 -1
  75. data/lib/active_support/notifications/instrumenter.rb +1 -1
  76. data/lib/active_support/number_helper/number_converter.rb +1 -1
  77. data/lib/active_support/number_helper/number_to_delimited_converter.rb +17 -2
  78. data/lib/active_support/number_helper.rb +22 -0
  79. data/lib/active_support/railtie.rb +6 -0
  80. data/lib/active_support/tagged_logging.rb +5 -0
  81. data/lib/active_support/test_case.rb +6 -0
  82. data/lib/active_support/testing/assertions.rb +84 -21
  83. data/lib/active_support/testing/autorun.rb +5 -0
  84. data/lib/active_support/testing/isolation.rb +0 -2
  85. data/lib/active_support/testing/parallelization/server.rb +15 -2
  86. data/lib/active_support/testing/parallelization/worker.rb +7 -3
  87. data/lib/active_support/testing/parallelization.rb +12 -1
  88. data/lib/active_support/testing/time_helpers.rb +2 -1
  89. data/lib/active_support/time_with_zone.rb +22 -13
  90. data/lib/active_support/values/time_zone.rb +11 -9
  91. data/lib/active_support/xml_mini.rb +2 -0
  92. data/lib/active_support.rb +10 -3
  93. metadata +24 -12
  94. data/lib/active_support/proxy_object.rb +0 -20
  95. data/lib/active_support/testing/strict_warnings.rb +0 -43
@@ -13,12 +13,30 @@ 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\"}"
21
16
  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\":\"<>&\"}"
22
40
  def encode(value, options = nil)
23
41
  Encoding.json_encoder.new(options).encode(value)
24
42
  end
@@ -36,14 +54,14 @@ module ActiveSupport
36
54
  # Encode the given object into a JSON string
37
55
  def encode(value)
38
56
  unless options.empty?
39
- value = value.as_json(options.dup)
57
+ value = value.as_json(options.dup.freeze)
40
58
  end
41
59
  json = stringify(jsonify(value))
42
60
 
43
61
  # Rails does more escaping than the JSON gem natively does (we
44
62
  # escape \u2028 and \u2029 and optionally >, <, & to work around
45
63
  # certain browser problems).
46
- if Encoding.escape_html_entities_in_json
64
+ if @options.fetch(:escape_html_entities, Encoding.escape_html_entities_in_json)
47
65
  json.gsub!(">", '\u003e')
48
66
  json.gsub!("<", '\u003c')
49
67
  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.
@@ -7,6 +7,11 @@ module ActiveSupport
7
7
  module LoggerThreadSafeLevel # :nodoc:
8
8
  extend ActiveSupport::Concern
9
9
 
10
+ def initialize(...)
11
+ super
12
+ @local_level_key = :"logger_thread_safe_level_#{object_id}"
13
+ end
14
+
10
15
  def local_level
11
16
  IsolatedExecutionState[local_level_key]
12
17
  end
@@ -40,8 +45,6 @@ module ActiveSupport
40
45
  end
41
46
 
42
47
  private
43
- def local_level_key
44
- @local_level_key ||= :"logger_thread_safe_level_#{object_id}"
45
- end
48
+ attr_reader :local_level_key
46
49
  end
47
50
  end
@@ -28,8 +28,8 @@ module ActiveSupport
28
28
  # <tt>transitional = false</tt>.
29
29
 
30
30
  ##
31
- # :method: initialize
32
- # :call-seq: initialize(&secret_generator)
31
+ # :singleton-method: new
32
+ # :call-seq: new(&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
@@ -4,7 +4,7 @@ require "bigdecimal"
4
4
  require "date"
5
5
  require "ipaddr"
6
6
  require "pathname"
7
- require "uri/generic"
7
+ require "uri"
8
8
  require "msgpack/bigint"
9
9
  require "active_support/hash_with_indifferent_access"
10
10
  require "active_support/time"
@@ -154,6 +154,8 @@ 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.
157
159
  #
158
160
  # [+:force_legacy_metadata_serializer+]
159
161
  # Whether to use the legacy metadata serializer, which serializes the
@@ -318,6 +320,13 @@ module ActiveSupport
318
320
  end
319
321
 
320
322
  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
+
321
330
  def sign_encoded(encoded)
322
331
  digest = generate_digest(encoded)
323
332
  encoded << SEPARATOR << digest
@@ -28,8 +28,8 @@ module ActiveSupport
28
28
  # <tt>transitional = false</tt>.
29
29
 
30
30
  ##
31
- # :method: initialize
32
- # :call-seq: initialize(&secret_generator)
31
+ # :singleton-method: new
32
+ # :call-seq: new(&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,7 +59,9 @@ module ActiveSupport
59
59
 
60
60
  ##
61
61
  # :method: rotate
62
- # :call-seq: rotate(**options)
62
+ # :call-seq:
63
+ # rotate(**options)
64
+ # rotate(&block)
63
65
  #
64
66
  # Adds +options+ to the list of option sets. Messages will be signed using
65
67
  # the first set in the list. When verifying, however, each set will be
@@ -15,6 +15,11 @@ 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
+
18
23
  def fall_back_to(fallback)
19
24
  @rotations << fallback
20
25
  self
@@ -55,7 +55,10 @@ module ActiveSupport # :nodoc:
55
55
  # Creates a new Chars instance by wrapping _string_.
56
56
  def initialize(string)
57
57
  @wrapped_string = string
58
- @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
58
+ if string.encoding != Encoding::UTF_8
59
+ @wrapped_string = @wrapped_string.dup
60
+ @wrapped_string.force_encoding(Encoding::UTF_8)
61
+ end
59
62
  end
60
63
 
61
64
  # Forward all undefined methods to the wrapped string.
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "concurrent/map"
4
- require "set"
5
4
  require "active_support/core_ext/object/try"
6
5
 
7
6
  module ActiveSupport
@@ -164,7 +164,7 @@ module ActiveSupport
164
164
  @cpu_time_finish - @cpu_time_start
165
165
  end
166
166
 
167
- # Returns the idle time time (in milliseconds) passed between the call to
167
+ # Returns the idle time (in milliseconds) passed between the call to
168
168
  # #start! and the call to #finish!.
169
169
  def idle_time
170
170
  diff = duration - cpu_time
@@ -180,7 +180,7 @@ module ActiveSupport
180
180
  when Float, Rational
181
181
  number.to_d(0)
182
182
  when String
183
- BigDecimal(number, exception: false)
183
+ BigDecimal(number, exception: false) unless number.to_s.match?(/[de]/i)
184
184
  else
185
185
  number.to_d rescue nil
186
186
  end
@@ -16,9 +16,24 @@ module ActiveSupport
16
16
  private
17
17
  def parts
18
18
  left, right = number.to_s.split(".")
19
- left.gsub!(delimiter_pattern) do |digit_to_delimit|
20
- "#{digit_to_delimit}#{options[:delimiter]}"
19
+ if delimiter_pattern
20
+ left.gsub!(delimiter_pattern) do |digit_to_delimit|
21
+ "#{digit_to_delimit}#{options[:delimiter]}"
22
+ end
23
+ else
24
+ left_parts = []
25
+ offset = left.size % 3
26
+ if offset > 0
27
+ left_parts << left[0, offset]
28
+ end
29
+
30
+ (left.size / 3).times do |i|
31
+ left_parts << left[offset + (i * 3), 3]
32
+ end
33
+
34
+ left = left_parts.join(options[:delimiter])
21
35
  end
36
+
22
37
  [left, right].compact
23
38
  end
24
39
 
@@ -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,12 @@ 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
+ config.after_initialize do
101
+ ActiveSupport.to_time_preserves_timezone = app.config.active_support.to_time_preserves_timezone
102
+ end
103
+ end
104
+
99
105
  # Sets the default week start
100
106
  # If assigned value is not a valid day symbol (e.g. :sunday, :monday, ...), an exception will be raised.
101
107
  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
 
@@ -45,6 +45,12 @@ module ActiveSupport
45
45
  ActiveSupport.test_order ||= :random
46
46
  end
47
47
 
48
+ if Minitest.respond_to? :run_order # MT6 API change
49
+ def run_order # :nodoc:
50
+ test_order
51
+ end
52
+ end
53
+
48
54
  # Parallelizes the test suite.
49
55
  #
50
56
  # Takes a +workers+ argument that controls how many times the process
@@ -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,14 @@ 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 = "#{error}\n#{diff before_value + diff, actual}" if Minitest::VERSION > "6"
125
+ error = "#{message}.\n#{error}" if message
126
+ error
127
+ end
128
+ assert_equal(before_value + diff, actual, rich_message)
124
129
  end
125
130
 
126
131
  retval
@@ -195,22 +200,32 @@ module ActiveSupport
195
200
  retval = _assert_nothing_raised_or_warn("assert_changes", &block)
196
201
 
197
202
  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
203
+ rich_message = -> do
204
+ error = "Expected change from #{from.inspect}, got #{before.inspect}"
205
+ error = "#{message}.\n#{error}" if message
206
+ error
207
+ end
208
+ assert from === before, rich_message
201
209
  end
202
210
 
203
211
  after = exp.call
204
212
 
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
213
+ rich_message = -> do
214
+ code_string = expression.respond_to?(:call) ? _callable_to_source_string(expression) : expression
215
+ error = "`#{code_string}` didn't change"
216
+ error = "#{error}. It was already #{to.inspect}." if before == to
217
+ error = "#{message}.\n#{error}" if message
218
+ error
219
+ end
220
+ refute_equal before, after, rich_message
209
221
 
210
222
  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
223
+ rich_message = -> do
224
+ error = "Expected change to #{to.inspect}, got #{after.inspect}\n"
225
+ error = "#{message}.\n#{error}" if message
226
+ error
227
+ end
228
+ assert to === after, rich_message
214
229
  end
215
230
 
216
231
  retval
@@ -242,20 +257,28 @@ module ActiveSupport
242
257
  retval = _assert_nothing_raised_or_warn("assert_no_changes", &block)
243
258
 
244
259
  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
260
+ rich_message = -> do
261
+ error = "Expected initial value of #{from.inspect}, got #{before.inspect}"
262
+ error = "#{message}.\n#{error}" if message
263
+ error
264
+ end
265
+ assert from === before, rich_message
248
266
  end
249
267
 
250
268
  after = exp.call
251
269
 
252
- error = "#{expression.inspect} changed"
253
- error = "#{message}.\n#{error}" if message
270
+ rich_message = -> do
271
+ code_string = expression.respond_to?(:call) ? _callable_to_source_string(expression) : expression
272
+ error = "`#{code_string}` changed."
273
+ error = "#{message}.\n#{error}" if message
274
+ error = "#{error}\n#{diff before, after}" if Minitest::VERSION > "6"
275
+ error
276
+ end
254
277
 
255
278
  if before.nil?
256
- assert_nil after, error
279
+ assert_nil after, rich_message
257
280
  else
258
- assert_equal before, after, error
281
+ assert_equal before, after, rich_message
259
282
  end
260
283
 
261
284
  retval
@@ -276,6 +299,46 @@ module ActiveSupport
276
299
 
277
300
  raise
278
301
  end
302
+
303
+ def _callable_to_source_string(callable)
304
+ if defined?(RubyVM::InstructionSequence) && callable.is_a?(Proc)
305
+ iseq = RubyVM::InstructionSequence.of(callable)
306
+ source =
307
+ if iseq.script_lines
308
+ iseq.script_lines.join("\n")
309
+ elsif File.readable?(iseq.absolute_path)
310
+ File.read(iseq.absolute_path)
311
+ end
312
+
313
+ return callable unless source
314
+
315
+ location = iseq.to_a[4][:code_location]
316
+ return callable unless location
317
+
318
+ lines = source.lines[(location[0] - 1)..(location[2] - 1)]
319
+ lines[-1] = lines[-1].byteslice(...location[3])
320
+ lines[0] = lines[0].byteslice(location[1]...)
321
+ source = lines.join.strip
322
+
323
+ # Strip stabby lambda from Ruby 4.1+
324
+ source = source.sub(/^->\s*/, "")
325
+
326
+ # We ignore procs defined with do/end as they are likely multi-line anyway.
327
+ if source.start_with?("{")
328
+ source.delete_suffix!("}")
329
+ source.delete_prefix!("{")
330
+ source.strip!
331
+ # It won't read nice if the callable contains multiple
332
+ # lines, and it should be a rare occurrence anyway.
333
+ # Same if it takes arguments.
334
+ if !source.include?("\n") && !source.start_with?("|")
335
+ return source
336
+ end
337
+ end
338
+ end
339
+
340
+ callable
341
+ end
279
342
  end
280
343
  end
281
344
  end
@@ -2,4 +2,9 @@
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
5
10
  Minitest.autorun
@@ -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,6 +14,7 @@ module ActiveSupport
14
14
  def initialize
15
15
  @queue = Queue.new
16
16
  @active_workers = Concurrent::Map.new
17
+ @worker_pids = Concurrent::Map.new
17
18
  @in_flight = Concurrent::Map.new
18
19
  end
19
20
 
@@ -40,12 +41,24 @@ module ActiveSupport
40
41
  end
41
42
  end
42
43
 
43
- def start_worker(worker_id)
44
+ def start_worker(worker_id, worker_pid)
44
45
  @active_workers[worker_id] = true
46
+ @worker_pids[worker_id] = worker_pid
45
47
  end
46
48
 
47
- def stop_worker(worker_id)
49
+ def stop_worker(worker_id, worker_pid)
48
50
  @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
49
62
  end
50
63
 
51
64
  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, Process.pid)
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, Process.pid)
33
33
  end
34
34
  end
35
35
 
@@ -47,7 +47,11 @@ module ActiveSupport
47
47
  set_process_title("#{klass}##{method}")
48
48
 
49
49
  result = klass.with_info_handler reporter do
50
- Minitest.run_one_method(klass, method)
50
+ if Minitest.respond_to? :run_one_method
51
+ Minitest.run_one_method klass, method
52
+ else
53
+ klass.new(method).run
54
+ end
51
55
  end
52
56
 
53
57
  safe_record(reporter, result)
@@ -47,8 +47,19 @@ 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
+
50
57
  @queue_server.shutdown
51
- @worker_pool.each { |pid| Process.waitpid pid }
58
+ @worker_pool.each do |pid|
59
+ Process.waitpid(pid)
60
+ rescue Errno::ECHILD
61
+ nil
62
+ end
52
63
  end
53
64
  end
54
65
  end
@@ -166,9 +166,10 @@ module ActiveSupport
166
166
  else
167
167
  now = date_or_time
168
168
  now = now.to_time unless now.is_a?(Time)
169
- now = now.change(usec: 0) unless with_usec
170
169
  end
171
170
 
171
+ now = now.change(usec: 0) unless with_usec
172
+
172
173
  # +now+ must be in local system timezone, because +Time.at(now)+
173
174
  # and +now.to_date+ (see stubs below) will use +now+'s timezone too!
174
175
  now = now.getlocal