diff options
Diffstat (limited to 'activesupport')
97 files changed, 1005 insertions, 456 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index ccdf2c3040..f20c7c92e6 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,63 @@ +* Add `compact_blank` for those times when you want to remove #blank? values from + an Enumerable (also `compact_blank!` on Hash, Array, ActionController::Parameters) + + *Dana Sherson* + +* Make ActiveSupport::Logger Fiber-safe. Fixes #36752. + + Use `Fiber.current.__id__` in `ActiveSupport::Logger#local_level=` in order + to make log level local to Ruby Fibers in addition to Threads. + + Example: + + logger = ActiveSupport::Logger.new(STDOUT) + logger.level = 1 + p "Main is debug? #{logger.debug?}" + + Fiber.new { + logger.local_level = 0 + p "Thread is debug? #{logger.debug?}" + }.resume + + p "Main is debug? #{logger.debug?}" + + Before: + + Main is debug? false + Thread is debug? true + Main is debug? true + + After: + + Main is debug? false + Thread is debug? true + Main is debug? false + + *Alexander Varnin* + +* Allow the `on_rotation` proc used when decrypting/verifying a message to be + passed at the constructor level. + + Before: + + crypt = ActiveSupport::MessageEncryptor.new('long_secret') + crypt.decrypt_and_verify(encrypted_message, on_rotation: proc { ... }) + crypt.decrypt_and_verify(another_encrypted_message, on_rotation: proc { ... }) + + After: + + crypt = ActiveSupport::MessageEncryptor.new('long_secret', on_rotation: proc { ... }) + crypt.decrypt_and_verify(encrypted_message) + crypt.decrypt_and_verify(another_encrypted_message) + + *Edouard Chin* + +* `delegate_missing_to` would raise a `DelegationError` if the object + delegated to was `nil`. Now the `allow_nil` option has been added to enable + the user to specify they want `nil` returned in this case. + + *Matthew Tanous* + * `truncate` would return the original string if it was too short to be truncated and a frozen string if it were long enough to be truncated. Now truncate will consistently return an unfrozen string regardless. This behavior is consistent @@ -5,18 +65,19 @@ Before: - 'foobar'.truncate(5).frozen? - => true - 'foobar'.truncate(6).frozen? - => false + 'foobar'.truncate(5).frozen? + # => true + 'foobar'.truncate(6).frozen? + # => false After: - 'foobar'.truncate(5).frozen? - => false - 'foobar'.truncate(6).frozen? - => false + 'foobar'.truncate(5).frozen? + # => false + 'foobar'.truncate(6).frozen? + # => false *Jordan Thomas* + Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activesupport/CHANGELOG.md) for previous changes. diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 7ecbafc911..da295a0d35 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", "~> 2.1", ">= 2.1.4" + s.add_dependency "zeitwerk", "~> 2.1", ">= 2.1.8" end diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index 02cbfbaee6..6273012808 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -16,7 +16,7 @@ module ActiveSupport # # bc = ActiveSupport::BacktraceCleaner.new # bc.add_filter { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix - # bc.add_silencer { |line| line =~ /puma|rubygems/ } # skip any lines from puma or rubygems + # bc.add_silencer { |line| /puma|rubygems/.match?(line) } # skip any lines from puma or rubygems # bc.clean(exception.backtrace) # perform the cleanup # # To reconfigure an existing BacktraceCleaner (like the default one in Rails) @@ -65,7 +65,7 @@ module ActiveSupport # for a given line, it will be excluded from the clean backtrace. # # # Will reject all lines that include the word "puma", like "/gems/puma/server.rb" or "/app/my_puma_server/rb" - # backtrace_cleaner.add_silencer { |line| line =~ /puma/ } + # backtrace_cleaner.add_silencer { |line| /puma/.match?(line) } def add_silencer(&block) @silencers << block end @@ -85,7 +85,6 @@ module ActiveSupport end private - FORMATTED_GEMS_PATTERN = /\A[^\/]+ \([\w.]+\) / def add_gem_filter diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index e055135bb4..a5063d0784 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -678,18 +678,15 @@ module ActiveSupport end def instrument(operation, key, options = nil) - log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" } + if logger && logger.debug? && !silence? + logger.debug "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" + end payload = { key: key } payload.merge!(options) if options.is_a?(Hash) ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) } end - def log - return unless logger && logger.debug? && !silence? - logger.debug(yield) - end - def handle_expired_entry(entry, key, options) if entry && entry.expired? race_ttl = options[:race_condition_ttl].to_i diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index f43894a1ea..925440a23f 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -72,7 +72,6 @@ module ActiveSupport end private - def read_entry(key, options) if File.exist?(key) File.open(key) { |f| Marshal.load(f) } diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb index 629eb2dd70..fa24da91b4 100644 --- a/activesupport/lib/active_support/cache/memory_store.rb +++ b/activesupport/lib/active_support/cache/memory_store.rb @@ -114,7 +114,6 @@ module ActiveSupport end private - PER_ENTRY_OVERHEAD = 240 def cached_size(key, entry) diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index 39b32fc7f6..8e80946fbb 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "active_support/core_ext/object/duplicable" require "active_support/core_ext/string/inflections" require "active_support/per_thread_registry" @@ -75,7 +74,10 @@ module ActiveSupport end def fetch_entry(key, options = nil) # :nodoc: - @data.fetch(key) { @data[key] = yield } + entry = @data.fetch(key) { @data[key] = yield } + dup_entry = entry.dup + dup_entry&.dup_value! + dup_entry end end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index d0644a0f7e..daf98c9528 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -7,7 +7,6 @@ require "active_support/core_ext/class/attribute" require "active_support/core_ext/kernel/reporting" require "active_support/core_ext/kernel/singleton_class" require "active_support/core_ext/string/filters" -require "active_support/deprecation" require "thread" module ActiveSupport @@ -142,7 +141,6 @@ module ActiveSupport end private - # A hook invoked every time a before callback is halted. # This can be overridden in ActiveSupport::Callbacks implementors in order # to provide better debugging/logging. @@ -582,7 +580,6 @@ module ActiveSupport attr_reader :chain private - def append_one(callback) @callbacks = nil remove_duplicates(callback) @@ -843,7 +840,6 @@ module ActiveSupport end protected - def get_callbacks(name) # :nodoc: __callbacks[name.to_sym] end diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb index f18ccf1c88..eae7d4469f 100644 --- a/activesupport/lib/active_support/concurrency/share_lock.rb +++ b/activesupport/lib/active_support/concurrency/share_lock.rb @@ -200,7 +200,6 @@ module ActiveSupport end private - # Must be called within synchronize def busy_for_exclusive?(purpose) busy_for_sharing?(purpose) || diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb index e2e11545e2..c7a2378e41 100644 --- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb @@ -20,21 +20,11 @@ module DateAndTime advance(days: -1) end - # Returns a new date/time the specified number of days ago. - def prev_day(days = 1) - advance(days: -days) - end - # Returns a new date/time representing tomorrow. def tomorrow advance(days: 1) end - # Returns a new date/time the specified number of days in the future. - def next_day(days = 1) - advance(days: days) - end - # Returns true if the date/time is today. def today? to_date == ::Date.current @@ -198,21 +188,11 @@ module DateAndTime end end - # Returns a new date/time the specified number of months in the future. - def next_month(months = 1) - advance(months: months) - end - # Short-hand for months_since(3) def next_quarter months_since(3) end - # Returns a new date/time the specified number of years in the future. - def next_year(years = 1) - advance(years: years) - end - # Returns a new date/time representing the given day in the previous week. # Week is assumed to start on +start_day+, default is # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. @@ -233,11 +213,6 @@ module DateAndTime end alias_method :last_weekday, :prev_weekday - # Returns a new date/time the specified number of months ago. - def prev_month(months = 1) - advance(months: -months) - end - # Short-hand for months_ago(1). def last_month months_ago(1) @@ -249,11 +224,6 @@ module DateAndTime end alias_method :last_quarter, :prev_quarter - # Returns a new date/time the specified number of years ago. - def prev_year(years = 1) - advance(years: -years) - end - # Short-hand for years_ago(1). def last_year years_ago(1) diff --git a/activesupport/lib/active_support/core_ext/date_and_time/zones.rb b/activesupport/lib/active_support/core_ext/date_and_time/zones.rb index 894fd9b76d..fb6a27cb27 100644 --- a/activesupport/lib/active_support/core_ext/date_and_time/zones.rb +++ b/activesupport/lib/active_support/core_ext/date_and_time/zones.rb @@ -29,7 +29,6 @@ module DateAndTime end private - def time_with_zone(time, zone) if time ActiveSupport::TimeWithZone.new(time.utc? ? time : time.getutc, zone) diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb index 29725c89f7..231bf870a2 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -96,7 +96,6 @@ class DateTime end private - def offset_in_seconds (offset * 86400).to_i end diff --git a/activesupport/lib/active_support/core_ext/digest.rb b/activesupport/lib/active_support/core_ext/digest.rb new file mode 100644 index 0000000000..ce1427e13a --- /dev/null +++ b/activesupport/lib/active_support/core_ext/digest.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "active_support/core_ext/digest/uuid" diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 4675c41936..728d332306 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -148,6 +148,41 @@ module Enumerable map { |element| element[keys.first] } end end + + # Returns a new +Array+ without the blank items. + # Uses Object#blank? for determining if an item is blank. + # + # [1, "", nil, 2, " ", [], {}, false, true].compact_blank + # # => [1, 2, true] + # + # Set.new([nil, "", 1, 2]) + # # => [2, 1] (or [1, 2]) + # + # When called on a +Hash+, returns a new +Hash+ without the blank values. + # + # { a: "", b: 1, c: nil, d: [], e: false, f: true }.compact_blank + # #=> { b: 1, f: true } + def compact_blank + reject(&:blank?) + end +end + +class Hash + # Hash#reject has its own definition, so this needs one too. + def compact_blank #:nodoc: + reject { |_k, v| v.blank? } + end + + # Removes all blank values from the +Hash+ in place and returns self. + # Uses Object#blank? for determining if a value is blank. + # + # h = { a: "", b: 1, c: nil, d: [], e: false, f: true } + # h.compact_blank! + # # => { b: 1, f: true } + def compact_blank! + # use delete_if rather than reject! because it always returns self even if nothing changed + delete_if { |_k, v| v.blank? } + end end class Range #:nodoc: @@ -185,4 +220,15 @@ class Array #:nodoc: super end end + + # Removes all blank elements from the +Array+ in place and returns self. + # Uses Object#blank? for determining if an item is blank. + # + # a = [1, "", nil, 2, " ", [], {}, false, true] + # a.compact_blank! + # # => [1, 2, true] + def compact_blank! + # use delete_if rather than reject! because it always returns self even if nothing changed + delete_if(&:blank?) + end end diff --git a/activesupport/lib/active_support/core_ext/hash/deep_transform_values.rb b/activesupport/lib/active_support/core_ext/hash/deep_transform_values.rb index 720a1f67c8..18acb1e70c 100644 --- a/activesupport/lib/active_support/core_ext/hash/deep_transform_values.rb +++ b/activesupport/lib/active_support/core_ext/hash/deep_transform_values.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class Hash - # Returns a new hash with all keys converted by the block operation. - # This includes the keys from the root hash and from all + # Returns a new hash with all values converted by the block operation. + # This includes the values from the root hash and from all # nested hashes and arrays. # # hash = { person: { name: 'Rob', age: '28' } } diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 9fe7f8fe01..14d7f0c484 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -205,18 +205,18 @@ class Module if allow_nil method_def = [ "def #{method_prefix}#{method}(#{definition})", - "_ = #{to}", - "if !_.nil? || nil.respond_to?(:#{method})", - " _.#{method}(#{definition})", - "end", - "end" + " _ = #{to}", + " if !_.nil? || nil.respond_to?(:#{method})", + " _.#{method}(#{definition})", + " end", + "end" ].join ";" else exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") method_def = [ "def #{method_prefix}#{method}(#{definition})", - " _ = #{to}", + " _ = #{to}", " _.#{method}(#{definition})", "rescue NoMethodError => e", " if _.nil? && e.name == :#{method}", @@ -274,8 +274,14 @@ class Module # variables, methods, constants, etc. # # The delegated method must be public on the target, otherwise it will - # raise +NoMethodError+. - def delegate_missing_to(target) + # raise +DelegationError+. If you wish to instead return +nil+, + # use the <tt>:allow_nil</tt> option. + # + # The <tt>marshal_dump</tt> and <tt>_dump</tt> methods are exempt from + # delegation due to possible interference when calling + # <tt>Marshal.dump(object)</tt>, should the delegation target method + # of <tt>object</tt> add or remove instance variables. + def delegate_missing_to(target, allow_nil: nil) target = target.to_s target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target) @@ -284,6 +290,7 @@ class Module # It may look like an oversight, but we deliberately do not pass # +include_private+, because they do not get delegated. + return false if name == :marshal_dump || name == :_dump #{target}.respond_to?(name) || super end @@ -295,7 +302,11 @@ class Module super rescue NoMethodError if #{target}.nil? - raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil" + if #{allow_nil == true} + nil + else + raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil" + end else raise end diff --git a/activesupport/lib/active_support/core_ext/name_error.rb b/activesupport/lib/active_support/core_ext/name_error.rb index 6d37cd9dfd..c51aec0e03 100644 --- a/activesupport/lib/active_support/core_ext/name_error.rb +++ b/activesupport/lib/active_support/core_ext/name_error.rb @@ -15,7 +15,7 @@ class NameError # We should use original_message message instead. message = respond_to?(:original_message) ? original_message : self.message - if /undefined local variable or method/ !~ message + unless /undefined local variable or method/.match?(message) $1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message end end diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index c78ee6bbfc..3ebcdca02b 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -28,96 +28,6 @@ class Object end end -class NilClass - begin - nil.dup - rescue TypeError - - # +nil+ is not duplicable: - # - # nil.duplicable? # => false - # nil.dup # => TypeError: can't dup NilClass - def duplicable? - false - end - end -end - -class FalseClass - begin - false.dup - rescue TypeError - - # +false+ is not duplicable: - # - # false.duplicable? # => false - # false.dup # => TypeError: can't dup FalseClass - def duplicable? - false - end - end -end - -class TrueClass - begin - true.dup - rescue TypeError - - # +true+ is not duplicable: - # - # true.duplicable? # => false - # true.dup # => TypeError: can't dup TrueClass - def duplicable? - false - end - end -end - -class Symbol - begin - :symbol.dup - - # Some symbols couldn't be duped in Ruby 2.4.0 only, due to a bug. - # This feature check catches any regression. - "symbol_from_string".to_sym.dup - rescue TypeError - - # Symbols are not duplicable: - # - # :my_symbol.duplicable? # => false - # :my_symbol.dup # => TypeError: can't dup Symbol - def duplicable? - false - end - end -end - -class Numeric - begin - 1.dup - rescue TypeError - - # Numbers are not duplicable: - # - # 3.duplicable? # => false - # 3.dup # => TypeError: can't dup Integer - def duplicable? - false - end - end -end - -require "bigdecimal" -class BigDecimal - # BigDecimals are duplicable: - # - # BigDecimal("1.2").duplicable? # => true - # BigDecimal("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)> - def duplicable? - true - end -end - class Method # Methods are not duplicable: # @@ -128,32 +38,12 @@ class Method end end -class Complex - begin - Complex(1).dup - rescue TypeError - - # Complexes are not duplicable: - # - # Complex(1).duplicable? # => false - # Complex(1).dup # => TypeError: can't copy Complex - def duplicable? - false - end - end -end - -class Rational - begin - Rational(1).dup - rescue TypeError - - # Rationals are not duplicable: - # - # Rational(1).duplicable? # => false - # Rational(1).dup # => TypeError: can't copy Rational - def duplicable? - false - end +class UnboundMethod + # Unbound methods are not duplicable: + # + # method(:puts).unbind.duplicable? # => false + # method(:puts).unbind.dup # => TypeError: allocator undefined for UnboundMethod + def duplicable? + false end end diff --git a/activesupport/lib/active_support/core_ext/range/each.rb b/activesupport/lib/active_support/core_ext/range/each.rb index 2f22cd0e92..2d86997edf 100644 --- a/activesupport/lib/active_support/core_ext/range/each.rb +++ b/activesupport/lib/active_support/core_ext/range/each.rb @@ -15,7 +15,6 @@ module ActiveSupport end private - def ensure_iteration_allowed raise TypeError, "can't iterate from #{first.class}" if first.is_a?(TimeWithZone) end diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb index 4ca24028b0..1a0b10b4f6 100644 --- a/activesupport/lib/active_support/core_ext/string/access.rb +++ b/activesupport/lib/active_support/core_ext/string/access.rb @@ -44,7 +44,7 @@ class String # str.from(0).to(-1) # => "hello" # str.from(1).to(-2) # => "ell" def from(position) - self[position..-1] + self[position, length] end # Returns a substring from the beginning of the string to the given position. @@ -61,7 +61,8 @@ class String # str.from(0).to(-1) # => "hello" # str.from(1).to(-2) # => "ell" def to(position) - self[0..position] + position += size if position < 0 + self[0, position + 1] || +"" end # Returns the first character. If a limit is supplied, returns a substring @@ -75,17 +76,7 @@ class String # str.first(0) # => "" # str.first(6) # => "hello" def first(limit = 1) - ActiveSupport::Deprecation.warn( - "Calling String#first with a negative integer limit " \ - "will raise an ArgumentError in Rails 6.1." - ) if limit < 0 - if limit == 0 - "" - elsif limit >= size - dup - else - to(limit - 1) - end + self[0, limit] || (raise ArgumentError, "negative limit") end # Returns the last character of the string. If a limit is supplied, returns a substring @@ -99,16 +90,6 @@ class String # str.last(0) # => "" # str.last(6) # => "hello" def last(limit = 1) - ActiveSupport::Deprecation.warn( - "Calling String#last with a negative integer limit " \ - "will raise an ArgumentError in Rails 6.1." - ) if limit < 0 - if limit == 0 - "" - elsif limit >= size - dup - else - from(-limit) - end + self[[length - limit, 0].max, limit] || (raise ArgumentError, "negative limit") end 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 645b1fea17..f48ea56326 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -291,7 +291,6 @@ module ActiveSupport #:nodoc: end private - def html_escape_interpolated_argument(arg) (!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s) end diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index f09a6271ad..eed34965e3 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -311,4 +311,34 @@ class Time end alias_method :eql_without_coercion, :eql? alias_method :eql?, :eql_with_coercion + + # Returns a new time the specified number of days ago. + def prev_day(days = 1) + advance(days: -days) + end + + # Returns a new time the specified number of days in the future. + def next_day(days = 1) + advance(days: days) + end + + # Returns a new time the specified number of months ago. + def prev_month(months = 1) + advance(months: -months) + end + + # Returns a new time the specified number of months in the future. + def next_month(months = 1) + advance(months: months) + end + + # Returns a new time the specified number of years ago. + def prev_year(years = 1) + advance(years: -years) + end + + # Returns a new time the specified number of years in the future. + def next_year(years = 1) + advance(years: years) + end end diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 82f07c085e..6d71a67a46 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -20,6 +20,9 @@ module ActiveSupport #:nodoc: module Dependencies #:nodoc: extend self + UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name) + private_constant :UNBOUND_METHOD_MODULE_NAME + mattr_accessor :interlock, default: Interlock.new # :doc: @@ -201,6 +204,11 @@ module ActiveSupport #:nodoc: end end + def self.include_into(base) + base.include(self) + append_features(base) + end + def const_missing(const_name) from_mod = anonymous? ? guess_for_anonymous(const_name) : self Dependencies.load_missing_constant(from_mod, const_name) @@ -230,6 +238,21 @@ module ActiveSupport #:nodoc: base.class_eval do define_method(:load, Kernel.instance_method(:load)) private :load + + define_method(:require, Kernel.instance_method(:require)) + private :require + end + end + + def self.include_into(base) + base.include(self) + + if base.instance_method(:load).owner == base + base.remove_method(:load) + end + + if base.instance_method(:require).owner == base + base.remove_method(:require) end end @@ -290,7 +313,6 @@ module ActiveSupport #:nodoc: end private - def load(file, wrap = false) result = false load_dependency(file) { result = super } @@ -326,9 +348,9 @@ module ActiveSupport #:nodoc: end def hook! - Object.class_eval { include Loadable } - Module.class_eval { include ModuleConstMissing } - Exception.class_eval { include Blamable } + Loadable.include_into(Object) + ModuleConstMissing.include_into(Module) + Exception.include(Blamable) end def unhook! @@ -639,7 +661,7 @@ module ActiveSupport #:nodoc: # Determine if the given constant has been automatically loaded. def autoloaded?(desc) - return false if desc.is_a?(Module) && desc.anonymous? + return false if desc.is_a?(Module) && real_mod_name(desc).nil? name = to_constant_name desc return false unless qualified_const_defined?(name) autoloaded_constants.include?(name) @@ -695,7 +717,7 @@ module ActiveSupport #:nodoc: when String then desc.sub(/^::/, "") when Symbol then desc.to_s when Module - desc.name || + real_mod_name(desc) || raise(ArgumentError, "Anonymous modules have no name to be referenced by") else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}" end @@ -769,6 +791,13 @@ module ActiveSupport #:nodoc: def log(message) logger.debug("autoloading: #{message}") if logger && verbose end + + private + # Returns the original name of a class or module even if `name` has been + # overridden. + def real_mod_name(mod) + UNBOUND_METHOD_MODULE_NAME.bind(mod).call + end end end diff --git a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb index fd39ad6e55..2cf55713bd 100644 --- a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb +++ b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb @@ -28,7 +28,7 @@ module ActiveSupport end def autoloaded?(object) - cpath = object.is_a?(Module) ? object.name : object.to_s + cpath = object.is_a?(Module) ? real_mod_name(object) : object.to_s Rails.autoloaders.main.unloadable_cpath?(cpath) end @@ -42,6 +42,17 @@ module ActiveSupport end end + module RequireDependency + def require_dependency(filename) + filename = filename.to_path if filename.respond_to?(:to_path) + if abspath = ActiveSupport::Dependencies.search_for_file(filename) + require abspath + else + require filename + end + end + end + module Inflector def self.camelize(basename, _abspath) basename.camelize @@ -56,7 +67,6 @@ module ActiveSupport end private - def setup_autoloaders(enable_reloading) Dependencies.autoload_paths.each do |autoload_path| # Zeitwerk only accepts existing directories in `push_dir` to @@ -91,7 +101,7 @@ module ActiveSupport def decorate_dependencies Dependencies.unhook! Dependencies.singleton_class.prepend(Decorations) - Object.class_eval { alias_method :require_dependency, :require } + Object.prepend(RequireDependency) end end end diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index 56f1e23136..d7d3c30b97 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -120,9 +120,16 @@ module ActiveSupport # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead. # (Backtrace information…) # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] - class DeprecatedConstantProxy < DeprecationProxy + class DeprecatedConstantProxy < Module + def self.new(*args, &block) + object = args.first + + return object unless object + super + end + def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance, message: "#{old_const} is deprecated! Use #{new_const} instead.") - require "active_support/inflector/methods" + Kernel.require "active_support/inflector/methods" @old_const = old_const @new_const = new_const @@ -130,6 +137,14 @@ module ActiveSupport @message = message end + instance_methods.each { |m| undef_method m unless /^__|^object_id$/.match?(m) } + + # Don't give a deprecation warning on inspect since test/unit and error + # logs rely on it for diagnostics. + def inspect + target.inspect + end + # Returns the class of the new constant. # # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune) @@ -144,8 +159,14 @@ module ActiveSupport ActiveSupport::Inflector.constantize(@new_const.to_s) end - def warn(callstack, called, args) - @deprecator.warn(@message, callstack) + def const_missing(name) + @deprecator.warn(@message, caller_locations) + target.const_get(name) + end + + def method_missing(called, *args, &block) + @deprecator.warn(@message, caller_locations) + target.__send__(called, *args, &block) end end end diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb index 21565138a7..b14842bf67 100644 --- a/activesupport/lib/active_support/descendants_tracker.rb +++ b/activesupport/lib/active_support/descendants_tracker.rb @@ -41,7 +41,6 @@ module ActiveSupport end private - def accumulate_descendants(klass, acc) if direct_descendants = @@direct_descendants[klass] direct_descendants.each do |direct_descendant| @@ -78,15 +77,17 @@ module ActiveSupport end def <<(klass) - cleanup! @refs << WeakRef.new(klass) end def each - @refs.each do |ref| + @refs.reject! do |ref| yield ref.__getobj__ + false rescue WeakRef::RefError + true end + self end def refs_size diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index a30bd11a87..2b4f1288f1 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -198,7 +198,6 @@ module ActiveSupport end private - def calculate_total_seconds(parts) parts.inject(0) do |total, (part, value)| total + value * PARTS_IN_SECONDS[part] @@ -399,7 +398,6 @@ module ActiveSupport end private - def sum(sign, time = ::Time.current) parts.inject(time) do |t, (type, number)| if t.acts_like?(:time) || t.acts_like?(:date) diff --git a/activesupport/lib/active_support/duration/iso8601_parser.rb b/activesupport/lib/active_support/duration/iso8601_parser.rb index d3233e6111..83f3b28602 100644 --- a/activesupport/lib/active_support/duration/iso8601_parser.rb +++ b/activesupport/lib/active_support/duration/iso8601_parser.rb @@ -80,7 +80,6 @@ module ActiveSupport end private - def finished? scanner.eos? end diff --git a/activesupport/lib/active_support/duration/iso8601_serializer.rb b/activesupport/lib/active_support/duration/iso8601_serializer.rb index 1125454919..8314df12b0 100644 --- a/activesupport/lib/active_support/duration/iso8601_serializer.rb +++ b/activesupport/lib/active_support/duration/iso8601_serializer.rb @@ -32,7 +32,6 @@ module ActiveSupport end private - # Return pair of duration's parts and whole duration sign. # Parts are summarized (as they can become repetitive due to addition, etc). # Zero parts are removed as not significant. diff --git a/activesupport/lib/active_support/evented_file_update_checker.rb b/activesupport/lib/active_support/evented_file_update_checker.rb index 3893b0de0e..6075e0a3d3 100644 --- a/activesupport/lib/active_support/evented_file_update_checker.rb +++ b/activesupport/lib/active_support/evented_file_update_checker.rb @@ -107,13 +107,23 @@ module ActiveSupport private def boot! - Listen.to(*@dtw, &method(:changed)).start + normalize_dirs! + + unless @dtw.empty? + Listen.to(*@dtw, &method(:changed)).start + end end def shutdown! Listen.stop end + def normalize_dirs! + @dirs.transform_keys! do |dir| + dir.exist? ? dir.realpath : dir + end + end + def changed(modified, added, removed) unless updated? @updated.make_true if (modified + added + removed).any? { |f| watching?(f) } @@ -187,13 +197,6 @@ module ActiveSupport lcsp end - # Returns the deepest existing ascendant, which could be the argument itself. - def existing_parent(dir) - dir.ascend do |ascendant| - break ascendant if ascendant.directory? - end - end - # Filters out directories which are descendants of others in the collection (stable). def filter_out_descendants(dirs) return dirs if dirs.length < 2 @@ -214,7 +217,6 @@ module ActiveSupport end private - def ascendant_of?(base, other) base != other && other.ascend do |ascendant| break true if base == ascendant diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index 1a0bb10815..9b665e7f19 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -98,7 +98,6 @@ module ActiveSupport end private - def watched @watched || begin all = @files.select { |f| File.exist?(f) } diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 5981763f0e..6acf64cb39 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -367,18 +367,20 @@ module ActiveSupport key.kind_of?(Symbol) ? key.to_s : key end - def convert_value(value, options = {}) # :doc: + def convert_value(value, for: nil) # :doc: + conversion = binding.local_variable_get(:for) + if value.is_a? Hash - if options[:for] == :to_hash + if conversion == :to_hash value.to_hash else value.nested_under_indifferent_access end elsif value.is_a?(Array) - if options[:for] != :assignment || value.frozen? + if conversion != :assignment || value.frozen? value = value.dup end - value.map! { |e| convert_value(e, options) } + value.map! { |e| convert_value(e, for: conversion) } else value end diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index 88cdd99dbd..efee74a1df 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -2,7 +2,6 @@ require "concurrent/map" require "active_support/i18n" -require "active_support/deprecation" module ActiveSupport module Inflector @@ -230,7 +229,6 @@ module ActiveSupport end private - def define_acronym_regex_patterns @acronym_regex = @acronyms.empty? ? /(?=a)b/ : /#{@acronyms.values.join("|")}/ @acronyms_camelize_regex = /^(?:#{@acronym_regex}(?=\b|[A-Z_])|\w)/ diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index ee193add6f..18f3f53879 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -359,7 +359,6 @@ module ActiveSupport end private - # Mounts a regular expression, returned as a string to ease interpolation, # that will match part by part the given constant. # diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index ec6e9ccb59..0751f8a3ad 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -56,14 +56,39 @@ module ActiveSupport # # transliterate('Jürgen', locale: :de) # # => "Juergen" + # + # Transliteration is restricted to UTF-8, US-ASCII and GB18030 strings + # Other encodings will raise an ArgumentError. def transliterate(string, replacement = "?", locale: nil) raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String) - I18n.transliterate( + allowed_encodings = [Encoding::UTF_8, Encoding::US_ASCII, Encoding::GB18030] + raise ArgumentError, "Can not transliterate strings with #{string.encoding} encoding" unless allowed_encodings.include?(string.encoding) + + input_encoding = string.encoding + + # US-ASCII is a subset of UTF-8 so we'll force encoding as UTF-8 if + # US-ASCII is given. This way we can let tidy_bytes handle the string + # in the same way as we do for UTF-8 + string.force_encoding(Encoding::UTF_8) if string.encoding == Encoding::US_ASCII + + # GB18030 is Unicode compatible but is not a direct mapping so needs to be + # transcoded. Using invalid/undef :replace will result in loss of data in + # the event of invalid characters, but since tidy_bytes will replace + # invalid/undef with a "?" we're safe to do the same beforehand + string.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace) if string.encoding == Encoding::GB18030 + + transliterated = I18n.transliterate( ActiveSupport::Multibyte::Unicode.tidy_bytes(string).unicode_normalize(:nfc), replacement: replacement, locale: locale ) + + # Restore the string encoding of the input if it was not UTF-8. + # Apply invalid/undef :replace as tidy_bytes does + transliterated.encode!(input_encoding, invalid: :replace, undef: :replace) if input_encoding != transliterated.encoding + + transliterated end # Replaces special characters in a string so that it may be used as part of diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb index 402a3fbe60..b4bf882bc3 100644 --- a/activesupport/lib/active_support/json/decoding.rb +++ b/activesupport/lib/active_support/json/decoding.rb @@ -44,7 +44,6 @@ module ActiveSupport end private - def convert_dates_from(data) case data when nil diff --git a/activesupport/lib/active_support/lazy_load_hooks.rb b/activesupport/lib/active_support/lazy_load_hooks.rb index a6b096a973..c6f7ccf0a2 100644 --- a/activesupport/lib/active_support/lazy_load_hooks.rb +++ b/activesupport/lib/active_support/lazy_load_hooks.rb @@ -54,7 +54,6 @@ module ActiveSupport end private - def with_execution_control(name, block, once) unless @run_once[name].include?(block) @run_once[name] << block if once diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index 938cfdb914..8b9dd1fffe 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -29,6 +29,9 @@ module ActiveSupport # subscriber, the line above should be called after your # <tt>ActiveRecord::LogSubscriber</tt> definition. # + # A logger also needs to be set with <tt>ActiveRecord::LogSubscriber.logger=</tt>. + # This is assigned automatically in a Rails environment. + # # After configured, whenever a <tt>"sql.active_record"</tt> notification is published, # it will properly dispatch the event # (<tt>ActiveSupport::Notifications::Event</tt>) to the sql method. @@ -112,7 +115,6 @@ module ActiveSupport end private - %w(info debug warn error fatal unknown).each do |level| class_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{level}(progname = nil, &block) diff --git a/activesupport/lib/active_support/logger_thread_safe_level.rb b/activesupport/lib/active_support/logger_thread_safe_level.rb index f16c90cfc6..1775a41492 100644 --- a/activesupport/lib/active_support/logger_thread_safe_level.rb +++ b/activesupport/lib/active_support/logger_thread_safe_level.rb @@ -3,6 +3,7 @@ require "active_support/concern" require "active_support/core_ext/module/attribute_accessors" require "concurrent" +require "fiber" module ActiveSupport module LoggerThreadSafeLevel # :nodoc: @@ -28,7 +29,7 @@ module ActiveSupport end def local_log_id - Thread.current.__id__ + Fiber.current.__id__ end def local_level diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index c4a4afe95f..a5dc1181d8 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -178,8 +178,8 @@ module ActiveSupport # Generates a signed message for the provided value. # - # The message is signed with the +MessageVerifier+'s secret. Without knowing - # the secret, the original value cannot be extracted from the message. + # The message is signed with the +MessageVerifier+'s secret. + # Returns Base64-encoded message joined with the generated signature. # # verifier = ActiveSupport::MessageVerifier.new 's3Krit' # verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772" diff --git a/activesupport/lib/active_support/messages/rotator.rb b/activesupport/lib/active_support/messages/rotator.rb index 823a399d67..50ea7dcd8d 100644 --- a/activesupport/lib/active_support/messages/rotator.rb +++ b/activesupport/lib/active_support/messages/rotator.rb @@ -3,11 +3,12 @@ module ActiveSupport module Messages module Rotator # :nodoc: - def initialize(*, **options) + def initialize(*, on_rotation: nil, **options) super @options = options @rotations = [] + @on_rotation = on_rotation end def rotate(*secrets, **options) @@ -17,7 +18,7 @@ module ActiveSupport module Encryptor include Rotator - def decrypt_and_verify(*args, on_rotation: nil, **options) + def decrypt_and_verify(*args, on_rotation: @on_rotation, **options) super rescue MessageEncryptor::InvalidMessage, MessageVerifier::InvalidSignature run_rotations(on_rotation) { |encryptor| encryptor.decrypt_and_verify(*args, options) } || raise @@ -32,7 +33,7 @@ module ActiveSupport module Verifier include Rotator - def verified(*args, on_rotation: nil, **options) + def verified(*args, on_rotation: @on_rotation, **options) super || run_rotations(on_rotation) { |verifier| verifier.verified(*args, options) } end @@ -46,7 +47,7 @@ module ActiveSupport def run_rotations(on_rotation) @rotations.find do |rotation| if message = yield(rotation) rescue next - on_rotation.call if on_rotation + on_rotation&.call return message end end diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index a1e23aeaca..868cb70423 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -48,7 +48,7 @@ module ActiveSupport #:nodoc: alias to_s wrapped_string alias to_str wrapped_string - delegate :<=>, :=~, :acts_like_string?, to: :wrapped_string + delegate :<=>, :=~, :match?, :acts_like_string?, to: :wrapped_string # Creates a new Chars instance by wrapping _string_. def initialize(string) @@ -207,7 +207,6 @@ module ActiveSupport #:nodoc: end private - def chars(string) self.class.new(string) end diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index ce8ecece69..3956137049 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -148,7 +148,6 @@ module ActiveSupport end private - def recode_windows1252_chars(string) string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace) end diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index d9e93b530c..a7a6112b0f 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -38,6 +38,19 @@ module ActiveSupport # payload # => Hash, the payload # end # + # Here, the +start+ and +finish+ values represent wall-clock time. If you are + # concerned about accuracy, you can register a monotonic subscriber. + # + # ActiveSupport::Notifications.monotonic_subscribe('render') do |name, start, finish, id, payload| + # name # => String, name of the event (such as 'render' from above) + # start # => Monotonic time, when the instrumented block started execution + # finish # => Monotonic time, when the instrumented block ended execution + # id # => String, unique ID for the instrumenter that fired the event + # payload # => Hash, the payload + # end + # + # The +start+ and +finish+ values above represent monotonic time. + # # For instance, let's store all "render" events in an array: # # events = [] @@ -135,6 +148,16 @@ module ActiveSupport # during the execution of the block. The callback is unsubscribed automatically # after that. # + # To record +started+ and +finished+ values with monotonic time, + # specify the optional <tt>:monotonic</tt> option to the + # <tt>subscribed</tt> method. The <tt>:monotonic</tt> option is set + # to +false+ by default. + # + # callback = lambda {|name, started, finished, unique_id, payload| ... } + # ActiveSupport::Notifications.subscribed(callback, "sql.active_record", monotonic: true) do + # ... + # end + # # === Manual Unsubscription # # The +subscribe+ method returns a subscriber object: @@ -208,12 +231,16 @@ module ActiveSupport # ActiveSupport::Notifications.subscribe(/render/) do |event| # @event = event # end - def subscribe(*args, &block) - notifier.subscribe(*args, &block) + def subscribe(pattern = nil, callback = nil, &block) + notifier.subscribe(pattern, callback, monotonic: false, &block) + end + + def monotonic_subscribe(pattern = nil, callback = nil, &block) + notifier.subscribe(pattern, callback, monotonic: true, &block) end - def subscribed(callback, *args, &block) - subscriber = subscribe(*args, &callback) + def subscribed(callback, pattern = nil, monotonic: false, &block) + subscriber = notifier.subscribe(pattern, callback, monotonic: monotonic) yield ensure unsubscribe(subscriber) diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index c506b35b1e..dda71b880e 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -20,8 +20,8 @@ module ActiveSupport super end - def subscribe(pattern = nil, callable = nil, &block) - subscriber = Subscribers.new(pattern, callable || block) + def subscribe(pattern = nil, callable = nil, monotonic: false, &block) + subscriber = Subscribers.new(pattern, callable || block, monotonic) synchronize do if String === pattern @string_subscribers[pattern] << subscriber @@ -84,8 +84,8 @@ module ActiveSupport end module Subscribers # :nodoc: - def self.new(pattern, listener) - subscriber_class = Timed + def self.new(pattern, listener, monotonic) + subscriber_class = monotonic ? MonotonicTimed : Timed if listener.respond_to?(:start) && listener.respond_to?(:finish) subscriber_class = Evented @@ -190,6 +190,23 @@ module ActiveSupport end end + class MonotonicTimed < Evented # :nodoc: + def publish(name, *args) + @delegate.call name, *args + end + + def start(name, id, payload) + timestack = Thread.current[:_timestack_monotonic] ||= [] + timestack.push Concurrent.monotonic_time + end + + def finish(name, id, payload) + timestack = Thread.current[:_timestack_monotonic] + started = timestack.pop + @delegate.call(name, started, Concurrent.monotonic_time, id, payload) + end + end + class EventObject < Evented def start(name, id, payload) stack = Thread.current[:_event_stack] ||= [] @@ -201,6 +218,7 @@ module ActiveSupport def finish(name, id, payload) stack = Thread.current[:_event_stack] event = stack.pop + event.payload = payload event.finish! @delegate.call event end diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index 12546511a8..24e1ab313a 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -46,14 +46,14 @@ module ActiveSupport end private - def unique_id SecureRandom.hex(10) end end class Event - attr_reader :name, :time, :end, :transaction_id, :payload, :children + attr_reader :name, :time, :end, :transaction_id, :children + attr_accessor :payload def self.clock_gettime_supported? # :nodoc: defined?(Process::CLOCK_PROCESS_CPUTIME_ID) && diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index d19a2f64d4..0c87114c0d 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "active_support/dependencies/autoload" - module ActiveSupport module NumberHelper extend ActiveSupport::Autoload @@ -228,7 +226,7 @@ module ActiveSupport end # Formats the bytes in +number+ into a more understandable - # representation (e.g., giving it 1500 yields 1.5 KB). This + # representation (e.g., giving it 1500 yields 1.46 KB). This # method is useful for reporting file sizes to users. You can # customize the format in the +options+ hash. # diff --git a/activesupport/lib/active_support/number_helper/number_converter.rb b/activesupport/lib/active_support/number_helper/number_converter.rb index 06ba797a13..76dd12f27d 100644 --- a/activesupport/lib/active_support/number_helper/number_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_converter.rb @@ -136,7 +136,6 @@ module ActiveSupport end private - def options @options ||= format_options.merge(opts) end diff --git a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb index 0e8ae82dd5..4ad89dc62e 100644 --- a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb @@ -21,7 +21,6 @@ module ActiveSupport end private - def absolute_value(number) number.respond_to?(:abs) ? number.abs : number.sub(/\A-/, "") end diff --git a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb index 467a580a2e..351444289c 100644 --- a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb @@ -14,7 +14,6 @@ module ActiveSupport end private - def parts left, right = number.to_s.split(".") left.gsub!(delimiter_pattern) do |digit_to_delimit| diff --git a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb index 494408fc01..f089d7ae65 100644 --- a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb @@ -31,7 +31,6 @@ module ActiveSupport end private - def format options[:format] || translate_in_locale("human.decimal_units.format") end diff --git a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb index 91262fa656..ed8acbda6e 100644 --- a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb @@ -28,7 +28,6 @@ module ActiveSupport end private - def conversion_format translate_number_value_with_default("human.storage_units.format", locale: options[:locale], raise: true) end diff --git a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb index d5e72981b4..21eadfdcc7 100644 --- a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb @@ -12,7 +12,6 @@ module ActiveSupport end private - def convert_to_phone_number(number) if opts[:area_code] convert_with_area_code(number) diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb index 6ceb9a572e..767cfe22ad 100644 --- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb @@ -38,7 +38,6 @@ module ActiveSupport end private - def strip_insignificant_zeros options[:strip_insignificant_zeros] end diff --git a/activesupport/lib/active_support/option_merger.rb b/activesupport/lib/active_support/option_merger.rb index ab9ca727f6..8e54b11be6 100644 --- a/activesupport/lib/active_support/option_merger.rb +++ b/activesupport/lib/active_support/option_merger.rb @@ -5,7 +5,7 @@ require "active_support/core_ext/hash/deep_merge" module ActiveSupport class OptionMerger #:nodoc: instance_methods.each do |method| - undef_method(method) if method !~ /^(__|instance_eval|class|object_id)/ + undef_method(method) unless method.match?(/^(__|instance_eval|class|object_id)/) end def initialize(context, options) diff --git a/activesupport/lib/active_support/parameter_filter.rb b/activesupport/lib/active_support/parameter_filter.rb index 1389d82523..f4c4f2d2fb 100644 --- a/activesupport/lib/active_support/parameter_filter.rb +++ b/activesupport/lib/active_support/parameter_filter.rb @@ -22,7 +22,7 @@ module ActiveSupport # change { file: { code: "xxxx"} } # # ActiveSupport::ParameterFilter.new([-> (k, v) do - # v.reverse! if k =~ /secret/i + # v.reverse! if /secret/i.match?(k) # end]) # => reverses the value to all keys matching /secret/i class ParameterFilter @@ -51,7 +51,6 @@ module ActiveSupport end private - def compiled_filter @compiled_filter ||= CompiledFilter.compile(@filters, mask: @mask) end @@ -110,7 +109,12 @@ module ActiveSupport elsif value.is_a?(Hash) value = call(value, parents, original_params) elsif value.is_a?(Array) - value = value.map { |v| v.is_a?(Hash) ? call(v, parents, original_params) : v } + # If we don't pop the current parent it will be duplicated as we + # process each array value. + parents.pop if deep_regexps + value = value.map { |v| value_for_key(key, v, parents, original_params) } + # Restore the parent stack after processing the array. + parents.push(key) if deep_regexps elsif blocks.any? key = key.dup if key.duplicable? value = value.dup if value.duplicable? diff --git a/activesupport/lib/active_support/rails.rb b/activesupport/lib/active_support/rails.rb index 8b727a69ec..30857f04d8 100644 --- a/activesupport/lib/active_support/rails.rb +++ b/activesupport/lib/active_support/rails.rb @@ -13,9 +13,6 @@ # Defines Object#blank? and Object#present?. require "active_support/core_ext/object/blank" -# Rails own autoload, eager_load, etc. -require "active_support/dependencies/autoload" - # Support for ClassMethods and the included macro. require "active_support/concern" diff --git a/activesupport/lib/active_support/secure_compare_rotator.rb b/activesupport/lib/active_support/secure_compare_rotator.rb new file mode 100644 index 0000000000..269703c34a --- /dev/null +++ b/activesupport/lib/active_support/secure_compare_rotator.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "active_support/security_utils" +require "active_support/messages/rotator" + +module ActiveSupport + # The ActiveSupport::SecureCompareRotator is a wrapper around +ActiveSupport::SecurityUtils.secure_compare+ + # and allows you to rotate a previously defined value to a new one. + # + # It can be used as follow: + # + # rotator = ActiveSupport::SecureCompareRotator.new('new_production_value') + # rotator.rotate('previous_production_value') + # rotator.secure_compare!('previous_production_value') + # + # One real use case example would be to rotate a basic auth credentials: + # + # class MyController < ApplicationController + # def authenticate_request + # rotator = ActiveSupport::SecureComparerotator.new('new_password') + # rotator.rotate('old_password') + # + # authenticate_or_request_with_http_basic do |username, password| + # rotator.secure_compare!(password) + # rescue ActiveSupport::SecureCompareRotator::InvalidMatch + # false + # end + # end + # end + class SecureCompareRotator + include SecurityUtils + prepend Messages::Rotator + + InvalidMatch = Class.new(StandardError) + + def initialize(value, **_options) + @value = value + end + + def secure_compare!(other_value, on_rotation: @on_rotation) + secure_compare(@value, other_value) || + run_rotations(on_rotation) { |wrapper| wrapper.secure_compare!(other_value) } || + raise(InvalidMatch) + end + + private + def build_rotation(previous_value, _options) + self.class.new(previous_value) + end + end +end diff --git a/activesupport/lib/active_support/string_inquirer.rb b/activesupport/lib/active_support/string_inquirer.rb index a3af36720e..e5091e127a 100644 --- a/activesupport/lib/active_support/string_inquirer.rb +++ b/activesupport/lib/active_support/string_inquirer.rb @@ -18,7 +18,6 @@ module ActiveSupport # vehicle.bike? # => false class StringInquirer < String private - def respond_to_missing?(method_name, include_private = false) (method_name[-1] == "?") || super end diff --git a/activesupport/lib/active_support/testing/parallelization.rb b/activesupport/lib/active_support/testing/parallelization.rb index e760bf5ce3..96518a4a58 100644 --- a/activesupport/lib/active_support/testing/parallelization.rb +++ b/activesupport/lib/active_support/testing/parallelization.rb @@ -27,6 +27,10 @@ module ActiveSupport @queue << o end + def length + @queue.length + end + def pop; @queue.pop; end end @@ -68,7 +72,11 @@ module ActiveSupport def start @pool = @queue_size.times.map do |worker| + title = "Rails test worker #{worker}" + fork do + Process.setproctitle("#{title} - (starting)") + DRb.stop_service begin @@ -81,6 +89,9 @@ module ActiveSupport klass = job[0] method = job[1] reporter = job[2] + + Process.setproctitle("#{title} - #{klass}##{method}") + result = klass.with_info_handler reporter do Minitest.run_one_method(klass, method) end @@ -95,8 +106,12 @@ module ActiveSupport end queue.record(reporter, result) end + + Process.setproctitle("#{title} - (idle)") end ensure + Process.setproctitle("#{title} - (stopping)") + run_cleanup(worker) end end @@ -109,6 +124,10 @@ module ActiveSupport def shutdown @queue_size.times { @queue << nil } @pool.each { |pid| Process.waitpid pid } + + if @queue.length > 0 + raise "Queue not empty, but all workers have finished. This probably means that a worker crashed and #{@queue.length} tests were missed." + end end private diff --git a/activesupport/lib/active_support/testing/stream.rb b/activesupport/lib/active_support/testing/stream.rb index 127cfe1e12..f895a74644 100644 --- a/activesupport/lib/active_support/testing/stream.rb +++ b/activesupport/lib/active_support/testing/stream.rb @@ -4,7 +4,6 @@ module ActiveSupport module Testing module Stream #:nodoc: private - def silence_stream(stream) old_stream = stream.dup stream.reopen(IO::NULL) diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index 5a3fa9346c..84bd920d86 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -40,7 +40,6 @@ module ActiveSupport end private - def unstub_object(stub) singleton_class = stub.object.singleton_class singleton_class.silence_redefinition_of_method stub.method_name @@ -191,7 +190,6 @@ module ActiveSupport end private - def simple_stubs @simple_stubs ||= SimpleStubs.new end diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index d9e033e23b..90830b89bd 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -334,6 +334,13 @@ module ActiveSupport re === name || re === MAPPING[name] end + # Compare #name and TZInfo identifier to a supplied regexp, returning +true+ + # if a match is found. + def match?(re) + (re == name) || (re == MAPPING[name]) || + ((Regexp === re) && (re.match?(name) || re.match?(MAPPING[name]))) + end + # Returns a textual representation of this time zone. def to_s "(GMT#{formatted_offset}) #{name}" diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index 075cd4ed8b..f6ae08bb5d 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -155,7 +155,6 @@ module ActiveSupport end private - def _dasherize(key) # $2 must be a non-greedy regex for this to work left, middle, right = /\A(_*)(.*?)(_*)\Z/.match(key.strip)[1, 3] diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb index 32fe6ade28..12ca19a76f 100644 --- a/activesupport/lib/active_support/xml_mini/jdom.rb +++ b/activesupport/lib/active_support/xml_mini/jdom.rb @@ -53,7 +53,6 @@ module ActiveSupport end private - # Convert an XML element and merge into the hash # # hash:: diff --git a/activesupport/test/cache/behaviors/cache_store_behavior.rb b/activesupport/test/cache/behaviors/cache_store_behavior.rb index a696760bb2..e6f014e08d 100644 --- a/activesupport/test/cache/behaviors/cache_store_behavior.rb +++ b/activesupport/test/cache/behaviors/cache_store_behavior.rb @@ -507,7 +507,6 @@ module CacheStoreBehavior end private - def assert_compressed(value, **options) assert_compression(true, value, **options) end diff --git a/activesupport/test/cache/behaviors/local_cache_behavior.rb b/activesupport/test/cache/behaviors/local_cache_behavior.rb index baa38ba6ac..6f5d53c190 100644 --- a/activesupport/test/cache/behaviors/local_cache_behavior.rb +++ b/activesupport/test/cache/behaviors/local_cache_behavior.rb @@ -46,6 +46,15 @@ module LocalCacheBehavior end end + def test_local_cache_of_read_returns_a_copy_of_the_entry + @cache.with_local_cache do + @cache.write(:foo, type: "bar") + value = @cache.read(:foo) + assert_equal("bar", value.delete(:type)) + assert_equal({ type: "bar" }, @cache.read(:foo)) + end + end + def test_local_cache_of_read @cache.write("foo", "bar") @cache.with_local_cache do diff --git a/activesupport/test/cache/cache_key_test.rb b/activesupport/test/cache/cache_key_test.rb index c2240d03c2..f0cd991553 100644 --- a/activesupport/test/cache/cache_key_test.rb +++ b/activesupport/test/cache/cache_key_test.rb @@ -79,7 +79,6 @@ class CacheKeyTest < ActiveSupport::TestCase end private - def with_env(kv) old_values = {} kv.each { |key, value| old_values[key], ENV[key] = ENV[key], value } diff --git a/activesupport/test/cache/stores/mem_cache_store_test.rb b/activesupport/test/cache/stores/mem_cache_store_test.rb index 0e472f5a1a..0a5e20ed46 100644 --- a/activesupport/test/cache/stores/mem_cache_store_test.rb +++ b/activesupport/test/cache/stores/mem_cache_store_test.rb @@ -9,7 +9,7 @@ require "dalli" # connection pool testing. class SlowDalliClient < Dalli::Client def get(key, options = {}) - if key =~ /latency/ + if /latency/.match?(key) sleep 3 else super @@ -113,7 +113,6 @@ class MemCacheStoreTest < ActiveSupport::TestCase end private - def store [:mem_cache_store, ENV["MEMCACHE_SERVERS"] || "localhost:11211"] end diff --git a/activesupport/test/cache/stores/redis_cache_store_test.rb b/activesupport/test/cache/stores/redis_cache_store_test.rb index 790534cd3c..0ea37b15d5 100644 --- a/activesupport/test/cache/stores/redis_cache_store_test.rb +++ b/activesupport/test/cache/stores/redis_cache_store_test.rb @@ -15,7 +15,7 @@ Redis::Connection.drivers.append(driver) # connection pool testing. class SlowRedis < Redis def get(key, options = {}) - if key =~ /latency/ + if /latency/.match?(key) sleep 3 else super @@ -191,7 +191,6 @@ module ActiveSupport::Cache::RedisCacheStoreTests include ConnectionPoolBehavior private - def store [:redis_cache_store] end @@ -238,7 +237,6 @@ module ActiveSupport::Cache::RedisCacheStoreTests include FailureSafetyBehavior private - def emulating_unavailability old_client = Redis.send(:remove_const, :Client) Redis.const_set(:Client, UnavailableRedisClient) diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index 79098b2a7d..221a8a5731 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -256,7 +256,7 @@ module CallbacksTest end def respond_to_missing?(sym) - sym =~ /^(log|wrap)_/ || super + sym.match?(/^(log|wrap)_/) || super end end @@ -397,7 +397,6 @@ module CallbacksTest end private - def record1 @recorder << 1 end @@ -989,6 +988,7 @@ module CallbacksTest define_callbacks :foo, scope: [:name] set_callback :foo, :before, :foo, if: callback def run; run_callbacks :foo; end + private def foo; end } diff --git a/activesupport/test/core_ext/date_and_time_behavior.rb b/activesupport/test/core_ext/date_and_time_behavior.rb index b77ea22701..6d31734f27 100644 --- a/activesupport/test/core_ext/date_and_time_behavior.rb +++ b/activesupport/test/core_ext/date_and_time_behavior.rb @@ -8,31 +8,11 @@ module DateAndTimeBehavior assert_equal date_time_init(2005, 2, 28, 10, 10, 10), date_time_init(2005, 3, 2, 10, 10, 10).yesterday.yesterday end - def test_prev_day - assert_equal date_time_init(2005, 2, 24, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(-2) - assert_equal date_time_init(2005, 2, 23, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(-1) - assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(0) - assert_equal date_time_init(2005, 2, 21, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(1) - assert_equal date_time_init(2005, 2, 20, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(2) - assert_equal date_time_init(2005, 2, 21, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day - assert_equal date_time_init(2005, 2, 28, 10, 10, 10), date_time_init(2005, 3, 2, 10, 10, 10).prev_day.prev_day - end - def test_tomorrow assert_equal date_time_init(2005, 2, 23, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).tomorrow assert_equal date_time_init(2005, 3, 2, 10, 10, 10), date_time_init(2005, 2, 28, 10, 10, 10).tomorrow.tomorrow end - def test_next_day - assert_equal date_time_init(2005, 2, 20, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(-2) - assert_equal date_time_init(2005, 2, 21, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(-1) - assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(0) - assert_equal date_time_init(2005, 2, 23, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(1) - assert_equal date_time_init(2005, 2, 24, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(2) - assert_equal date_time_init(2005, 2, 23, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day - assert_equal date_time_init(2005, 3, 2, 10, 10, 10), date_time_init(2005, 2, 28, 10, 10, 10).next_day.next_day - end - def test_days_ago assert_equal date_time_init(2005, 6, 4, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).days_ago(1) assert_equal date_time_init(2005, 5, 31, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).days_ago(5) @@ -161,16 +141,6 @@ module DateAndTimeBehavior assert_equal date_time_init(2015, 1, 5, 15, 15, 10), date_time_init(2015, 1, 3, 15, 15, 10).next_weekday end - def test_next_month - assert_equal date_time_init(2004, 12, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(-2) - assert_equal date_time_init(2005, 1, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(-1) - assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(0) - assert_equal date_time_init(2005, 3, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(1) - assert_equal date_time_init(2005, 4, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(2) - assert_equal date_time_init(2005, 3, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month - assert_equal date_time_init(2005, 4, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month.next_month - end - def test_next_month_on_31st assert_equal date_time_init(2005, 9, 30, 15, 15, 10), date_time_init(2005, 8, 31, 15, 15, 10).next_month end @@ -179,16 +149,6 @@ module DateAndTimeBehavior assert_equal date_time_init(2005, 11, 30, 15, 15, 10), date_time_init(2005, 8, 31, 15, 15, 10).next_quarter end - def test_next_year - assert_equal date_time_init(2003, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(-2) - assert_equal date_time_init(2004, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(-1) - assert_equal date_time_init(2005, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(0) - assert_equal date_time_init(2006, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(1) - assert_equal date_time_init(2007, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(2) - assert_equal date_time_init(2006, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year - assert_equal date_time_init(2007, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year.next_year - end - def test_prev_week assert_equal date_time_init(2005, 2, 21, 0, 0, 0), date_time_init(2005, 3, 1, 15, 15, 10).prev_week assert_equal date_time_init(2005, 2, 22, 0, 0, 0), date_time_init(2005, 3, 1, 15, 15, 10).prev_week(:tuesday) @@ -229,16 +189,6 @@ module DateAndTimeBehavior assert_equal date_time_init(2015, 1, 2, 15, 15, 10), date_time_init(2015, 1, 4, 15, 15, 10).prev_weekday end - def test_prev_month - assert_equal date_time_init(2005, 4, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(-2) - assert_equal date_time_init(2005, 3, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(-1) - assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(0) - assert_equal date_time_init(2005, 1, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(1) - assert_equal date_time_init(2004, 12, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(2) - assert_equal date_time_init(2005, 1, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month - assert_equal date_time_init(2004, 12, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month.prev_month - end - def test_prev_month_on_31st assert_equal date_time_init(2004, 2, 29, 10, 10, 10), date_time_init(2004, 3, 31, 10, 10, 10).prev_month end @@ -247,16 +197,6 @@ module DateAndTimeBehavior assert_equal date_time_init(2004, 2, 29, 10, 10, 10), date_time_init(2004, 5, 31, 10, 10, 10).prev_quarter end - def test_prev_year - assert_equal date_time_init(2007, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(-2) - assert_equal date_time_init(2006, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(-1) - assert_equal date_time_init(2005, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(0) - assert_equal date_time_init(2004, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(1) - assert_equal date_time_init(2003, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(2) - assert_equal date_time_init(2004, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year - assert_equal date_time_init(2003, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year.prev_year - end - def test_last_month_on_31st assert_equal date_time_init(2004, 2, 29, 0, 0, 0), date_time_init(2004, 3, 31, 0, 0, 0).last_month end diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index b8652884ce..7060dd3c56 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -112,14 +112,6 @@ class DateExtCalculationsTest < ActiveSupport::TestCase assert_equal Date.new(2005, 4, 30), Date.new(2005, 4, 20).end_of_month end - def test_prev_year_in_leap_years - assert_equal Date.new(1999, 2, 28), Date.new(2000, 2, 29).prev_year - end - - def test_prev_year_in_calendar_reform - assert_equal Date.new(1582, 10, 4), Date.new(1583, 10, 14).prev_year - end - def test_last_year_in_leap_years assert_equal Date.new(1999, 2, 28), Date.new(2000, 2, 29).last_year end @@ -128,14 +120,6 @@ class DateExtCalculationsTest < ActiveSupport::TestCase assert_equal Date.new(1582, 10, 4), Date.new(1583, 10, 14).last_year end - def test_next_year_in_leap_years - assert_equal Date.new(2001, 2, 28), Date.new(2000, 2, 29).next_year - end - - def test_next_year_in_calendar_reform - assert_equal Date.new(1582, 10, 4), Date.new(1581, 10, 10).next_year - end - def test_advance assert_equal Date.new(2006, 2, 28), Date.new(2005, 2, 28).advance(years: 1) assert_equal Date.new(2005, 6, 28), Date.new(2005, 2, 28).advance(months: 4) diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 381b5a1f32..a9bf4b82f4 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -242,4 +242,28 @@ class EnumerableTests < ActiveSupport::TestCase ]) assert_equal [[5, 99], [15, 0], [10, 50]], payments.pluck(:dollars, :cents) end + + def test_compact_blank + values = GenericEnumerable.new([1, "", nil, 2, " ", [], {}, false, true]) + + assert_equal [1, 2, true], values.compact_blank + end + + def test_array_compact_blank! + values = [1, "", nil, 2, " ", [], {}, false, true] + values.compact_blank! + + assert_equal [1, 2, true], values + end + + def test_hash_compact_blank + values = { a: "", b: 1, c: nil, d: [], e: false, f: true } + assert_equal({ b: 1, f: true }, values.compact_blank) + end + + def test_hash_compact_blank! + values = { a: "", b: 1, c: nil, d: [], e: false, f: true } + values.compact_blank! + assert_equal({ b: 1, f: true }, values) + end end diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index 04692f1484..dd36a9373a 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -25,7 +25,6 @@ Someone = Struct.new(:name, :place) do delegate :bar, to: :place, allow_nil: true private - def private_name "Private" end @@ -92,6 +91,16 @@ DecoratedTester = Struct.new(:client) do delegate_missing_to :client end +class DecoratedMissingAllowNil + delegate_missing_to :case, allow_nil: true + + attr_reader :case + + def initialize(kase) + @case = kase + end +end + class DecoratedReserved delegate_missing_to :case @@ -102,6 +111,24 @@ class DecoratedReserved end end +class Maze + attr_accessor :cavern, :passages +end + +class Cavern + delegate_missing_to :target + + attr_reader :maze + + def initialize(maze) + @maze = maze + end + + def target + @maze.passages = :twisty + end +end + class Block def hello? true @@ -382,6 +409,10 @@ class ModuleTest < ActiveSupport::TestCase assert_equal "name delegated to client, but client is nil", e.message end + def test_delegate_missing_to_returns_nil_if_allow_nil_and_nil_target + assert_nil DecoratedMissingAllowNil.new(nil).name + end + def test_delegate_missing_to_affects_respond_to assert_respond_to DecoratedTester.new(@david), :name assert_not_respond_to DecoratedTester.new(@david), :private_name @@ -398,6 +429,17 @@ class ModuleTest < ActiveSupport::TestCase assert_respond_to DecoratedTester.new(@david), :extra_missing end + def test_delegate_missing_to_does_not_interfere_with_marshallization + maze = Maze.new + maze.cavern = Cavern.new(maze) + + array = [maze, nil] + serialized_array = Marshal.dump(array) + deserialized_array = Marshal.load(serialized_array) + + assert_nil deserialized_array[1] + end + def test_delegate_with_case event = Event.new(Tester.new) assert_equal 1, event.foo diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb index 5203434ae6..a577c30c40 100644 --- a/activesupport/test/core_ext/object/duplicable_test.rb +++ b/activesupport/test/core_ext/object/duplicable_test.rb @@ -6,13 +6,8 @@ require "active_support/core_ext/object/duplicable" require "active_support/core_ext/numeric/time" class DuplicableTest < ActiveSupport::TestCase - if RUBY_VERSION >= "2.5.0" - RAISE_DUP = [method(:puts)] - ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56"), nil, false, true, 1, 2.3, Complex(1), Rational(1)] - else - RAISE_DUP = [method(:puts), Complex(1), Rational(1)] - ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56"), nil, false, true, 1, 2.3] - end + RAISE_DUP = [method(:puts), method(:puts).unbind] + ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56"), nil, false, true, 1, 2.3, Complex(1), Rational(1)] def test_duplicable rubinius_skip "* Method#dup is allowed at the moment on Rubinius\n" \ diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index c5a000b67a..af8f9c9b09 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -455,6 +455,8 @@ class StringAccessTest < ActiveSupport::TestCase test "#to with negative Integer, position is counted from the end" do assert_equal "hell", "hello".to(-2) + assert_equal "h", "hello".to(-5) + assert_equal "", "hello".to(-7) end test "#from and #to can be combined" do @@ -480,12 +482,16 @@ class StringAccessTest < ActiveSupport::TestCase assert_not_same different_string, string end - test "#first with negative Integer is deprecated" do - string = "hello" - message = "Calling String#first with a negative integer limit " \ - "will raise an ArgumentError in Rails 6.1." - assert_deprecated(message) do - string.first(-1) + test "#first with Integer returns a non-frozen string" do + string = "he" + (0..string.length + 1).each do |limit| + assert_not string.first(limit).frozen? + end + end + + test "#first with negative Integer raises ArgumentError" do + assert_raise ArgumentError do + "hello".first(-1) end end @@ -507,12 +513,16 @@ class StringAccessTest < ActiveSupport::TestCase assert_not_same different_string, string end - test "#last with negative Integer is deprecated" do - string = "hello" - message = "Calling String#last with a negative integer limit " \ - "will raise an ArgumentError in Rails 6.1." - assert_deprecated(message) do - string.last(-1) + test "#last with Integer returns a non-frozen string" do + string = "he" + (0..string.length + 1).each do |limit| + assert_not string.last(limit).frozen? + end + end + + test "#last with negative Integer raises ArgumentError" do + assert_raise ArgumentError do + "hello".last(-1) end end diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 590b81b770..41ab98e721 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -949,6 +949,66 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal "invalid date", exception.message end + + def test_prev_day + assert_equal date_time_init(2005, 2, 24, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(-2) + assert_equal date_time_init(2005, 2, 23, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(-1) + assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(0) + assert_equal date_time_init(2005, 2, 21, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(1) + assert_equal date_time_init(2005, 2, 20, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(2) + assert_equal date_time_init(2005, 2, 21, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day + assert_equal date_time_init(2005, 2, 28, 10, 10, 10), date_time_init(2005, 3, 2, 10, 10, 10).prev_day.prev_day + end + + def test_next_day + assert_equal date_time_init(2005, 2, 20, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(-2) + assert_equal date_time_init(2005, 2, 21, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(-1) + assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(0) + assert_equal date_time_init(2005, 2, 23, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(1) + assert_equal date_time_init(2005, 2, 24, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(2) + assert_equal date_time_init(2005, 2, 23, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day + assert_equal date_time_init(2005, 3, 2, 10, 10, 10), date_time_init(2005, 2, 28, 10, 10, 10).next_day.next_day + end + + def test_prev_month + assert_equal date_time_init(2005, 4, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(-2) + assert_equal date_time_init(2005, 3, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(-1) + assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(0) + assert_equal date_time_init(2005, 1, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(1) + assert_equal date_time_init(2004, 12, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(2) + assert_equal date_time_init(2005, 1, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month + assert_equal date_time_init(2004, 12, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month.prev_month + end + + def test_next_month + assert_equal date_time_init(2004, 12, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(-2) + assert_equal date_time_init(2005, 1, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(-1) + assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(0) + assert_equal date_time_init(2005, 3, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(1) + assert_equal date_time_init(2005, 4, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(2) + assert_equal date_time_init(2005, 3, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month + assert_equal date_time_init(2005, 4, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month.next_month + end + + def test_prev_year + assert_equal date_time_init(2007, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(-2) + assert_equal date_time_init(2006, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(-1) + assert_equal date_time_init(2005, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(0) + assert_equal date_time_init(2004, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(1) + assert_equal date_time_init(2003, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(2) + assert_equal date_time_init(2004, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year + assert_equal date_time_init(2003, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year.prev_year + end + + def test_next_year + assert_equal date_time_init(2003, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(-2) + assert_equal date_time_init(2004, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(-1) + assert_equal date_time_init(2005, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(0) + assert_equal date_time_init(2006, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(1) + assert_equal date_time_init(2007, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(2) + assert_equal date_time_init(2006, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year + assert_equal date_time_init(2007, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year.next_year + end end class TimeExtMarshalingTest < ActiveSupport::TestCase diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index d4e709137e..6bad69f7f2 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -481,17 +481,14 @@ class DependenciesTest < ActiveSupport::TestCase end end - # This raises only on 2.5.. (warns on ..2.4) - if RUBY_VERSION > "2.5" - def test_access_thru_and_upwards_fails - with_autoloading_fixtures do - assert_not defined?(ModuleFolder) - assert_raise(NameError) { ModuleFolder::Object } - assert_raise(NameError) { ModuleFolder::NestedClass::Object } - end - ensure - remove_constants(:ModuleFolder) + def test_access_thru_and_upwards_fails + with_autoloading_fixtures do + assert_not defined?(ModuleFolder) + assert_raise(NameError) { ModuleFolder::Object } + assert_raise(NameError) { ModuleFolder::NestedClass::Object } end + ensure + remove_constants(:ModuleFolder) end def test_non_existing_const_raises_name_error_with_fully_qualified_name @@ -595,6 +592,13 @@ class DependenciesTest < ActiveSupport::TestCase nil_name = Module.new def nil_name.name() nil end assert_not ActiveSupport::Dependencies.autoloaded?(nil_name) + + invalid_constant_name = Module.new do + def self.name + "primary::SchemaMigration" + end + end + assert_not ActiveSupport::Dependencies.autoloaded?(invalid_constant_name) end ensure remove_constants(:ModuleFolder) diff --git a/activesupport/test/deprecation/method_wrappers_test.rb b/activesupport/test/deprecation/method_wrappers_test.rb index 0aa3233aab..8c40534452 100644 --- a/activesupport/test/deprecation/method_wrappers_test.rb +++ b/activesupport/test/deprecation/method_wrappers_test.rb @@ -10,12 +10,10 @@ class MethodWrappersTest < ActiveSupport::TestCase alias_method :old_method, :new_method protected - def new_protected_method; "abc" end alias_method :old_protected_method, :new_protected_method private - def new_private_method; "abc" end alias_method :old_private_method, :new_private_method end diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index f25c704586..ae2f4a6e58 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -38,6 +38,11 @@ class Deprecatee C = 1 end A = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("Deprecatee::A", "Deprecatee::B::C") + + module New + class Descendant; end + end + Old = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("Deprecatee::Old", "Deprecatee::New") end class DeprecateeWithAccessor @@ -210,6 +215,18 @@ class DeprecationTest < ActiveSupport::TestCase assert_not_deprecated { assert_equal Deprecatee::B::C.class, Deprecatee::A.class } end + def test_deprecated_constant_descendant + assert_not_deprecated { Deprecatee::New::Descendant } + + assert_deprecated("Deprecatee::Old") do + assert_equal Deprecatee::Old::Descendant, Deprecatee::New::Descendant + end + + assert_raises(NameError) do + assert_deprecated("Deprecatee::Old") { Deprecatee::Old::NON_EXISTENCE } + end + end + def test_deprecated_constant_accessor assert_not_deprecated { DeprecateeWithAccessor::B::C } assert_deprecated("DeprecateeWithAccessor::A") { assert_equal DeprecateeWithAccessor::B::C, DeprecateeWithAccessor::A } diff --git a/activesupport/test/descendants_tracker_test_cases.rb b/activesupport/test/descendants_tracker_test_cases.rb index f8752688d2..7d39783826 100644 --- a/activesupport/test/descendants_tracker_test_cases.rb +++ b/activesupport/test/descendants_tracker_test_cases.rb @@ -52,7 +52,6 @@ module DescendantsTrackerTestCases end private - def assert_equal_sets(expected, actual) assert_equal Set.new(expected), Set.new(actual) end diff --git a/activesupport/test/evented_file_update_checker_test.rb b/activesupport/test/evented_file_update_checker_test.rb index b2d5eb94c2..4d5a9bed7a 100644 --- a/activesupport/test/evented_file_update_checker_test.rb +++ b/activesupport/test/evented_file_update_checker_test.rb @@ -77,32 +77,48 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase Process.wait(pid) end + test "should detect changes through symlink" do + actual_dir = File.join(tmpdir, "actual") + linked_dir = File.join(tmpdir, "linked") + + Dir.mkdir(actual_dir) + FileUtils.ln_s(actual_dir, linked_dir) + + checker = new_checker([], linked_dir => ".rb") { } + + assert_not_predicate checker, :updated? + + FileUtils.touch(File.join(actual_dir, "a.rb")) + wait + + assert_predicate checker, :updated? + assert checker.execute_if_updated + end + test "updated should become true when nonexistent directory is added later" do - Dir.mktmpdir do |dir| - watched_dir = File.join(dir, "app") - unwatched_dir = File.join(dir, "node_modules") - not_exist_watched_dir = File.join(dir, "test") + watched_dir = File.join(tmpdir, "app") + unwatched_dir = File.join(tmpdir, "node_modules") + not_exist_watched_dir = File.join(tmpdir, "test") - Dir.mkdir(watched_dir) - Dir.mkdir(unwatched_dir) + Dir.mkdir(watched_dir) + Dir.mkdir(unwatched_dir) - checker = new_checker([], watched_dir => ".rb", not_exist_watched_dir => ".rb") { } + checker = new_checker([], watched_dir => ".rb", not_exist_watched_dir => ".rb") { } - FileUtils.touch(File.join(watched_dir, "a.rb")) - wait - assert_predicate checker, :updated? - assert checker.execute_if_updated + FileUtils.touch(File.join(watched_dir, "a.rb")) + wait + assert_predicate checker, :updated? + assert checker.execute_if_updated - Dir.mkdir(not_exist_watched_dir) - wait - assert_predicate checker, :updated? - assert checker.execute_if_updated + Dir.mkdir(not_exist_watched_dir) + wait + assert_predicate checker, :updated? + assert checker.execute_if_updated - FileUtils.touch(File.join(unwatched_dir, "a.rb")) - wait - assert_not_predicate checker, :updated? - assert_not checker.execute_if_updated - end + FileUtils.touch(File.join(unwatched_dir, "a.rb")) + wait + assert_not_predicate checker, :updated? + assert_not checker.execute_if_updated end end @@ -156,14 +172,6 @@ class EventedFileUpdateCheckerPathHelperTest < ActiveSupport::TestCase assert_nil @ph.longest_common_subpath([]) end - test "#existing_parent returns the most specific existing ascendant" do - wd = Pathname.getwd - - assert_equal wd, @ph.existing_parent(wd) - assert_equal wd, @ph.existing_parent(wd.join("non-existing/directory")) - assert_equal pn("/"), @ph.existing_parent(pn("/non-existing/directory")) - end - test "#filter_out_descendants returns the same collection if there are no descendants (empty)" do assert_equal [], @ph.filter_out_descendants([]) end diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb index 8d9587f248..2e8ea01aca 100644 --- a/activesupport/test/json/decoding_test.rb +++ b/activesupport/test/json/decoding_test.rb @@ -113,7 +113,6 @@ class TestJSONDecoding < ActiveSupport::TestCase end private - def with_parse_json_times(value) old_value = ActiveSupport.parse_json_times ActiveSupport.parse_json_times = value diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 7589ffd0ea..c4e0b71f26 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -466,7 +466,6 @@ EXPECTED end private - def object_keys(json_object) json_object[1..-2].scan(/([^{}:,\s]+):/).flatten.sort end diff --git a/activesupport/test/lazy_load_hooks_test.rb b/activesupport/test/lazy_load_hooks_test.rb index 50a703e49f..5e9a21f047 100644 --- a/activesupport/test/lazy_load_hooks_test.rb +++ b/activesupport/test/lazy_load_hooks_test.rb @@ -175,7 +175,6 @@ class LazyLoadHooksTest < ActiveSupport::TestCase end private - def incr_amt 5 end diff --git a/activesupport/test/logger_test.rb b/activesupport/test/logger_test.rb index 160e1156b6..6f7a186022 100644 --- a/activesupport/test/logger_test.rb +++ b/activesupport/test/logger_test.rb @@ -258,6 +258,50 @@ class LoggerTest < ActiveSupport::TestCase assert_level(Logger::INFO) end + def test_logger_level_main_fiber_safety + @logger.level = Logger::INFO + assert_level(Logger::INFO) + + fiber = Fiber.new do + assert_level(Logger::INFO) + end + + @logger.silence(Logger::ERROR) do + assert_level(Logger::ERROR) + fiber.resume + end + end + + def test_logger_level_local_fiber_safety + @logger.level = Logger::INFO + assert_level(Logger::INFO) + + another_fiber = Fiber.new do + @logger.silence(Logger::ERROR) do + assert_level(Logger::ERROR) + @logger.silence(Logger::DEBUG) do + assert_level(Logger::DEBUG) + end + end + + assert_level(Logger::INFO) + end + + Fiber.new do + @logger.silence(Logger::ERROR) do + assert_level(Logger::ERROR) + @logger.silence(Logger::DEBUG) do + another_fiber.resume + assert_level(Logger::DEBUG) + end + end + + assert_level(Logger::INFO) + end.resume + + assert_level(Logger::INFO) + end + private def level_name(level) ::Logger::Severity.constants.find do |severity| diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb index 9edf07f762..f807497709 100644 --- a/activesupport/test/message_encryptor_test.rb +++ b/activesupport/test/message_encryptor_test.rb @@ -171,6 +171,34 @@ class MessageEncryptorTest < ActiveSupport::TestCase assert rotated end + def test_on_rotation_can_be_passed_at_the_constructor_level + older_message = ActiveSupport::MessageEncryptor.new(secrets[:older], "older sign").encrypt_and_sign(encoded: "message") + + rotated = rotated = false # double assigning to suppress "assigned but unused variable" warning + encryptor = ActiveSupport::MessageEncryptor.new(@secret, on_rotation: proc { rotated = true }) + encryptor.rotate secrets[:older], "older sign" + + assert_changes(:rotated, from: false, to: true) do + message = encryptor.decrypt_and_verify(older_message) + + assert_equal({ encoded: "message" }, message) + end + end + + def test_on_rotation_option_takes_precedence_over_the_one_given_in_constructor + older_message = ActiveSupport::MessageEncryptor.new(secrets[:older], "older sign").encrypt_and_sign(encoded: "message") + + rotated = rotated = false # double assigning to suppress "assigned but unused variable" warning + encryptor = ActiveSupport::MessageEncryptor.new(@secret, on_rotation: proc { rotated = true }) + encryptor.rotate secrets[:older], "older sign" + + assert_changes(:rotated, from: false, to: "Yes") do + message = encryptor.decrypt_and_verify(older_message, on_rotation: proc { rotated = "Yes" }) + + assert_equal({ encoded: "message" }, message) + end + end + def test_with_rotated_metadata old_message = ActiveSupport::MessageEncryptor.new(secrets[:old], cipher: "aes-256-gcm"). encrypt_and_sign("metadata", purpose: :rotation) diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index 5f4e3f3fd3..b0027047df 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -204,6 +204,12 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase assert_equal 3, (@chars =~ /わ/u) end + def test_match_should_return_boolean_for_regexp_match + assert_not @chars.match?(/wrong/u) + assert @chars.match?(/こに/u) + assert @chars.match?(/ち/u) + end + def test_should_use_character_offsets_for_insert_offsets assert_equal "", (+"").mb_chars.insert(0, "") assert_equal "こわにちわ", @chars.insert(1, "わ") @@ -783,7 +789,6 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase end private - def string_from_classes(classes) # Characters from the character classes as described in UAX #29 character_from_class = { diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index d3c660a014..08277e5436 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -20,7 +20,6 @@ module Notifications end private - def event(*args) ActiveSupport::Notifications::Event.new(*args) end @@ -42,6 +41,27 @@ module Notifications assert_operator event.duration, :>, 0 end + def test_subscribe_to_events_where_payload_is_changed_during_instrumentation + @notifier.subscribe do |event| + assert_equal "success!", event.payload[:my_key] + end + + ActiveSupport::Notifications.instrument("foo") do |payload| + payload[:my_key] = "success!" + end + end + + def test_subscribe_to_events_can_handle_nested_hashes_in_the_paylaod + @notifier.subscribe do |event| + assert_equal "success!", event.payload[:some_key][:key_one] + assert_equal "great_success!", event.payload[:some_key][:key_two] + end + + ActiveSupport::Notifications.instrument("foo", some_key: { key_one: "success!" }) do |payload| + payload[:some_key][:key_two] = "great_success!" + end + end + def test_subscribe_via_top_level_api old_notifier = ActiveSupport::Notifications.notifier ActiveSupport::Notifications.notifier = ActiveSupport::Notifications::Fanout.new @@ -62,6 +82,38 @@ module Notifications end end + class TimedAndMonotonicTimedSubscriberTest < TestCase + def test_subscribe + event_name = "foo" + class_of_started = nil + class_of_finished = nil + + ActiveSupport::Notifications.subscribe(event_name) do |name, started, finished, unique_id, data| + class_of_started = started.class + class_of_finished = finished.class + end + + ActiveSupport::Notifications.instrument(event_name) + + assert_equal [Time, Time], [class_of_started, class_of_finished] + end + + def test_monotonic_subscribe + event_name = "foo" + class_of_started = nil + class_of_finished = nil + + ActiveSupport::Notifications.monotonic_subscribe(event_name) do |name, started, finished, unique_id, data| + class_of_started = started.class + class_of_finished = finished.class + end + + ActiveSupport::Notifications.instrument(event_name) + + assert_equal [Float, Float], [class_of_started, class_of_finished] + end + end + class SubscribedTest < TestCase def test_subscribed name = "foo" @@ -81,6 +133,24 @@ module Notifications assert_equal expected, events end + def test_subscribed_all_messages + name = "foo" + name2 = name * 2 + expected = [name, name2, name] + + events = [] + callback = lambda { |*_| events << _.first } + ActiveSupport::Notifications.subscribed(callback) do + ActiveSupport::Notifications.instrument(name) + ActiveSupport::Notifications.instrument(name2) + ActiveSupport::Notifications.instrument(name) + end + assert_equal expected, events + + ActiveSupport::Notifications.instrument(name) + assert_equal expected, events + end + def test_subscribing_to_instrumentation_while_inside_it # the repro requires that there are no evented subscribers for the "foo" event, # so we have to duplicate some of the setup code @@ -95,6 +165,42 @@ module Notifications ensure ActiveSupport::Notifications.notifier = old_notifier end + + def test_timed_subscribed + event_name = "foo" + class_of_started = nil + class_of_finished = nil + callback = lambda do |name, started, finished, unique_id, data| + class_of_started = started.class + class_of_finished = finished.class + end + + ActiveSupport::Notifications.subscribed(callback, event_name) do + ActiveSupport::Notifications.instrument(event_name) + end + + ActiveSupport::Notifications.instrument(event_name) + + assert_equal [Time, Time], [class_of_started, class_of_finished] + end + + def test_monotonic_timed_subscribed + event_name = "foo" + class_of_started = nil + class_of_finished = nil + callback = lambda do |name, started, finished, unique_id, data| + class_of_started = started.class + class_of_finished = finished.class + end + + ActiveSupport::Notifications.subscribed(callback, event_name, monotonic: true) do + ActiveSupport::Notifications.instrument(event_name) + end + + ActiveSupport::Notifications.instrument(event_name) + + assert_equal [Float, Float], [class_of_started, class_of_finished] + end end class UnsubscribeTest < TestCase @@ -317,7 +423,6 @@ module Notifications assert_equal 0, event.cpu_time end - def test_events_consumes_information_given_as_payload event = event(:foo, Concurrent.monotonic_time, Concurrent.monotonic_time + 1, random_id, payload: :bar) assert_equal Hash[payload: :bar], event.payload diff --git a/activesupport/test/parameter_filter_test.rb b/activesupport/test/parameter_filter_test.rb index d2dc71061d..510921cf95 100644 --- a/activesupport/test/parameter_filter_test.rb +++ b/activesupport/test/parameter_filter_test.rb @@ -22,16 +22,23 @@ class ParameterFilterTest < ActiveSupport::TestCase filter_words << "blah" filter_words << lambda { |key, value| - value.reverse! if key =~ /bargain/ + value.reverse! if /bargain/.match?(key) } filter_words << lambda { |key, value, original_params| value.replace("world!") if original_params["barg"]["blah"] == "bar" && key == "hello" } + filter_words << lambda { |key, value| + value.upcase! if key == "array_elements" + } + parameter_filter = ActiveSupport::ParameterFilter.new(filter_words) before_filter["barg"] = { :bargain => "gain", "blah" => "bar", "bar" => { "bargain" => { "blah" => "foo", "hello" => "world" } } } after_filter["barg"] = { :bargain => "niag", "blah" => "[FILTERED]", "bar" => { "bargain" => { "blah" => "[FILTERED]", "hello" => "world!" } } } + before_filter["array_elements"] = %w(element1 element2) + after_filter["array_elements"] = %w(ELEMENT1 ELEMENT2) + assert_equal after_filter, parameter_filter.filter(before_filter) end end @@ -54,7 +61,7 @@ class ParameterFilterTest < ActiveSupport::TestCase filter_words << "blah" filter_words << lambda { |key, value| - value.reverse! if key =~ /bargain/ + value.reverse! if /bargain/.match?(key) } filter_words << lambda { |key, value, original_params| value.replace("world!") if original_params["barg"]["blah"] == "bar" && key == "hello" diff --git a/activesupport/test/secure_compare_rotator_test.rb b/activesupport/test/secure_compare_rotator_test.rb new file mode 100644 index 0000000000..d80faea128 --- /dev/null +++ b/activesupport/test/secure_compare_rotator_test.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "active_support/secure_compare_rotator" + +class SecureCompareRotatorTest < ActiveSupport::TestCase + test "#secure_compare! works correctly after rotation" do + wrapper = ActiveSupport::SecureCompareRotator.new("old_secret") + wrapper.rotate("new_secret") + + assert_equal(true, wrapper.secure_compare!("new_secret")) + end + + test "#secure_compare! works correctly after multiple rotation" do + wrapper = ActiveSupport::SecureCompareRotator.new("old_secret") + wrapper.rotate("new_secret") + wrapper.rotate("another_secret") + wrapper.rotate("and_another_one") + + assert_equal(true, wrapper.secure_compare!("and_another_one")) + end + + test "#secure_compare! fails correctly when credential is not part of the rotation" do + wrapper = ActiveSupport::SecureCompareRotator.new("old_secret") + wrapper.rotate("new_secret") + + assert_raises(ActiveSupport::SecureCompareRotator::InvalidMatch) do + wrapper.secure_compare!("different_secret") + end + end + + test "#secure_compare! calls the on_rotation proc" do + wrapper = ActiveSupport::SecureCompareRotator.new("old_secret") + wrapper.rotate("new_secret") + wrapper.rotate("another_secret") + wrapper.rotate("and_another_one") + + @witness = nil + + assert_changes(:@witness, from: nil, to: true) do + assert_equal(true, wrapper.secure_compare!("and_another_one", on_rotation: -> { @witness = true })) + end + end + + test "#secure_compare! calls the on_rotation proc that given in constructor" do + @witness = nil + + wrapper = ActiveSupport::SecureCompareRotator.new("old_secret", on_rotation: -> { @witness = true }) + wrapper.rotate("new_secret") + wrapper.rotate("another_secret") + wrapper.rotate("and_another_one") + + assert_changes(:@witness, from: nil, to: true) do + assert_equal(true, wrapper.secure_compare!("and_another_one")) + end + end +end diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb index a40c813fe3..68e78ccc16 100644 --- a/activesupport/test/share_lock_test.rb +++ b/activesupport/test/share_lock_test.rb @@ -488,12 +488,10 @@ class ShareLockTest < ActiveSupport::TestCase end private - module CustomAssertions SUFFICIENT_TIMEOUT = 0.2 private - def assert_threads_stuck_but_releasable_by_latch(threads, latch) assert_threads_stuck threads latch.count_down diff --git a/activesupport/test/subscriber_test.rb b/activesupport/test/subscriber_test.rb index bc8d8f1c13..829fba6057 100644 --- a/activesupport/test/subscriber_test.rb +++ b/activesupport/test/subscriber_test.rb @@ -17,7 +17,6 @@ class TestSubscriber < ActiveSupport::Subscriber end private - def private_party(event) events << event end diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb index 56cd2665e0..dd75548e9c 100644 --- a/activesupport/test/test_case_test.rb +++ b/activesupport/test/test_case_test.rb @@ -315,7 +315,6 @@ class SetupAndTeardownTest < ActiveSupport::TestCase end private - def reset_callback_record @called_back = [] end diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index f948c363df..6ca3afd561 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -717,6 +717,13 @@ class TimeZoneTest < ActiveSupport::TestCase assert zone !~ /Nonexistent_Place/ end + def test_zone_match? + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + assert zone.match?(/Eastern/) + assert zone.match?(/New_York/) + assert_not zone.match?(/Nonexistent_Place/) + end + def test_to_s assert_equal "(GMT+05:30) New Delhi", ActiveSupport::TimeZone["New Delhi"].to_s end diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb index 9e29a93ea0..2e02b5e938 100644 --- a/activesupport/test/transliterate_test.rb +++ b/activesupport/test/transliterate_test.rb @@ -57,4 +57,53 @@ class TransliterateTest < ActiveSupport::TestCase end assert_equal "Can only transliterate strings. Received Object", exception.message end + + def test_transliterate_handles_strings_with_valid_utf8_encodings + string = String.new("A", encoding: Encoding::UTF_8) + assert_equal "A", ActiveSupport::Inflector.transliterate(string) + end + + def test_transliterate_handles_strings_with_valid_us_ascii_encodings + string = String.new("A", encoding: Encoding::US_ASCII) + transcoded = ActiveSupport::Inflector.transliterate(string) + assert_equal "A", transcoded + assert_equal Encoding::US_ASCII, transcoded.encoding + end + + def test_transliterate_handles_strings_with_valid_gb18030_encodings + string = String.new("A", encoding: Encoding::GB18030) + transcoded = ActiveSupport::Inflector.transliterate(string) + assert_equal "A", transcoded + assert_equal Encoding::GB18030, transcoded.encoding + end + + def test_transliterate_handles_strings_with_incompatible_encodings + incompatible_encodings = Encoding.list - [ + Encoding::UTF_8, + Encoding::US_ASCII, + Encoding::GB18030 + ] + incompatible_encodings.each do |encoding| + string = String.new("", encoding: encoding) + exception = assert_raises ArgumentError do + ActiveSupport::Inflector.transliterate(string) + end + assert_equal "Can not transliterate strings with #{encoding} encoding", exception.message + end + end + + def test_transliterate_handles_strings_with_invalid_utf8_bytes + string = String.new("\255", encoding: Encoding::UTF_8) + assert_equal "?", ActiveSupport::Inflector.transliterate(string) + end + + def test_transliterate_handles_strings_with_invalid_us_ascii_bytes + string = String.new("\255", encoding: Encoding::US_ASCII) + assert_equal "?", ActiveSupport::Inflector.transliterate(string) + end + + def test_transliterate_handles_strings_with_invalid_gb18030_bytes + string = String.new("\255", encoding: Encoding::GB18030) + assert_equal "?", ActiveSupport::Inflector.transliterate(string) + end end |