activesupport 7.1.3.2 → 8.0.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.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -1126
  3. data/lib/active_support/array_inquirer.rb +1 -1
  4. data/lib/active_support/backtrace_cleaner.rb +15 -3
  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 +19 -18
  8. data/lib/active_support/cache/file_store.rb +27 -12
  9. data/lib/active_support/cache/mem_cache_store.rb +16 -74
  10. data/lib/active_support/cache/memory_store.rb +8 -3
  11. data/lib/active_support/cache/redis_cache_store.rb +21 -15
  12. data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
  13. data/lib/active_support/cache.rb +76 -78
  14. data/lib/active_support/callbacks.rb +79 -116
  15. data/lib/active_support/class_attribute.rb +33 -0
  16. data/lib/active_support/code_generator.rb +24 -10
  17. data/lib/active_support/concurrency/share_lock.rb +0 -1
  18. data/lib/active_support/configuration_file.rb +15 -6
  19. data/lib/active_support/core_ext/array/conversions.rb +3 -5
  20. data/lib/active_support/core_ext/benchmark.rb +6 -9
  21. data/lib/active_support/core_ext/class/attribute.rb +24 -20
  22. data/lib/active_support/core_ext/class/subclasses.rb +15 -35
  23. data/lib/active_support/core_ext/date/blank.rb +4 -0
  24. data/lib/active_support/core_ext/date/conversions.rb +2 -2
  25. data/lib/active_support/core_ext/date_and_time/compatibility.rb +28 -1
  26. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  27. data/lib/active_support/core_ext/date_time/conversions.rb +0 -4
  28. data/lib/active_support/core_ext/digest/uuid.rb +6 -0
  29. data/lib/active_support/core_ext/enumerable.rb +8 -3
  30. data/lib/active_support/core_ext/erb/util.rb +7 -2
  31. data/lib/active_support/core_ext/hash/except.rb +0 -12
  32. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  33. data/lib/active_support/core_ext/module/attr_internal.rb +16 -6
  34. data/lib/active_support/core_ext/module/delegation.rb +20 -148
  35. data/lib/active_support/core_ext/module/deprecation.rb +1 -4
  36. data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
  37. data/lib/active_support/core_ext/object/blank.rb +45 -1
  38. data/lib/active_support/core_ext/object/duplicable.rb +24 -15
  39. data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
  40. data/lib/active_support/core_ext/object/json.rb +21 -13
  41. data/lib/active_support/core_ext/object/with.rb +5 -3
  42. data/lib/active_support/core_ext/pathname/blank.rb +4 -0
  43. data/lib/active_support/core_ext/range/overlap.rb +1 -1
  44. data/lib/active_support/core_ext/securerandom.rb +4 -4
  45. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  46. data/lib/active_support/core_ext/string/filters.rb +1 -1
  47. data/lib/active_support/core_ext/string/multibyte.rb +1 -1
  48. data/lib/active_support/core_ext/string/output_safety.rb +0 -7
  49. data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
  50. data/lib/active_support/core_ext/time/calculations.rb +32 -30
  51. data/lib/active_support/core_ext/time/compatibility.rb +24 -0
  52. data/lib/active_support/core_ext/time/conversions.rb +2 -2
  53. data/lib/active_support/core_ext/time/zones.rb +1 -1
  54. data/lib/active_support/core_ext.rb +0 -1
  55. data/lib/active_support/current_attributes.rb +38 -40
  56. data/lib/active_support/delegation.rb +200 -0
  57. data/lib/active_support/dependencies/autoload.rb +0 -12
  58. data/lib/active_support/dependencies.rb +0 -1
  59. data/lib/active_support/deprecation/constant_accessor.rb +47 -26
  60. data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
  61. data/lib/active_support/deprecation/reporting.rb +3 -17
  62. data/lib/active_support/deprecation.rb +8 -5
  63. data/lib/active_support/descendants_tracker.rb +9 -87
  64. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  65. data/lib/active_support/duration/iso8601_serializer.rb +1 -2
  66. data/lib/active_support/duration.rb +25 -16
  67. data/lib/active_support/encrypted_configuration.rb +20 -2
  68. data/lib/active_support/encrypted_file.rb +1 -1
  69. data/lib/active_support/error_reporter.rb +65 -3
  70. data/lib/active_support/evented_file_update_checker.rb +0 -2
  71. data/lib/active_support/execution_wrapper.rb +0 -1
  72. data/lib/active_support/file_update_checker.rb +1 -1
  73. data/lib/active_support/fork_tracker.rb +2 -38
  74. data/lib/active_support/gem_version.rb +4 -4
  75. data/lib/active_support/hash_with_indifferent_access.rb +21 -23
  76. data/lib/active_support/html_safe_translation.rb +7 -4
  77. data/lib/active_support/i18n_railtie.rb +19 -11
  78. data/lib/active_support/isolated_execution_state.rb +0 -2
  79. data/lib/active_support/json/encoding.rb +3 -3
  80. data/lib/active_support/log_subscriber.rb +1 -12
  81. data/lib/active_support/logger.rb +15 -2
  82. data/lib/active_support/logger_thread_safe_level.rb +0 -8
  83. data/lib/active_support/message_pack/extensions.rb +15 -2
  84. data/lib/active_support/message_verifier.rb +12 -0
  85. data/lib/active_support/messages/codec.rb +1 -1
  86. data/lib/active_support/multibyte/chars.rb +2 -2
  87. data/lib/active_support/notifications/fanout.rb +4 -8
  88. data/lib/active_support/notifications/instrumenter.rb +32 -21
  89. data/lib/active_support/notifications.rb +28 -27
  90. data/lib/active_support/number_helper/number_converter.rb +2 -2
  91. data/lib/active_support/number_helper.rb +22 -0
  92. data/lib/active_support/option_merger.rb +2 -2
  93. data/lib/active_support/ordered_options.rb +53 -15
  94. data/lib/active_support/railtie.rb +8 -11
  95. data/lib/active_support/string_inquirer.rb +1 -1
  96. data/lib/active_support/subscriber.rb +1 -0
  97. data/lib/active_support/syntax_error_proxy.rb +1 -11
  98. data/lib/active_support/tagged_logging.rb +9 -1
  99. data/lib/active_support/test_case.rb +3 -1
  100. data/lib/active_support/testing/assertions.rb +79 -21
  101. data/lib/active_support/testing/constant_stubbing.rb +30 -8
  102. data/lib/active_support/testing/deprecation.rb +5 -12
  103. data/lib/active_support/testing/isolation.rb +19 -9
  104. data/lib/active_support/testing/method_call_assertions.rb +2 -16
  105. data/lib/active_support/testing/parallelization/server.rb +3 -0
  106. data/lib/active_support/testing/setup_and_teardown.rb +2 -0
  107. data/lib/active_support/testing/strict_warnings.rb +8 -4
  108. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  109. data/lib/active_support/testing/time_helpers.rb +4 -3
  110. data/lib/active_support/time_with_zone.rb +30 -17
  111. data/lib/active_support/values/time_zone.rb +25 -14
  112. data/lib/active_support/xml_mini.rb +11 -2
  113. data/lib/active_support.rb +12 -4
  114. metadata +68 -19
  115. data/lib/active_support/deprecation/instance_delegator.rb +0 -65
  116. data/lib/active_support/proxy_object.rb +0 -17
  117. data/lib/active_support/ruby_features.rb +0 -7
