activesupport 7.1.3.4 → 7.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +123 -1084
  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/broadcast_logger.rb +5 -4
  6. data/lib/active_support/cache/file_store.rb +15 -10
  7. data/lib/active_support/cache/mem_cache_store.rb +16 -74
  8. data/lib/active_support/cache/memory_store.rb +2 -1
  9. data/lib/active_support/cache/redis_cache_store.rb +16 -13
  10. data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
  11. data/lib/active_support/cache.rb +61 -68
  12. data/lib/active_support/callbacks.rb +74 -113
  13. data/lib/active_support/code_generator.rb +15 -10
  14. data/lib/active_support/core_ext/array/conversions.rb +0 -2
  15. data/lib/active_support/core_ext/class/subclasses.rb +15 -35
  16. data/lib/active_support/core_ext/date/blank.rb +4 -0
  17. data/lib/active_support/core_ext/date/conversions.rb +0 -2
  18. data/lib/active_support/core_ext/date_and_time/compatibility.rb +28 -1
  19. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  20. data/lib/active_support/core_ext/date_time/conversions.rb +0 -4
  21. data/lib/active_support/core_ext/digest/uuid.rb +6 -0
  22. data/lib/active_support/core_ext/erb/util.rb +5 -0
  23. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  24. data/lib/active_support/core_ext/module/attr_internal.rb +17 -6
  25. data/lib/active_support/core_ext/module/delegation.rb +20 -148
  26. data/lib/active_support/core_ext/module/deprecation.rb +1 -4
  27. data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
  28. data/lib/active_support/core_ext/object/blank.rb +45 -1
  29. data/lib/active_support/core_ext/object/duplicable.rb +24 -15
  30. data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
  31. data/lib/active_support/core_ext/object/json.rb +1 -1
  32. data/lib/active_support/core_ext/object/with.rb +5 -3
  33. data/lib/active_support/core_ext/pathname/blank.rb +4 -0
  34. data/lib/active_support/core_ext/range/overlap.rb +1 -1
  35. data/lib/active_support/core_ext/securerandom.rb +8 -24
  36. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  37. data/lib/active_support/core_ext/string/filters.rb +1 -1
  38. data/lib/active_support/core_ext/string/output_safety.rb +0 -7
  39. data/lib/active_support/core_ext/time/calculations.rb +18 -28
  40. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  41. data/lib/active_support/core_ext/time/conversions.rb +0 -2
  42. data/lib/active_support/core_ext.rb +0 -1
  43. data/lib/active_support/current_attributes.rb +34 -40
  44. data/lib/active_support/delegation.rb +202 -0
  45. data/lib/active_support/dependencies/autoload.rb +0 -12
  46. data/lib/active_support/deprecation/constant_accessor.rb +47 -26
  47. data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
  48. data/lib/active_support/deprecation/reporting.rb +7 -2
  49. data/lib/active_support/deprecation.rb +8 -5
  50. data/lib/active_support/descendants_tracker.rb +9 -87
  51. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  52. data/lib/active_support/duration/iso8601_serializer.rb +1 -2
  53. data/lib/active_support/duration.rb +11 -6
  54. data/lib/active_support/error_reporter.rb +41 -3
  55. data/lib/active_support/evented_file_update_checker.rb +0 -1
  56. data/lib/active_support/execution_wrapper.rb +0 -1
  57. data/lib/active_support/file_update_checker.rb +1 -1
  58. data/lib/active_support/fork_tracker.rb +2 -38
  59. data/lib/active_support/gem_version.rb +3 -3
  60. data/lib/active_support/hash_with_indifferent_access.rb +6 -8
  61. data/lib/active_support/html_safe_translation.rb +7 -4
  62. data/lib/active_support/json/encoding.rb +1 -1
  63. data/lib/active_support/log_subscriber.rb +1 -12
  64. data/lib/active_support/logger.rb +15 -2
  65. data/lib/active_support/logger_thread_safe_level.rb +0 -8
  66. data/lib/active_support/message_pack/extensions.rb +15 -2
  67. data/lib/active_support/message_verifier.rb +12 -0
  68. data/lib/active_support/messages/codec.rb +1 -1
  69. data/lib/active_support/multibyte/chars.rb +2 -2
  70. data/lib/active_support/notifications/fanout.rb +4 -7
  71. data/lib/active_support/notifications/instrumenter.rb +32 -21
  72. data/lib/active_support/notifications.rb +28 -27
  73. data/lib/active_support/number_helper/number_converter.rb +2 -2
  74. data/lib/active_support/option_merger.rb +2 -2
  75. data/lib/active_support/ordered_options.rb +53 -15
  76. data/lib/active_support/proxy_object.rb +8 -5
  77. data/lib/active_support/railtie.rb +4 -11
  78. data/lib/active_support/string_inquirer.rb +1 -1
  79. data/lib/active_support/subscriber.rb +1 -0
  80. data/lib/active_support/syntax_error_proxy.rb +1 -11
  81. data/lib/active_support/tagged_logging.rb +4 -1
  82. data/lib/active_support/test_case.rb +3 -1
  83. data/lib/active_support/testing/assertions.rb +4 -4
  84. data/lib/active_support/testing/constant_stubbing.rb +30 -8
  85. data/lib/active_support/testing/deprecation.rb +5 -12
  86. data/lib/active_support/testing/isolation.rb +18 -8
  87. data/lib/active_support/testing/method_call_assertions.rb +2 -16
  88. data/lib/active_support/testing/setup_and_teardown.rb +2 -0
  89. data/lib/active_support/testing/strict_warnings.rb +5 -4
  90. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  91. data/lib/active_support/testing/time_helpers.rb +3 -3
  92. data/lib/active_support/time_with_zone.rb +7 -3
  93. data/lib/active_support/values/time_zone.rb +10 -1
  94. data/lib/active_support/xml_mini.rb +11 -2
  95. data/lib/active_support.rb +3 -2
  96. metadata +35 -15
  97. data/lib/active_support/deprecation/instance_delegator.rb +0 -65
  98. data/lib/active_support/ruby_features.rb +0 -7
