diff options
Diffstat (limited to 'activesupport')
19 files changed, 252 insertions, 54 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index b2330f2c9d..db9c7e96f3 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,52 @@ +* In `:zeitwerk` mode, eager load directories in engines and applications only + if present in their respective `config.eager_load_paths`. + + A common use case for this is adding `lib` to `config.autoload_paths`, but + not to `config.eager_load_paths`. In that configuration, for example, files + in the `lib` directory should not be eager loaded. + + *Xavier Noria* + +* Fix bug in Range comparisons when comparing to an excluded-end Range + + Before: + + (1..10).cover?(1...11) # => false + + After: + + (1..10).cover?(1...11) # => true + + With the same change for `Range#include?` and `Range#===`. + + *Owen Stephens* + +* Use weak references in descendants tracker to allow anonymous subclasses to + be garbage collected. + + *Edgars Beigarts* + +* Update `ActiveSupport::Notifications::Instrumenter#instrument` to make + passing a block optional. This will let users use + `ActiveSupport::Notifications` messaging features outside of + instrumentation. + + *Ali Ibrahim* + +* Fix `Time#advance` to work with dates before 1001-03-07 + + Before: + + Time.utc(1001, 3, 6).advance(years: -1) # => 1000-03-05 00:00:00 UTC + + After + + Time.utc(1001, 3, 6).advance(years: -1) # => 1000-03-06 00:00:00 UTC + + Note that this doesn't affect `DateTime#advance` as that doesn't use a proleptic calendar. + + *Andrew White* + * In Zeitwerk mode, engines are now managed by the `main` autoloader. Engines may reference application constants, if the application is reloaded and we do not reload engines, they won't use the reloaded application code. *Xavier Noria* @@ -6,27 +55,27 @@ I18n.backend.store_translations(:de, i18n: { transliterate: { rule: { "ü" => "ue" } } }) - ActiveSupport::Inflector.transliterate("ü", locale: :de) => "ue" - "Fünf autos".parameterize(locale: :de) => "fuenf-autos" - ActiveSupport::Inflector.parameterize("Fünf autos", locale: :de) => "fuenf-autos" + ActiveSupport::Inflector.transliterate("ü", locale: :de) # => "ue" + "Fünf autos".parameterize(locale: :de) # => "fuenf-autos" + ActiveSupport::Inflector.parameterize("Fünf autos", locale: :de) # => "fuenf-autos" *Kaan Ozkan*, *Sharang Dashputre* -* Allow Array#excluding and Enumerable#excluding to deal with a passed array gracefully. +* Allow `Array#excluding` and `Enumerable#excluding` to deal with a passed array gracefully. - [ 1, 2, 3, 4, 5 ].excluding([4, 5]) => [ 1, 2, 3 ] + [ 1, 2, 3, 4, 5 ].excluding([4, 5]) # => [ 1, 2, 3 ] *DHH* -* Renamed Array#without and Enumerable#without to Array#excluding and Enumerable#excluding, to create parity with - Array#including and Enumerable#including. Retained the old names as aliases. +* Renamed `Array#without` and `Enumerable#without` to `Array#excluding` and `Enumerable#excluding`, to create parity with + `Array#including` and `Enumerable#including`. Retained the old names as aliases. *DHH* -* Added Array#including and Enumerable#including to conveniently enlarge a collection with more members using a method rather than an operator: +* Added `Array#including` and `Enumerable#including` to conveniently enlarge a collection with more members using a method rather than an operator: - [ 1, 2, 3 ].including(4, 5) => [ 1, 2, 3, 4, 5 ] - post.authors.including(Current.person) => All the authors plus the current person! + [ 1, 2, 3 ].including(4, 5) # => [ 1, 2, 3, 4, 5 ] + post.authors.including(Current.person) # => All the authors plus the current person! *DHH* diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index e55d73c717..bf0fe0f76d 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -34,5 +34,5 @@ Gem::Specification.new do |s| s.add_dependency "tzinfo", "~> 1.1" s.add_dependency "minitest", "~> 5.1" s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2" - s.add_dependency "zeitwerk", "~> 1.3", ">= 1.3.4" + s.add_dependency "zeitwerk", "~> 1.4", ">= 1.4.3" end diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index 10e4c6b09d..ea01e5891c 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -31,16 +31,16 @@ class Array # Returns a new array that includes the passed elements. # - # [ 1, 2, 3 ].including(4, 5) => [ 1, 2, 3, 4, 5 ] - # [ [ 0, 1 ] ].including([ [ 1, 0 ] ]) => [ [ 0, 1 ], [ 1, 0 ] ] + # [ 1, 2, 3 ].including(4, 5) # => [ 1, 2, 3, 4, 5 ] + # [ [ 0, 1 ] ].including([ [ 1, 0 ] ]) # => [ [ 0, 1 ], [ 1, 0 ] ] def including(*elements) self + elements.flatten(1) end # Returns a copy of the Array excluding the specified elements. # - # ["David", "Rafael", "Aaron", "Todd"].excluding("Aaron", "Todd") => ["David", "Rafael"] - # [ [ 0, 1 ], [ 1, 0 ] ].excluding([ [ 1, 0 ] ]) => [ [ 0, 1 ] ] + # ["David", "Rafael", "Aaron", "Todd"].excluding("Aaron", "Todd") # => ["David", "Rafael"] + # [ [ 0, 1 ], [ 1, 0 ] ].excluding([ [ 1, 0 ] ]) # => [ [ 0, 1 ] ] # # Note: This is an optimization of <tt>Enumerable#excluding</tt> that uses <tt>Array#-</tt> # instead of <tt>Array#reject</tt> for performance reasons. diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb index 6258610c98..5013812460 100644 --- a/activesupport/lib/active_support/core_ext/hash/except.rb +++ b/activesupport/lib/active_support/core_ext/hash/except.rb @@ -10,7 +10,7 @@ class Hash # This is useful for limiting a set of parameters to everything but a few known toggles: # @person.update(params[:person].except(:admin)) def except(*keys) - dup.except!(*keys) + slice(*self.keys - keys) end # Removes the given keys from hash and returns it. diff --git a/activesupport/lib/active_support/core_ext/range/compare_range.rb b/activesupport/lib/active_support/core_ext/range/compare_range.rb index 6f6d2a27bb..ea1dc29a76 100644 --- a/activesupport/lib/active_support/core_ext/range/compare_range.rb +++ b/activesupport/lib/active_support/core_ext/range/compare_range.rb @@ -3,9 +3,10 @@ module ActiveSupport module CompareWithRange # Extends the default Range#=== to support range comparisons. - # (1..5) === (1..5) # => true - # (1..5) === (2..3) # => true - # (1..5) === (2..6) # => false + # (1..5) === (1..5) # => true + # (1..5) === (2..3) # => true + # (1..5) === (1...6) # => true + # (1..5) === (2..6) # => false # # The native Range#=== behavior is untouched. # ('a'..'f') === ('c') # => true @@ -13,17 +14,20 @@ module ActiveSupport def ===(value) if value.is_a?(::Range) # 1...10 includes 1..9 but it does not include 1..10. + # 1..10 includes 1...11 but it does not include 1...12. operator = exclude_end? && !value.exclude_end? ? :< : :<= - super(value.first) && value.last.send(operator, last) + value_max = !exclude_end? && value.exclude_end? ? value.max : value.last + super(value.first) && value_max.send(operator, last) else super end end # Extends the default Range#include? to support range comparisons. - # (1..5).include?(1..5) # => true - # (1..5).include?(2..3) # => true - # (1..5).include?(2..6) # => false + # (1..5).include?(1..5) # => true + # (1..5).include?(2..3) # => true + # (1..5).include?(1...6) # => true + # (1..5).include?(2..6) # => false # # The native Range#include? behavior is untouched. # ('a'..'f').include?('c') # => true @@ -31,17 +35,20 @@ module ActiveSupport def include?(value) if value.is_a?(::Range) # 1...10 includes 1..9 but it does not include 1..10. + # 1..10 includes 1...11 but it does not include 1...12. operator = exclude_end? && !value.exclude_end? ? :< : :<= - super(value.first) && value.last.send(operator, last) + value_max = !exclude_end? && value.exclude_end? ? value.max : value.last + super(value.first) && value_max.send(operator, last) else super end end # Extends the default Range#cover? to support range comparisons. - # (1..5).cover?(1..5) # => true - # (1..5).cover?(2..3) # => true - # (1..5).cover?(2..6) # => false + # (1..5).cover?(1..5) # => true + # (1..5).cover?(2..3) # => true + # (1..5).cover?(1...6) # => true + # (1..5).cover?(2..6) # => false # # The native Range#cover? behavior is untouched. # ('a'..'f').cover?('c') # => true @@ -49,8 +56,10 @@ module ActiveSupport def cover?(value) if value.is_a?(::Range) # 1...10 covers 1..9 but it does not cover 1..10. + # 1..10 covers 1...11 but it does not cover 1...12. operator = exclude_end? && !value.exclude_end? ? :< : :<= - super(value.first) && value.last.send(operator, last) + value_max = !exclude_end? && value.exclude_end? ? value.max : value.last + super(value.first) && value_max.send(operator, last) else super end diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 0b40c3d799..638152626b 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -135,10 +135,12 @@ module ActiveSupport #:nodoc: class SafeBuffer < String UNSAFE_STRING_METHODS = %w( capitalize chomp chop delete delete_prefix delete_suffix - downcase gsub lstrip next reverse rstrip slice squeeze strip - sub succ swapcase tr tr_s unicode_normalize upcase + downcase lstrip next reverse rstrip slice squeeze strip + succ swapcase tr tr_s unicode_normalize upcase ) + UNSAFE_STRING_METHODS_WITH_BACKREF = %w(gsub sub) + alias_method :original_concat, :concat private :original_concat @@ -253,11 +255,44 @@ module ActiveSupport #:nodoc: end end + UNSAFE_STRING_METHODS_WITH_BACKREF.each do |unsafe_method| + if unsafe_method.respond_to?(unsafe_method) + class_eval <<-EOT, __FILE__, __LINE__ + 1 + def #{unsafe_method}(*args, &block) # def gsub(*args, &block) + if block # if block + to_str.#{unsafe_method}(*args) { |*params| # to_str.gsub(*args) { |*params| + set_block_back_references(block, $~) # set_block_back_references(block, $~) + block.call(*params) # block.call(*params) + } # } + else # else + to_str.#{unsafe_method}(*args) # to_str.gsub(*args) + end # end + end # end + + def #{unsafe_method}!(*args, &block) # def gsub!(*args, &block) + @html_safe = false # @html_safe = false + if block # if block + super(*args) { |*params| # super(*args) { |*params| + set_block_back_references(block, $~) # set_block_back_references(block, $~) + block.call(*params) # block.call(*params) + } # } + else # else + super # super + end # end + end # end + EOT + end + end + private def html_escape_interpolated_argument(arg) (!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s) end + + def set_block_back_references(block, match_data) + block.binding.eval("proc { |m| $~ = m }").call(match_data) + end end end diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 120768dec5..f09a6271ad 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -170,8 +170,7 @@ class Time options[:hours] = options.fetch(:hours, 0) + 24 * partial_days end - d = to_date.advance(options) - d = d.gregorian if d.julian? + d = to_date.gregorian.advance(options) time_advanced_by_date = change(year: d.year, month: d.month, day: d.day) seconds_to_advance = \ options.fetch(:seconds, 0) + diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index d5d00b5e6e..82f07c085e 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -70,6 +70,11 @@ module ActiveSupport #:nodoc: # only once. All directories in this set must also be present in +autoload_paths+. mattr_accessor :autoload_once_paths, default: [] + # This is a private set that collects all eager load paths during bootstrap. + # Useful for Zeitwerk integration. Its public interface is the config.* path + # accessors of each engine. + mattr_accessor :_eager_load_paths, default: Set.new + # An array of qualified constant names that have been loaded. Adding a name # to this array will cause it to be unloaded the next time Dependencies are # cleared. diff --git a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb index e00307d257..a43d03cf09 100644 --- a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb +++ b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "set" require "active_support/core_ext/string/inflections" module ActiveSupport @@ -21,7 +22,11 @@ module ActiveSupport end def autoloaded_constants - (Rails.autoloaders.main.loaded + Rails.autoloaders.once.loaded).to_a + cpaths = [] + Rails.autoloaders.each do |autoloader| + cpaths.concat(autoloader.loaded_cpaths.to_a) + end + cpaths end def autoloaded?(object) @@ -48,7 +53,7 @@ module ActiveSupport class << self def take_over setup_autoloaders - freeze_autoload_paths + freeze_paths decorate_dependencies end @@ -60,11 +65,11 @@ module ActiveSupport # prevent misconfigurations. next unless File.directory?(autoload_path) - if autoload_once?(autoload_path) - Rails.autoloaders.once.push_dir(autoload_path) - else - Rails.autoloaders.main.push_dir(autoload_path) - end + autoloader = \ + autoload_once?(autoload_path) ? Rails.autoloaders.once : Rails.autoloaders.main + + autoloader.push_dir(autoload_path) + autoloader.do_not_eager_load(autoload_path) unless eager_load?(autoload_path) end Rails.autoloaders.each(&:setup) @@ -74,9 +79,14 @@ module ActiveSupport Dependencies.autoload_once_paths.include?(autoload_path) end - def freeze_autoload_paths + def eager_load?(autoload_path) + Dependencies._eager_load_paths.member?(autoload_path) + end + + def freeze_paths Dependencies.autoload_paths.freeze Dependencies.autoload_once_paths.freeze + Dependencies._eager_load_paths.freeze end def decorate_dependencies diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb index 05236d3162..2dca990712 100644 --- a/activesupport/lib/active_support/descendants_tracker.rb +++ b/activesupport/lib/active_support/descendants_tracker.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "weakref" + module ActiveSupport # This module provides an internal implementation to track descendants # which is faster than iterating through ObjectSpace. @@ -8,7 +10,8 @@ module ActiveSupport class << self def direct_descendants(klass) - @@direct_descendants[klass] || [] + descendants = @@direct_descendants[klass] + descendants ? descendants.to_a : [] end def descendants(klass) @@ -34,15 +37,17 @@ module ActiveSupport # This is the only method that is not thread safe, but is only ever called # during the eager loading phase. def store_inherited(klass, descendant) - (@@direct_descendants[klass] ||= []) << descendant + (@@direct_descendants[klass] ||= DescendantsArray.new) << descendant end private def accumulate_descendants(klass, acc) if direct_descendants = @@direct_descendants[klass] - acc.concat(direct_descendants) - direct_descendants.each { |direct_descendant| accumulate_descendants(direct_descendant, acc) } + direct_descendants.each do |direct_descendant| + acc << direct_descendant + accumulate_descendants(direct_descendant, acc) + end end end end @@ -59,5 +64,46 @@ module ActiveSupport def descendants DescendantsTracker.descendants(self) end + + # DescendantsArray is an array that contains weak references to classes. + class DescendantsArray # :nodoc: + include Enumerable + + def initialize + @refs = [] + end + + def initialize_copy(orig) + @refs = @refs.dup + end + + def <<(klass) + cleanup! + @refs << WeakRef.new(klass) + end + + def each + @refs.each do |ref| + yield ref.__getobj__ + rescue WeakRef::RefError + end + end + + def refs_size + @refs.size + end + + def cleanup! + @refs.delete_if { |ref| !ref.weakref_alive? } + end + + def reject! + @refs.reject! do |ref| + yield ref.__getobj__ + rescue WeakRef::RefError + true + end + end + end end end diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 3a2b2652c4..42ae7e9b7b 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -225,8 +225,8 @@ module ActiveSupport # hash[:a] = 'x' # hash[:b] = 'y' # hash.values_at('a', 'b') # => ["x", "y"] - def values_at(*indices) - indices.collect { |key| self[convert_key(key)] } + def values_at(*keys) + super(*keys.map { |key| convert_key(key) }) end # Returns an array of the values at the specified indices, but also @@ -239,7 +239,7 @@ module ActiveSupport # hash.fetch_values('a', 'c') { |key| 'z' } # => ["x", "z"] # hash.fetch_values('a', 'c') # => KeyError: key not found: "c" def fetch_values(*indices, &block) - indices.collect { |key| fetch(key, &block) } + super(*indices.map { |key| convert_key(key) }, &block) end # Returns a shallow copy of the hash. @@ -293,6 +293,9 @@ module ActiveSupport super(convert_key(key)) end + def except(*keys) + slice(*self.keys - keys.map { |key| convert_key(key) }) + end alias_method :without, :except def stringify_keys!; self end diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index 00a57c38c9..a03e7e483e 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -13,14 +13,15 @@ module ActiveSupport @notifier = notifier end - # Instrument the given block by measuring the time taken to execute it - # and publish it. Notice that events get sent even if an error occurs - # in the passed-in block. + # Given a block, instrument it by measuring the time taken to execute + # and publish it. Without a block, simply send a message via the + # notifier. Notice that events get sent even if an error occurs in the + # passed-in block. def instrument(name, payload = {}) # some of the listeners might have state listeners_state = start name, payload begin - yield payload + yield payload if block_given? rescue Exception => e payload[:exception] = [e.class.name, e.message] payload[:exception_object] = e diff --git a/activesupport/lib/active_support/security_utils.rb b/activesupport/lib/active_support/security_utils.rb index 20b6b9cd3f..5e455fca57 100644 --- a/activesupport/lib/active_support/security_utils.rb +++ b/activesupport/lib/active_support/security_utils.rb @@ -24,7 +24,7 @@ module ActiveSupport # The values are first processed by SHA256, so that we don't leak length info # via timing attacks. def secure_compare(a, b) - fixed_length_secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b)) && a == b + fixed_length_secure_compare(::Digest::SHA256.digest(a), ::Digest::SHA256.digest(b)) && a == b end module_function :secure_compare end diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index 62356e4d46..01e60abd99 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -40,3 +40,5 @@ class ActiveSupport::TestCase skip message if defined?(JRUBY_VERSION) end end + +require_relative "../../tools/test_common" diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb index 4b8efb8a93..16d6a4c2f2 100644 --- a/activesupport/test/core_ext/range_ext_test.rb +++ b/activesupport/test/core_ext/range_ext_test.rb @@ -57,7 +57,7 @@ class RangeTest < ActiveSupport::TestCase end def test_should_include_other_with_exclusive_end - assert((1..10).include?(1...10)) + assert((1..10).include?(1...11)) end def test_should_compare_identical_inclusive @@ -69,7 +69,7 @@ class RangeTest < ActiveSupport::TestCase end def test_should_compare_other_with_exclusive_end - assert((1..10) === (1...10)) + assert((1..10) === (1...11)) end def test_exclusive_end_should_not_include_identical_with_inclusive_end @@ -93,6 +93,10 @@ class RangeTest < ActiveSupport::TestCase assert range.method(:include?) != range.method(:cover?) end + def test_should_cover_other_with_exclusive_end + assert((1..10).cover?(1...11)) + end + def test_overlaps_on_time time_range_1 = Time.utc(2005, 12, 10, 15, 30)..Time.utc(2005, 12, 10, 17, 30) time_range_2 = Time.utc(2005, 12, 10, 17, 00)..Time.utc(2005, 12, 10, 18, 00) diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 7078f3506d..590b81b770 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -514,6 +514,8 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(1582, 10, 15, 15, 15, 10), Time.local(1582, 10, 14, 15, 15, 10).advance(days: 1) assert_equal Time.local(1582, 10, 5, 15, 15, 10), Time.local(1582, 10, 4, 15, 15, 10).advance(days: 1) assert_equal Time.local(1582, 10, 4, 15, 15, 10), Time.local(1582, 10, 5, 15, 15, 10).advance(days: -1) + assert_equal Time.local(999, 10, 4, 15, 15, 10), Time.local(1000, 10, 4, 15, 15, 10).advance(years: -1) + assert_equal Time.local(1000, 10, 4, 15, 15, 10), Time.local(999, 10, 4, 15, 15, 10).advance(years: 1) end def test_last_week diff --git a/activesupport/test/descendants_tracker_test_cases.rb b/activesupport/test/descendants_tracker_test_cases.rb index 2c94c3c56c..f8752688d2 100644 --- a/activesupport/test/descendants_tracker_test_cases.rb +++ b/activesupport/test/descendants_tracker_test_cases.rb @@ -27,6 +27,15 @@ module DescendantsTrackerTestCases assert_equal_sets [], Child2.descendants end + def test_descendants_with_garbage_collected_classes + 1.times do + child_klass = Class.new(Parent) + assert_equal_sets [Child1, Grandchild1, Grandchild2, Child2, child_klass], Parent.descendants + end + GC.start + assert_equal_sets [Child1, Grandchild1, Grandchild2, Child2], Parent.descendants + end + def test_direct_descendants assert_equal_sets [Child1, Child2], Parent.direct_descendants assert_equal_sets [Grandchild1, Grandchild2], Child1.direct_descendants diff --git a/activesupport/test/notifications/instrumenter_test.rb b/activesupport/test/notifications/instrumenter_test.rb index d5c9e82e9f..9729ad5c89 100644 --- a/activesupport/test/notifications/instrumenter_test.rb +++ b/activesupport/test/notifications/instrumenter_test.rb @@ -44,6 +44,12 @@ module ActiveSupport assert_equal Hash[result: 2], payload end + def test_instrument_works_without_a_block + instrumenter.instrument("no.block", payload) + assert_equal 1, notifier.finishes.size + assert_equal "no.block", notifier.finishes.first.first + end + def test_start instrumenter.start("foo", payload) assert_equal [["foo", instrumenter.id, payload]], notifier.starts diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb index 32e1d2d5bf..b1a1c2d390 100644 --- a/activesupport/test/safe_buffer_test.rb +++ b/activesupport/test/safe_buffer_test.rb @@ -248,4 +248,22 @@ class SafeBufferTest < ActiveSupport::TestCase x = "Hello".html_safe assert_nil x[/a/, 1] end + + test "Should set back references" do + a = "foo123".html_safe + a2 = a.sub(/([a-z]+)([0-9]+)/) { $2 + $1 } + assert_equal "123foo", a2 + assert_not_predicate a2, :html_safe? + a.sub!(/([a-z]+)([0-9]+)/) { $2 + $1 } + assert_equal "123foo", a + assert_not_predicate a, :html_safe? + + b = "foo123 bar456".html_safe + b2 = b.gsub(/([a-z]+)([0-9]+)/) { $2 + $1 } + assert_equal "123foo 456bar", b2 + assert_not_predicate b2, :html_safe? + b.gsub!(/([a-z]+)([0-9]+)/) { $2 + $1 } + assert_equal "123foo 456bar", b + assert_not_predicate b, :html_safe? + end end |