@@ -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
 
@@ -31,8 +31,8 @@ module ActiveSupport
31
31
  end
32
32
  end
33
33
 
34
- def respond_to_missing?(*arguments)
35
- @context.respond_to?(*arguments)
34
+ def respond_to_missing?(...)
35
+ @context.respond_to?(...)
36
36
  end
37
37
  end
38
38
  end
@@ -46,18 +46,14 @@ module ActiveSupport
46
46
  super(key.to_sym, *identifiers)
47
47
  end
48
48
 
49
- def method_missing(name, *args)
50
- name_string = +name.to_s
51
- if name_string.chomp!("=")
52
- self[name_string] = args.first
49
+ def method_missing(method, *args)
50
+ if method.end_with?("=")
51
+ self[method.name.chomp("=")] = args.first
52
+ elsif method.end_with?("!")
53
+ name_string = method.name.chomp("!")
54
+ self[name_string].presence || raise(KeyError.new(":#{name_string} is blank"))
53
55
  else
54
- bangs = name_string.chomp!("!")
55
-
56
- if bangs
57
- self[name_string].presence || raise(KeyError.new(":#{name_string} is blank"))
58
- else
59
- self[name_string]
60
- end
56
+ self[method.name]
61
57
  end
62
58
  end
63
59
 
@@ -92,18 +88,60 @@ module ActiveSupport
92
88
  # h.boy # => 'John'