@@ -29,6 +29,16 @@ module ActiveSupport
29
29
  # You can consume those events and the information they provide by registering
30
30
  # a subscriber.
31
31
  #
32
+ # ActiveSupport::Notifications.subscribe('render') do |event|
33
+ # event.name # => "render"
34
+ # event.duration # => 10 (in milliseconds)
35
+ # event.payload # => { extra: :information }
36
+ # event.allocations # => 1826 (objects)
37
+ # end
38
+ #
39
+ # +Event+ objects record CPU time and allocations. If you don't need this
40
+ # it's also possible to pass a block that accepts five arguments:
41
+ #
32
42
  # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
33
43
  # name # => String, name of the event (such as 'render' from above)
34
44
  # start # => Time, when the instrumented block started execution
@@ -42,20 +52,18 @@ module ActiveSupport
42
52
  #
43
53
  # ActiveSupport::Notifications.monotonic_subscribe('render') do |name, start, finish, id, payload|
44
54
  # name # => String, name of the event (such as 'render' from above)
45
- # start # => Monotonic time, when the instrumented block started execution
46
- # finish # => Monotonic time, when the instrumented block ended execution
55
+ # start # => Float, monotonic time when the instrumented block started execution
56
+ # finish # => Float, monotonic time when the instrumented block ended execution
47
57
  # id # => String, unique ID for the instrumenter that fired the event
48
58
  # payload # => Hash, the payload
49
59
  # end
50
60
  #
51
- # The +start+ and +finish+ values above represent monotonic time.
52
- #
53
61
  # For instance, let's store all "render" events in an array:
54
62
  #
55
63
  # events = []
56
64
  #
57
- # ActiveSupport::Notifications.subscribe('render') do |*args|
58
- # events << ActiveSupport::Notifications::Event.new(*args)
65
+ # ActiveSupport::Notifications.subscribe('render') do |event|
66
+ # events << event
59
67
  # end
60
68
  #
61
69
  # That code returns right away, you are just subscribing to "render" events.
@@ -66,14 +74,10 @@ module ActiveSupport
66
74
  # end
67
75
  #
68
76
  # event = events.first
69
- # event.name # => "render"
70
- # event.duration # => 10 (in milliseconds)
71
- # event.payload # => { extra: :information }
72
- #
73
- # The block in the <tt>subscribe</tt> call gets the name of the event, start
74
- # timestamp, end timestamp, a string with a unique identifier for that event's instrumenter
75
- # (something like "535801666f04d0298cd6"), and a hash with the payload, in
76
- # that order.
77
+ # event.name # => "render"
78
+ # event.duration # => 10 (in milliseconds)
79
+ # event.payload # => { extra: :information }
80
+ # event.allocations # => 1826 (objects)
77
81
  #