93
89
  class InheritableOptions < OrderedOptions
94
90
  def initialize(parent = nil)
95
- if parent.kind_of?(OrderedOptions)
91
+ @parent = parent
92
+ if @parent.kind_of?(OrderedOptions)
96
93
  # use the faster _get when dealing with OrderedOptions
97
- super() { |h, k| parent._get(k) }
98
- elsif parent
99
- super() { |h, k| parent[k] }
94
+ super() { |h, k| @parent._get(k) }
95
+ elsif @parent
96
+ super() { |h, k| @parent[k] }
100
97
  else
101
98
  super()
99
+ @parent = {}
102
100
  end
103
101
  end
104
102
 
103
+ def to_h
104
+ @parent.merge(self)
105
+ end
106
+
107
+ def ==(other)
108
+ to_h == other.to_h
109
+ end
110
+
111
+ def inspect
112
+ "#<#{self.class.name} #{to_h.inspect}>"
113
+ end
114
+
115
+ def to_s
116
+ to_h.to_s
117
+ end
118
+
119
+ def pretty_print(pp)
120
+ pp.pp_hash(to_h)
121
+ end
122
+
123
+ alias_method :own_key?, :key?
124
+ private :own_key?
125
+
126
+ def key?(key)
127
+ super || @parent.key?(key)
128
+ end
129
+
130
+ def overridden?(key)
131
+ !!(@parent && @parent.key?(key) && own_key?(key.to_sym))
132
+ end
133
+
105
134
  def inheritable_copy
106
135
  self.class.new(self)
107
136
  end
137
+
138
+ def to_a
139
+ entries
140
+ end
141
+
142
+ def each(&block)
143
+ to_h.each(&block)
144
+ self
145
+ end
108
146
  end
109
147
  end
@@ -89,10 +89,15 @@ module ActiveSupport
89
89
  begin
90
90
  TZInfo::DataSource.get
91
91
  rescue TZInfo::DataSourceNotFound => e
92
- raise e.exception "tzinfo-data is not present. Please add gem 'tzinfo-data' to your Gemfile and run bundle install"
92
+ raise e.exception('tzinfo-data is not present. Please add gem "tzinfo-data" to your Gemfile and run bundle install')
93
93
  end
94
94
  require "active_support/core_ext/time/zones"
95
95
  Time.zone_default = Time.find_zone!(app.config.time_zone)
96
+ config.eager_load_namespaces << TZInfo
97
+ end
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
96
101
  end
97
102
 
98
103
  # Sets the default week start
@@ -117,16 +122,8 @@ module ActiveSupport
117
122
 
118
123
  initializer "active_support.set_configs" do |app|
119
124
  app.config.active_support.each do |k, v|
120
- if k == "disable_to_s_conversion"
121
- ActiveSupport.deprecator.warn("config.active_support.disable_to_s_conversion is deprecated and will be removed in Rails 7.2.")
122
- elsif k == "remove_deprecated_time_with_zone_name"
123
- ActiveSupport.deprecator.warn("config.active_support.remove_deprecated_time_with_zone_name is deprecated and will be removed in Rails 7.2.")
124
- elsif k == "use_rfc4122_namespaced_uuids"
125
- ActiveSupport.deprecator.warn("config.active_support.use_rfc4122_namespaced_uuids is deprecated and will be removed in Rails 7.2.")
126
- else
127
- k = "#{k}="
128
- ActiveSupport.public_send(k, v) if ActiveSupport.respond_to? k
129
- end
125
+ k = "#{k}="
126
+ ActiveSupport.public_send(k, v) if ActiveSupport.respond_to? k
130
127
  end
131
128
  end
132
129
 
@@ -24,7 +24,7 @@ module ActiveSupport
24
24
  method_name.end_with?("?") || super
25
25
  end
26
26
 
27
- def method_missing(method_name, *arguments)
27
+ def method_missing(method_name, ...)
28
28
  if method_name.end_with?("?")
29
29
  self == method_name[0..-2]
30
30
  else
@@ -67,6 +67,7 @@ module ActiveSupport
67
67
 
68
68
  # Adds event subscribers for all new methods added to the class.
69
69
  def method_added(event)
70
+ super
70
71
  # Only public methods are added as subscribers, and only if a notifier
71
72
  # has been set up. This means that subscribers will only be set up for
72
73
  # classes that call #attach_to.
@@ -45,7 +45,7 @@ module ActiveSupport
45
45
 
46
46
  private
47
47
  def parse_message_for_trace
48
- if source_location_eval?
48
+ if __getobj__.to_s.start_with?("(eval")
49
49
  # If the exception is coming from a call to eval, we need to keep
50
50
  # the path of the file in which eval was called to ensure we can
51
51
  # return the right source fragment to show the location of the
@@ -56,15 +56,5 @@ module ActiveSupport
56
56
  __getobj__.to_s.split("\n")
57
57
  end
58
58
  end
59
-
60
- if SyntaxError.method_defined?(:path) # Ruby 3.3+
61
- def source_location_eval?
62
- __getobj__.path.start_with?("(eval")
63
- end
64
- else # 3.2 and older versions of Ruby
65
- def source_location_eval?
66
- __getobj__.to_s.start_with?("(eval")
67
- end
68
- end
69
59
  end
70
60
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "active_support/core_ext/module/delegation"
4
4
  require "active_support/core_ext/object/blank"
5
- require "logger"
6
5
  require "active_support/logger"
7
6
 
8
7
  module ActiveSupport
@@ -114,11 +113,20 @@ module ActiveSupport
114
113
  end
115
114
  end
116
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
+
117
121
  def self.new(logger)
118
122
  logger = logger.clone
119
123
 
120
124
  if logger.formatter
121
125
  logger.formatter = logger.formatter.clone
126
+
127
+ # Workaround for https://bugs.ruby-lang.org/issues/20250
128
+ # Can be removed when Ruby 3.4 is the least supported version.
129
+ logger.formatter.object_id if logger.formatter.is_a?(Proc)
122
130
  else
123
131
  # Ensure we set a default formatter so we aren't extending nil!
124
132
  logger.formatter = ActiveSupport::Logger::SimpleFormatter.new
@@ -3,6 +3,7 @@
3
3
  require "minitest"
4
4
  require "active_support/testing/tagged_logging"
5
5
  require "active_support/testing/setup_and_teardown"
6
+ require "active_support/testing/tests_without_assertions"
6
7
  require "active_support/testing/assertions"
7
8
  require "active_support/testing/error_reporter_assertions"
8
9
  require "active_support/testing/deprecation"
@@ -78,7 +79,7 @@ module ActiveSupport
78
79
  # number of tests to run is above the +threshold+ param. The default value is
79
80
  # 50, and it's configurable via +config.active_support.test_parallelization_threshold+.
80
81
  def parallelize(workers: :number_of_processors, with: :processes, threshold: ActiveSupport.test_parallelization_threshold)