78
82
  # If an exception happens during that particular instrumentation the payload will
79
83
  # have a key <tt>:exception</tt> with an array of two elements as value: a string with
@@ -138,7 +142,7 @@ module ActiveSupport
138
142
  # You can subscribe to some event temporarily while some block runs. For
139
143
  # example, in
140
144
  #
141
- # callback = lambda {|*args| ... }
145
+ # callback = lambda {|event| ... }
142
146
  # ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do
143
147
  # ...
144
148
  # end
@@ -161,7 +165,7 @@ module ActiveSupport
161
165
  #
162
166
  # The +subscribe+ method returns a subscriber object:
163
167
  #
164
- # subscriber = ActiveSupport::Notifications.subscribe("render") do |*args|
168
+ # subscriber = ActiveSupport::Notifications.subscribe("render") do |event|
165
169
  # ...
166
170
  # end
167
171
  #
@@ -214,11 +218,15 @@ module ActiveSupport
214
218
  # You can subscribe to events by passing a String to match exact event
215
219
  # names, or by passing a Regexp to match all events that match a pattern.
216
220
  #
217
- # ActiveSupport::Notifications.subscribe(/render/) do |*args|
218
- # @event = ActiveSupport::Notifications::Event.new(*args)
221
+ # If the block passed to the method only takes one argument,
222
+ # it will yield an +Event+ object to the block:
223
+ #
224
+ # ActiveSupport::Notifications.subscribe(/render/) do |event|
225
+ # @event = event
219
226
  # end
220
227
  #
221
- # The +block+ will receive five parameters with information about the event:
228
+ # Otherwise the +block+ will receive five arguments with information
229
+ # about the event:
222
230
  #
223
231
  # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
224
232
  # name # => String, name of the event (such as 'render' from above)
@@ -228,16 +236,9 @@ module ActiveSupport
228
236
  # payload # => Hash, the payload
229
237
  # end
230
238
  #
231
- # If the block passed to the method only takes one parameter,
232
- # it will yield an event object to the block:
233
- #
234
- # ActiveSupport::Notifications.subscribe(/render/) do |event|
235
- # @event = event
236
- # end
237
- #
238
239
  # Raises an error if invalid event name type is passed:
239
240
  #
240
- # ActiveSupport::Notifications.subscribe(:render) {|*args| ...}
241
+ # ActiveSupport::Notifications.subscribe(:render) {|event| ...}
241
242
  # #=> ArgumentError (pattern must be specified as a String, Regexp or empty)
242
243
  #
243
244
  def subscribe(pattern = nil, callback = nil, &block)
@@ -164,11 +164,11 @@ module ActiveSupport
164
164
  end
165
165
 
166
166
  def translate_number_value_with_default(key, **i18n_options)
167
- I18n.translate(key, **{ default: default_value(key), scope: :number }.merge!(i18n_options))
167
+ I18n.translate(key, default: default_value(key), scope: :number, **i18n_options)
168
168
  end
169
169
 
170
170
  def translate_in_locale(key, **i18n_options)
171
- translate_number_value_with_default(key, **{ locale: options[:locale] }.merge(i18n_options))
171
+ translate_number_value_with_default(key, locale: options[:locale], **i18n_options)
172
172
  end
173
173
 
174
174
  def default_value(key)
@@ -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
@@ -1,11 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveSupport
4
- # = Active Support Proxy \Object
5
- #
6
- # A class with no predefined methods that behaves similarly to Builder's
7
- # BlankSlate. Used for proxy classes.
8
- class ProxyObject < ::BasicObject
4
+ class ProxyObject < ::BasicObject # :nodoc:
9
5
  undef_method :==
10
6
  undef_method :equal?
11
7
 
@@ -13,5 +9,12 @@ module ActiveSupport
13
9
  def raise(*args)
14
10
  ::Object.send(:raise, *args)
15
11
  end
12
+
13
+ def self.inherited(_subclass)
14
+ ::ActiveSupport.deprecator.warn(<<~MSG)
15
+ ActiveSupport::ProxyObject is deprecated and will be removed in Rails 8.0.
16
+ Use Ruby's built-in BasicObject instead.
17
+ MSG
18
+ end
16
19
  end
17
20
  end
@@ -89,10 +89,11 @@ 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
96
97
  end
97
98
 
98
99
  # Sets the default week start
@@ -117,16 +118,8 @@ module ActiveSupport
117
118
 
118
119
  initializer "active_support.set_configs" do |app|