81
- workers = Concurrent.physical_processor_count if workers == :number_of_processors
82
+ workers = Concurrent.processor_count if workers == :number_of_processors
82
83
  workers = ENV["PARALLEL_WORKERS"].to_i if ENV["PARALLEL_WORKERS"]
83
84
 
84
85
  Minitest.parallel_executor = ActiveSupport::Testing::ParallelizeExecutor.new(size: workers, with: with, threshold: threshold)
@@ -142,6 +143,7 @@ module ActiveSupport
142
143
 
143
144
  include ActiveSupport::Testing::TaggedLogging
144
145
  prepend ActiveSupport::Testing::SetupAndTeardown
146
+ prepend ActiveSupport::Testing::TestsWithoutAssertions
145
147
  include ActiveSupport::Testing::Assertions
146
148
  include ActiveSupport::Testing::ErrorReporterAssertions
147
149
  include ActiveSupport::Testing::Deprecation
@@ -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}"
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}" 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}, got #{after}\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}"
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,43 @@ module ActiveSupport
276
297
 
277
298
  raise
278
299
  end
300
+
301
+ def _callable_to_source_string(callable)
302
+ if defined?(RubyVM::InstructionSequence) && callable.is_a?(Proc)
303
+ iseq = RubyVM::InstructionSequence.of(callable)
304
+ source =
305
+ if iseq.script_lines
306
+ iseq.script_lines.join("\n")
307
+ elsif File.readable?(iseq.absolute_path)
308
+ File.read(iseq.absolute_path)
309
+ end
310
+
311
+ return callable unless source
312
+
313
+ location = iseq.to_a[4][:code_location]
314
+ return callable unless location
315
+
316
+ lines = source.lines[(location[0] - 1)..(location[2] - 1)]
317
+ lines[-1] = lines[-1].byteslice(...location[3])
318
+ lines[0] = lines[0].byteslice(location[1]...)
319
+ source = lines.join.strip
320
+
321
+ # We ignore procs defined with do/end as they are likely multi-line anyway.
322
+ if source.start_with?("{")
323
+ source.delete_suffix!("}")
324
+ source.delete_prefix!("{")
325
+ source.strip!
326
+ # It won't read nice if the callable contains multiple
327
+ # lines, and it should be a rare occurrence anyway.
328
+ # Same if it takes arguments.
329
+ if !source.include?("\n") && !source.start_with?("|")
330
+ return source
331
+ end
332
+ end
333
+ end
334
+
335
+ callable
336
+ end
279
337
  end
280
338
  end
281
339
  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
- old_value = mod.const_get(constant, false)
23
- mod.send(:remove_const, constant)
24
- mod.const_set(constant, new_value)
25
- yield
26
- ensure
27
- mod.send(:remove_const, constant)
28
- mod.const_set(constant, old_value)
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
- ActiveSupport.deprecator.warn("assert_deprecated without a deprecator is deprecated")
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 = nil, &block)
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 = nil)
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|
@@ -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
- require "thread"
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.wait(child.pid)
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
- # This method is a temporary wrapper for mock.expect as part of
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)
@@ -6,6 +6,8 @@ require "drb/unix" unless Gem.win_platform?
6
6
  module ActiveSupport
7
7
  module Testing
8
8
  class Parallelization # :nodoc:
9
+ PrerecordResultClass = Struct.new(:name)
10
+
9
11
  class Server
10
12
  include DRb::DRbUndumped
11
13
 
@@ -21,6 +23,7 @@ module ActiveSupport
21
23
  @in_flight.delete([result.klass, result.name])
22
24
 
23
25
  reporter.synchronize do
26
+ reporter.prerecord(PrerecordResultClass.new(result.klass), result.name)
24
27
  reporter.record(result)
25
28
  end
26
29
  end
@@ -46,6 +46,8 @@ module ActiveSupport
46
46
  run_callbacks :teardown
47
47
  rescue => e
48
48
  self.failures << Minitest::UnexpectedError.new(e)
49
+ rescue Minitest::Assertion => e
50
+ self.failures << e
49
51
  end
50
52
 
51
53
  super