119
120
  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
121
+ k = "#{k}="
122
+ ActiveSupport.public_send(k, v) if ActiveSupport.respond_to? k
130
123
  end
131
124
  end
132
125
 
@@ -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
@@ -119,6 +118,10 @@ module ActiveSupport
119
118
 
120
119
  if logger.formatter
121
120
  logger.formatter = logger.formatter.clone
121
+
122
+ # Workaround for https://bugs.ruby-lang.org/issues/20250
123
+ # Can be removed when Ruby 3.4 is the least supported version.
124
+ logger.formatter.object_id if logger.formatter.is_a?(Proc)
122
125
  else
123
126
  # Ensure we set a default formatter so we aren't extending nil!
124
127
  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
@@ -195,7 +195,7 @@ module ActiveSupport
195
195
  retval = _assert_nothing_raised_or_warn("assert_changes", &block)
196
196
 
197
197
  unless from == UNTRACKED
198
- error = "Expected change from #{from.inspect}, got #{before}"
198
+ error = "Expected change from #{from.inspect}, got #{before.inspect}"
199
199
  error = "#{message}.\n#{error}" if message
200
200
  assert from === before, error
201
201
  end
@@ -203,12 +203,12 @@ module ActiveSupport
203
203
  after = exp.call
204
204
 
205
205
  error = "#{expression.inspect} didn't change"
206
- error = "#{error}. It was already #{to}" if before == to
206
+ error = "#{error}. It was already #{to.inspect}" if before == to
207
207
  error = "#{message}.\n#{error}" if message
208
208
  refute_equal before, after, error
209
209
 
210
210
  unless to == UNTRACKED
211
- error = "Expected change to #{to}, got #{after}\n"
211
+ error = "Expected change to #{to.inspect}, got #{after.inspect}\n"
212
212
  error = "#{message}.\n#{error}" if message
213
213
  assert to === after, error
214
214
  end
@@ -242,7 +242,7 @@ module ActiveSupport
242
242
  retval = _assert_nothing_raised_or_warn("assert_no_changes", &block)
243
243
 
244
244
  unless from == UNTRACKED
245
- error = "Expected initial value of #{from.inspect}"
245
+ error = "Expected initial value of #{from.inspect}, got #{before.inspect}"
246
246
  error = "#{message}.\n#{error}" if message
247
247
  assert from === before, error
248
248
  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|
@@ -5,9 +5,11 @@ module ActiveSupport
5
5
  module Isolation
6
6
  require "thread"
7
7
 
8
+ SubprocessCrashed = Class.new(StandardError)
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)
@@ -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
@@ -5,6 +5,8 @@ Warning[:deprecated] = true
5
5
 
6
6
  module ActiveSupport
7
7
  module RaiseWarnings # :nodoc:
8
+ class WarningError < StandardError; end
9
+
8
10
  PROJECT_ROOT = File.expand_path("../../../../", __dir__)
9
11
  ALLOWED_WARNINGS = Regexp.union(
10
12
  /circular require considered harmful.*delayed_job/, # Bug in delayed job.
@@ -21,18 +23,17 @@ module ActiveSupport
21
23
  %r{/lib/mail/parsers/.*assigned but unused variable - testEof}
22
24
  )
23
25
 
24
- def warn(message, *)
26
+ def warn(message, ...)
25
27
  return if SUPPRESSED_WARNINGS.match?(message)
26
28
 
27
29
  super
28
30
 
29
31
  return unless message.include?(PROJECT_ROOT)
30
32
  return if ALLOWED_WARNINGS.match?(message)
31
- return unless ENV["RAILS_STRICT_WARNINGS"] || ENV["CI"]
33
+ return unless ENV["RAILS_STRICT_WARNINGS"] || ENV["BUILDKITE"]
32
34
 
33
- raise message
35
+ raise WarningError.new(message)
34
36
  end
35
- ruby2_keywords :warn if respond_to?(:ruby2_keywords, true)
36
37
  end
37
38
  end
38
39
 
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ module Testing
5
+ # Warns when a test case does not perform any assertions.
6
+ #
7
+ # This is helpful in detecting broken tests that do not perform intended assertions.
8
+ module TestsWithoutAssertions # :nodoc:
9
+ def after_teardown
10
+ super
11
+
12
+ if assertions.zero? && !skipped? && !error?
13
+ file, line = method(name).source_location
14
+ warn "Test is missing assertions: `#{name}` #{file}:#{line}"
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end