diff options
Diffstat (limited to 'activesupport')
67 files changed, 930 insertions, 474 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 29b22bb3f9..dad7005eb1 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,17 +1,62 @@ -* Allow the on_rotation proc used when decrypting/verifying a message to be - be passed at the constructor level. +* Allow initializing `thread_mattr_*` attributes via `:default` option + + class Scraper + thread_mattr_reader :client, default: Api::Client.new + end + + *Guilherme Mansur* + +* 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 { ... }) + 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) + crypt = ActiveSupport::MessageEncryptor.new('long_secret', on_rotation: proc { ... }) + crypt.decrypt_and_verify(encrypted_message) + crypt.decrypt_and_verify(another_encrypted_message) *Edouard Chin* 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 f55e821e10..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 diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index e055135bb4..a42553ae3a 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -7,6 +7,7 @@ require "active_support/core_ext/module/attribute_accessors" require "active_support/core_ext/numeric/bytes" require "active_support/core_ext/numeric/time" require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/try" require "active_support/core_ext/string/inflections" module ActiveSupport @@ -678,18 +679,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/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 11746e0537..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 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 c7a2378e41..27cb47eb6e 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 @@ -1,6 +1,7 @@ # frozen_string_literal: true require "active_support/core_ext/object/try" +require "active_support/core_ext/date_time/conversions" module DateAndTime module Calculations 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/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 5b48254646..cc42647879 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true require "active_support/xml_mini" -require "active_support/time" require "active_support/core_ext/object/blank" require "active_support/core_ext/object/to_param" require "active_support/core_ext/object/to_query" +require "active_support/core_ext/object/try" require "active_support/core_ext/array/wrap" require "active_support/core_ext/hash/reverse_merge" require "active_support/core_ext/string/inflections" diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb index 0c72cd7b47..5ff0e34d82 100644 --- a/activesupport/lib/active_support/core_ext/marshal.rb +++ b/activesupport/lib/active_support/core_ext/marshal.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "active_support/core_ext/string/inflections" + module ActiveSupport module MarshalWithAutoloading # :nodoc: def load(source, proc = nil) diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb index a6e87aeb68..ea4034303e 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb @@ -33,7 +33,7 @@ class Module # end # # Current.new.user # => NoMethodError - def thread_mattr_reader(*syms, instance_reader: true, instance_accessor: true) # :nodoc: + def thread_mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil) # :nodoc: syms.each do |sym| raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym) @@ -52,6 +52,8 @@ class Module end EOS end + + Thread.current["attr_" + name + "_#{sym}"] = default unless default.nil? end end alias :thread_cattr_reader :thread_mattr_reader @@ -74,7 +76,7 @@ class Module # end # # Current.new.user = "DHH" # => NoMethodError - def thread_mattr_writer(*syms, instance_writer: true, instance_accessor: true) # :nodoc: + def thread_mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil) # :nodoc: syms.each do |sym| raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym) @@ -93,6 +95,8 @@ class Module end EOS end + + public_send("#{sym}=", default) unless default.nil? end end alias :thread_cattr_writer :thread_mattr_writer @@ -136,8 +140,8 @@ class Module # # Current.new.user = "DHH" # => NoMethodError # Current.new.user # => NoMethodError - def thread_mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true) - thread_mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor) + def thread_mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil) + thread_mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default) thread_mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor) end alias :thread_cattr_accessor :thread_mattr_accessor diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 54271a3970..14d7f0c484 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -276,6 +276,11 @@ class Module # The delegated method must be public on the target, otherwise it will # 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) @@ -285,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 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/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb index 4ca24028b0..f6a14c08bc 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/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 5dc47b20c6..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 @@ -325,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! @@ -638,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) @@ -694,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 @@ -768,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 821e3f971e..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 @@ -90,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 1dad4f923e..b14842bf67 100644 --- a/activesupport/lib/active_support/descendants_tracker.rb +++ b/activesupport/lib/active_support/descendants_tracker.rb @@ -77,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/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 5b29a13894..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 diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 18f3f53879..33a17a7741 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "active_support/inflections" +require "active_support/core_ext/object/blank" module ActiveSupport # The Inflector transforms words from singular to plural, class names to table @@ -269,32 +270,36 @@ module ActiveSupport # NameError is raised when the name is not in CamelCase or the constant is # unknown. def constantize(camel_cased_word) - names = camel_cased_word.split("::") - - # Trigger a built-in NameError exception including the ill-formed constant in the message. - Object.const_get(camel_cased_word) if names.empty? - - # Remove the first blank element in case of '::ClassName' notation. - names.shift if names.size > 1 && names.first.empty? - - names.inject(Object) do |constant, name| - if constant == Object - constant.const_get(name) - else - candidate = constant.const_get(name) - next candidate if constant.const_defined?(name, false) - next candidate unless Object.const_defined?(name) - - # Go down the ancestors to check if it is owned directly. The check - # stops when we reach Object or the end of ancestors tree. - constant = constant.ancestors.inject(constant) do |const, ancestor| - break const if ancestor == Object - break ancestor if ancestor.const_defined?(name, false) - const + if camel_cased_word.blank? || !camel_cased_word.include?("::") + Object.const_get(camel_cased_word) + else + names = camel_cased_word.split("::") + + # Trigger a built-in NameError exception including the ill-formed constant in the message. + Object.const_get(camel_cased_word) if names.empty? + + # Remove the first blank element in case of '::ClassName' notation. + names.shift if names.size > 1 && names.first.empty? + + names.inject(Object) do |constant, name| + if constant == Object + constant.const_get(name) + else + candidate = constant.const_get(name) + next candidate if constant.const_defined?(name, false) + next candidate unless Object.const_defined?(name) + + # Go down the ancestors to check if it is owned directly. The check + # stops when we reach Object or the end of ancestors tree. + constant = constant.ancestors.inject(constant) do |const, ancestor| + break const if ancestor == Object + break ancestor if ancestor.const_defined?(name, false) + const + end + + # owner is in Object, so raise + constant.const_get(name, false) end - - # owner is in Object, so raise - constant.const_get(name, false) end end end @@ -371,7 +376,7 @@ module ActiveSupport last = parts.pop - parts.reverse.inject(last) do |acc, part| + parts.reverse!.inject(last) do |acc, part| part.empty? ? acc : "#{part}(::#{acc})?" end end diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index ec6e9ccb59..1899a1212d 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -56,14 +56,40 @@ 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) + string = string.dup if string.frozen? 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 @@ -91,7 +117,7 @@ module ActiveSupport # If the optional parameter +locale+ is specified, # the word will be parameterized as a word of that language. # By default, this parameter is set to <tt>nil</tt> and it will use - # the configured <tt>I18n.locale<tt>. + # the configured <tt>I18n.locale</tt>. def parameterize(string, separator: "-", preserve_case: false, locale: nil) # Replace accented chars with their ASCII equivalents. parameterized_string = transliterate(string, locale: locale) diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index db991c7a32..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. diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb index b8555c887b..2576bee4ff 100644 --- a/activesupport/lib/active_support/logger.rb +++ b/activesupport/lib/active_support/logger.rb @@ -14,7 +14,7 @@ module ActiveSupport # ActiveSupport::Logger.logger_outputs_to?(logger, STDOUT) # # => true def self.logger_outputs_to?(logger, *sources) - logdev = logger.instance_variable_get("@logdev") + logdev = logger.instance_variable_get(:@logdev) logger_source = logdev.dev if logdev.respond_to?(:dev) sources.any? { |source| source == logger_source } end 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/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 2ba3936cae..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) diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index aa602329ec..b0f30d2995 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -3,6 +3,7 @@ require "mutex_m" require "concurrent/map" require "set" +require "active_support/core_ext/object/try" module ActiveSupport module Notifications @@ -218,6 +219,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 7ab39c9bfb..24e1ab313a 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -52,7 +52,8 @@ module ActiveSupport 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 8f3d04aa5c..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 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 8e5595babf..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 @@ -109,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/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb index eb92fb4371..bf5a3b97ae 100644 --- a/activesupport/lib/active_support/per_thread_registry.rb +++ b/activesupport/lib/active_support/per_thread_registry.rb @@ -40,7 +40,7 @@ module ActiveSupport # If the class has an initializer, it must accept no arguments. module PerThreadRegistry def self.extended(object) - object.instance_variable_set "@per_thread_registry_key", object.name.freeze + object.instance_variable_set :@per_thread_registry_key, object.name.freeze end def instance 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/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb index d8a86d997e..75a7ca24c2 100644 --- a/activesupport/lib/active_support/tagged_logging.rb +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -32,15 +32,18 @@ module ActiveSupport def push_tags(*tags) tags.flatten.reject(&:blank?).tap do |new_tags| + @tags_text = nil current_tags.concat new_tags end end def pop_tags(size = 1) + @tags_text = nil current_tags.pop size end def clear_tags! + @tags_text = nil current_tags.clear end @@ -51,11 +54,13 @@ module ActiveSupport end def tags_text - tags = current_tags - if tags.one? - "[#{tags[0]}] " - elsif tags.any? - tags.collect { |tag| "[#{tag}] " }.join + @tags_text ||= begin + tags = current_tags + if tags.one? + "[#{tags[0]}] " + elsif tags.any? + tags.collect { |tag| "[#{tag}] " }.join + end end end end diff --git a/activesupport/lib/active_support/testing/parallelization.rb b/activesupport/lib/active_support/testing/parallelization.rb index f50a5e0554..96518a4a58 100644 --- a/activesupport/lib/active_support/testing/parallelization.rb +++ b/activesupport/lib/active_support/testing/parallelization.rb @@ -72,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 @@ -85,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 @@ -99,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 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/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/stores/mem_cache_store_test.rb b/activesupport/test/cache/stores/mem_cache_store_test.rb index 3917d14d1c..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 diff --git a/activesupport/test/cache/stores/redis_cache_store_test.rb b/activesupport/test/cache/stores/redis_cache_store_test.rb index a2177e0476..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 diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index cc37c4fa99..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 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/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 8572d56722..59d2d6f530 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -6,6 +6,7 @@ require "bigdecimal" require "active_support/core_ext/string/access" require "active_support/ordered_hash" require "active_support/core_ext/object/conversions" +require "active_support/core_ext/date/conversions" require "active_support/core_ext/object/deep_dup" require "active_support/inflections" diff --git a/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb b/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb index e0e331fc91..a2b3239884 100644 --- a/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb +++ b/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb @@ -4,23 +4,32 @@ require "abstract_unit" require "active_support/core_ext/module/attribute_accessors_per_thread" class ModuleAttributeAccessorPerThreadTest < ActiveSupport::TestCase - def setup - @class = Class.new do - thread_mattr_accessor :foo - thread_mattr_accessor :bar, instance_writer: false - thread_mattr_reader :shaq, instance_reader: false - thread_mattr_accessor :camp, instance_accessor: false - - def self.name; "MyClass" end - end + class MyClass + thread_mattr_accessor :foo + thread_mattr_accessor :bar, instance_writer: false + thread_mattr_reader :shaq, instance_reader: false + thread_mattr_accessor :camp, instance_accessor: false + end - @subclass = Class.new(@class) do - def self.name; "SubMyClass" end - end + class SubMyClass < MyClass + end + setup do + @class = MyClass + @subclass = SubMyClass @object = @class.new end + def test_can_initialize_with_default_value + Thread.new do + @class.thread_mattr_accessor :baz, default: "default_value" + + assert_equal "default_value", @class.baz + end.join + + assert_nil @class.baz + end + def test_should_use_mattr_default Thread.new do assert_nil @class.foo @@ -66,23 +75,23 @@ class ModuleAttributeAccessorPerThreadTest < ActiveSupport::TestCase threads = [] threads << Thread.new do @class.foo = "things" - sleep 1 + Thread.pass assert_equal "things", @class.foo end threads << Thread.new do @class.foo = "other things" - sleep 1 + Thread.pass assert_equal "other things", @class.foo end threads << Thread.new do @class.foo = "really other things" - sleep 1 + Thread.pass assert_equal "really other things", @class.foo end - threads.each { |t| t.join } + threads.each(&:join) end def test_should_raise_name_error_if_attribute_name_is_invalid diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index ec9ecd06ee..dd36a9373a 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -111,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 @@ -411,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/acts_like_test.rb b/activesupport/test/core_ext/object/acts_like_test.rb index 31241caf0a..8aa9eb036a 100644 --- a/activesupport/test/core_ext/object/acts_like_test.rb +++ b/activesupport/test/core_ext/object/acts_like_test.rb @@ -1,7 +1,10 @@ # frozen_string_literal: true require "abstract_unit" -require "active_support/core_ext/object" +require "active_support/core_ext/date/acts_like" +require "active_support/core_ext/time/acts_like" +require "active_support/core_ext/date_time/acts_like" +require "active_support/core_ext/object/acts_like" class ObjectTests < ActiveSupport::TestCase class DuckTime diff --git a/activesupport/test/core_ext/object/deep_dup_test.rb b/activesupport/test/core_ext/object/deep_dup_test.rb index 1fb26ebac7..8e5e28c513 100644 --- a/activesupport/test/core_ext/object/deep_dup_test.rb +++ b/activesupport/test/core_ext/object/deep_dup_test.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "abstract_unit" -require "active_support/core_ext/object" +require "active_support/core_ext/object/deep_dup" class DeepDupTest < ActiveSupport::TestCase def test_array_deep_dup diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb index c9af2cb624..a577c30c40 100644 --- a/activesupport/test/core_ext/object/duplicable_test.rb +++ b/activesupport/test/core_ext/object/duplicable_test.rb @@ -6,7 +6,7 @@ require "active_support/core_ext/object/duplicable" require "active_support/core_ext/numeric/time" class DuplicableTest < ActiveSupport::TestCase - RAISE_DUP = [method(:puts)] + 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 diff --git a/activesupport/test/core_ext/object/instance_variables_test.rb b/activesupport/test/core_ext/object/instance_variables_test.rb index 9052d209a3..dd710e9349 100644 --- a/activesupport/test/core_ext/object/instance_variables_test.rb +++ b/activesupport/test/core_ext/object/instance_variables_test.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "abstract_unit" -require "active_support/core_ext/object" +require "active_support/core_ext/object/instance_variables" class ObjectInstanceVariableTest < ActiveSupport::TestCase def setup diff --git a/activesupport/test/core_ext/object/json_gem_encoding_test.rb b/activesupport/test/core_ext/object/json_gem_encoding_test.rb index 4cdb6ed09f..eef02f7458 100644 --- a/activesupport/test/core_ext/object/json_gem_encoding_test.rb +++ b/activesupport/test/core_ext/object/json_gem_encoding_test.rb @@ -22,7 +22,7 @@ class JsonGemEncodingTest < ActiveSupport::TestCase JSONTest::EncodingTestCases.constants.each_with_index do |name| JSONTest::EncodingTestCases.const_get(name).each_with_index do |(subject, _), i| - test("#{name[0..-6].underscore} #{i}") do + test("#{name[0..-6]} #{i}") do assert_same_with_or_without_active_support(subject) end end diff --git a/activesupport/test/core_ext/object/try_test.rb b/activesupport/test/core_ext/object/try_test.rb index a838334034..d501b5edce 100644 --- a/activesupport/test/core_ext/object/try_test.rb +++ b/activesupport/test/core_ext/object/try_test.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "abstract_unit" -require "active_support/core_ext/object" +require "active_support/core_ext/object/try" class ObjectTryTest < ActiveSupport::TestCase def setup 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/current_attributes_test.rb b/activesupport/test/current_attributes_test.rb index adbdc646bc..4cbf462da0 100644 --- a/activesupport/test/current_attributes_test.rb +++ b/activesupport/test/current_attributes_test.rb @@ -9,7 +9,7 @@ class CurrentAttributesTest < ActiveSupport::TestCase attribute :world, :account, :person, :request delegate :time_zone, to: :person - before_reset { Session.previous = person.try(:id) } + before_reset { Session.previous = person&.id } resets do Time.zone = "UTC" @@ -18,13 +18,13 @@ class CurrentAttributesTest < ActiveSupport::TestCase def account=(account) super - self.person = "#{account}'s person" + self.person = Person.new(1, "#{account}'s person") end def person=(person) super - Time.zone = person.try(:time_zone) - Session.current = person.try(:id) + Time.zone = person&.time_zone + Session.current = person&.id end def request @@ -63,7 +63,7 @@ class CurrentAttributesTest < ActiveSupport::TestCase test "set attribute via overwritten method" do Current.account = "account/1" assert_equal "account/1", Current.account - assert_equal "account/1's person", Current.person + assert_equal "account/1's person", Current.person.name end test "set auxiliary class via overwritten method" do diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 003a0dbccb..6bad69f7f2 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -592,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_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/file_update_checker_shared_tests.rb b/activesupport/test/file_update_checker_shared_tests.rb index 84d89fa0a7..bd056a1576 100644 --- a/activesupport/test/file_update_checker_shared_tests.rb +++ b/activesupport/test/file_update_checker_shared_tests.rb @@ -3,294 +3,298 @@ require "fileutils" module FileUpdateCheckerSharedTests - extend ActiveSupport::Testing::Declarative - include FileUtils + def self.included(kls) + kls.class_eval do + extend ActiveSupport::Testing::Declarative + include FileUtils - def tmpdir - @tmpdir - end + def tmpdir + @tmpdir + end - def tmpfile(name) - File.join(tmpdir, name) - end + def tmpfile(name) + File.join(tmpdir, name) + end - def tmpfiles - @tmpfiles ||= %w(foo.rb bar.rb baz.rb).map { |f| tmpfile(f) } - end + def tmpfiles + @tmpfiles ||= %w(foo.rb bar.rb baz.rb).map { |f| tmpfile(f) } + end - def run(*args) - capture_exceptions do - Dir.mktmpdir(nil, __dir__) { |dir| @tmpdir = dir; super } - end - end + def run(*args) + capture_exceptions do + Dir.mktmpdir(nil, __dir__) { |dir| @tmpdir = dir; super } + end + end - test "should not execute the block if no paths are given" do - silence_warnings { require "listen" } - i = 0 + test "should not execute the block if no paths are given" do + silence_warnings { require "listen" } + i = 0 - checker = new_checker { i += 1 } + checker = new_checker { i += 1 } - assert_not checker.execute_if_updated - assert_equal 0, i - end + assert_not checker.execute_if_updated + assert_equal 0, i + end - test "should not execute the block if no files change" do - i = 0 + test "should not execute the block if no files change" do + i = 0 - FileUtils.touch(tmpfiles) + FileUtils.touch(tmpfiles) - checker = new_checker(tmpfiles) { i += 1 } + checker = new_checker(tmpfiles) { i += 1 } - assert_not checker.execute_if_updated - assert_equal 0, i - end + assert_not checker.execute_if_updated + assert_equal 0, i + end - test "should execute the block once when files are created" do - i = 0 + test "should execute the block once when files are created" do + i = 0 - checker = new_checker(tmpfiles) { i += 1 } + checker = new_checker(tmpfiles) { i += 1 } - touch(tmpfiles) - wait + touch(tmpfiles) + wait - assert checker.execute_if_updated - assert_equal 1, i - end + assert checker.execute_if_updated + assert_equal 1, i + end - test "should execute the block once when files are modified" do - i = 0 + test "should execute the block once when files are modified" do + i = 0 - FileUtils.touch(tmpfiles) + FileUtils.touch(tmpfiles) - checker = new_checker(tmpfiles) { i += 1 } + checker = new_checker(tmpfiles) { i += 1 } - touch(tmpfiles) - wait + touch(tmpfiles) + wait - assert checker.execute_if_updated - assert_equal 1, i - end + assert checker.execute_if_updated + assert_equal 1, i + end - test "should execute the block once when files are deleted" do - i = 0 + test "should execute the block once when files are deleted" do + i = 0 - FileUtils.touch(tmpfiles) + FileUtils.touch(tmpfiles) - checker = new_checker(tmpfiles) { i += 1 } + checker = new_checker(tmpfiles) { i += 1 } - rm_f(tmpfiles) - wait + rm_f(tmpfiles) + wait - assert checker.execute_if_updated - assert_equal 1, i - end + assert checker.execute_if_updated + assert_equal 1, i + end - test "updated should become true when watched files are created" do - i = 0 + test "updated should become true when watched files are created" do + i = 0 - checker = new_checker(tmpfiles) { i += 1 } - assert_not_predicate checker, :updated? + checker = new_checker(tmpfiles) { i += 1 } + assert_not_predicate checker, :updated? - touch(tmpfiles) - wait + touch(tmpfiles) + wait - assert_predicate checker, :updated? - end + assert_predicate checker, :updated? + end - test "updated should become true when watched files are modified" do - i = 0 + test "updated should become true when watched files are modified" do + i = 0 - FileUtils.touch(tmpfiles) + FileUtils.touch(tmpfiles) - checker = new_checker(tmpfiles) { i += 1 } - assert_not_predicate checker, :updated? + checker = new_checker(tmpfiles) { i += 1 } + assert_not_predicate checker, :updated? - touch(tmpfiles) - wait + touch(tmpfiles) + wait - assert_predicate checker, :updated? - end + assert_predicate checker, :updated? + end - test "updated should become true when watched files are deleted" do - i = 0 + test "updated should become true when watched files are deleted" do + i = 0 - FileUtils.touch(tmpfiles) + FileUtils.touch(tmpfiles) - checker = new_checker(tmpfiles) { i += 1 } - assert_not_predicate checker, :updated? + checker = new_checker(tmpfiles) { i += 1 } + assert_not_predicate checker, :updated? - rm_f(tmpfiles) - wait + rm_f(tmpfiles) + wait - assert_predicate checker, :updated? - end + assert_predicate checker, :updated? + end - test "should be robust to handle files with wrong modified time" do - i = 0 + test "should be robust to handle files with wrong modified time" do + i = 0 - FileUtils.touch(tmpfiles) + FileUtils.touch(tmpfiles) - now = Time.now - time = Time.mktime(now.year + 1, now.month, now.day) # wrong mtime from the future - File.utime(time, time, tmpfiles[0]) + now = Time.now + time = Time.mktime(now.year + 1, now.month, now.day) # wrong mtime from the future + File.utime(time, time, tmpfiles[0]) - checker = new_checker(tmpfiles) { i += 1 } + checker = new_checker(tmpfiles) { i += 1 } - touch(tmpfiles[1..-1]) - wait + touch(tmpfiles[1..-1]) + wait - assert checker.execute_if_updated - assert_equal 1, i - end + assert checker.execute_if_updated + assert_equal 1, i + end - test "should return max_time for files with mtime = Time.at(0)" do - i = 0 + test "should return max_time for files with mtime = Time.at(0)" do + i = 0 - FileUtils.touch(tmpfiles) + FileUtils.touch(tmpfiles) - time = Time.at(0) # wrong mtime from the future - File.utime(time, time, tmpfiles[0]) + time = Time.at(0) # wrong mtime from the future + File.utime(time, time, tmpfiles[0]) - checker = new_checker(tmpfiles) { i += 1 } + checker = new_checker(tmpfiles) { i += 1 } - touch(tmpfiles[1..-1]) - wait + touch(tmpfiles[1..-1]) + wait - assert checker.execute_if_updated - assert_equal 1, i - end + assert checker.execute_if_updated + assert_equal 1, i + end - test "should cache updated result until execute" do - i = 0 + test "should cache updated result until execute" do + i = 0 - checker = new_checker(tmpfiles) { i += 1 } - assert_not_predicate checker, :updated? + checker = new_checker(tmpfiles) { i += 1 } + assert_not_predicate checker, :updated? - touch(tmpfiles) - wait + touch(tmpfiles) + wait - assert_predicate checker, :updated? - checker.execute - assert_not_predicate checker, :updated? - end + assert_predicate checker, :updated? + checker.execute + assert_not_predicate checker, :updated? + end - test "should execute the block if files change in a watched directory one extension" do - i = 0 + test "should execute the block if files change in a watched directory one extension" do + i = 0 - checker = new_checker([], tmpdir => :rb) { i += 1 } + checker = new_checker([], tmpdir => :rb) { i += 1 } - touch(tmpfile("foo.rb")) - wait + touch(tmpfile("foo.rb")) + wait - assert checker.execute_if_updated - assert_equal 1, i - end + assert checker.execute_if_updated + assert_equal 1, i + end - test "should execute the block if files change in a watched directory any extensions" do - i = 0 + test "should execute the block if files change in a watched directory any extensions" do + i = 0 - checker = new_checker([], tmpdir => []) { i += 1 } + checker = new_checker([], tmpdir => []) { i += 1 } - touch(tmpfile("foo.rb")) - wait + touch(tmpfile("foo.rb")) + wait - assert checker.execute_if_updated - assert_equal 1, i - end + assert checker.execute_if_updated + assert_equal 1, i + end - test "should execute the block if files change in a watched directory several extensions" do - i = 0 + test "should execute the block if files change in a watched directory several extensions" do + i = 0 - checker = new_checker([], tmpdir => [:rb, :txt]) { i += 1 } + checker = new_checker([], tmpdir => [:rb, :txt]) { i += 1 } - touch(tmpfile("foo.rb")) - wait + touch(tmpfile("foo.rb")) + wait - assert checker.execute_if_updated - assert_equal 1, i + assert checker.execute_if_updated + assert_equal 1, i - touch(tmpfile("foo.txt")) - wait + touch(tmpfile("foo.txt")) + wait - assert checker.execute_if_updated - assert_equal 2, i - end + assert checker.execute_if_updated + assert_equal 2, i + end - test "should not execute the block if the file extension is not watched" do - i = 0 + test "should not execute the block if the file extension is not watched" do + i = 0 - checker = new_checker([], tmpdir => :txt) { i += 1 } + checker = new_checker([], tmpdir => :txt) { i += 1 } - touch(tmpfile("foo.rb")) - wait + touch(tmpfile("foo.rb")) + wait - assert_not checker.execute_if_updated - assert_equal 0, i - end + assert_not checker.execute_if_updated + assert_equal 0, i + end - test "does not assume files exist on instantiation" do - i = 0 + test "does not assume files exist on instantiation" do + i = 0 - non_existing = tmpfile("non_existing.rb") - checker = new_checker([non_existing]) { i += 1 } + non_existing = tmpfile("non_existing.rb") + checker = new_checker([non_existing]) { i += 1 } - touch(non_existing) - wait + touch(non_existing) + wait - assert checker.execute_if_updated - assert_equal 1, i - end + assert checker.execute_if_updated + assert_equal 1, i + end - test "detects files in new subdirectories" do - i = 0 + test "detects files in new subdirectories" do + i = 0 - checker = new_checker([], tmpdir => :rb) { i += 1 } + checker = new_checker([], tmpdir => :rb) { i += 1 } - subdir = tmpfile("subdir") - mkdir(subdir) - wait + subdir = tmpfile("subdir") + mkdir(subdir) + wait - assert_not checker.execute_if_updated - assert_equal 0, i + assert_not checker.execute_if_updated + assert_equal 0, i - touch(File.join(subdir, "nested.rb")) - wait + touch(File.join(subdir, "nested.rb")) + wait - assert checker.execute_if_updated - assert_equal 1, i - end + assert checker.execute_if_updated + assert_equal 1, i + end - test "looked up extensions are inherited in subdirectories not listening to them" do - i = 0 + test "looked up extensions are inherited in subdirectories not listening to them" do + i = 0 - subdir = tmpfile("subdir") - mkdir(subdir) + subdir = tmpfile("subdir") + mkdir(subdir) - checker = new_checker([], tmpdir => :rb, subdir => :txt) { i += 1 } + checker = new_checker([], tmpdir => :rb, subdir => :txt) { i += 1 } - touch(tmpfile("new.txt")) - wait + touch(tmpfile("new.txt")) + wait - assert_not checker.execute_if_updated - assert_equal 0, i + assert_not checker.execute_if_updated + assert_equal 0, i - # subdir does not look for Ruby files, but its parent tmpdir does. - touch(File.join(subdir, "nested.rb")) - wait + # subdir does not look for Ruby files, but its parent tmpdir does. + touch(File.join(subdir, "nested.rb")) + wait - assert checker.execute_if_updated - assert_equal 1, i + assert checker.execute_if_updated + assert_equal 1, i - touch(File.join(subdir, "nested.txt")) - wait + touch(File.join(subdir, "nested.txt")) + wait - assert checker.execute_if_updated - assert_equal 2, i - end + assert checker.execute_if_updated + assert_equal 2, i + end - test "initialize raises an ArgumentError if no block given" do - assert_raise ArgumentError do - new_checker([]) + test "initialize raises an ArgumentError if no block given" do + assert_raise ArgumentError do + new_checker([]) + end + end end end 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 097aa8b5f8..f807497709 100644 --- a/activesupport/test/message_encryptor_test.rb +++ b/activesupport/test/message_encryptor_test.rb @@ -174,7 +174,7 @@ class MessageEncryptorTest < ActiveSupport::TestCase 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 = false + 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" @@ -188,7 +188,7 @@ class MessageEncryptorTest < ActiveSupport::TestCase 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 = false + 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" diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index e0e0d9afc0..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, "わ") diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index c9c63680e4..08277e5436 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -41,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 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/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..f13c5efa47 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).freeze + 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).freeze + 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).freeze + 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).freeze + 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).freeze + 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).freeze + assert_equal "?", ActiveSupport::Inflector.transliterate(string) + end + + def test_transliterate_handles_strings_with_invalid_gb18030_bytes + string = String.new("\255", encoding: Encoding::GB18030).freeze + assert_equal "?", ActiveSupport::Inflector.transliterate(string) + end end diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb index 18a3f2ca66..73e7f40b0d 100644 --- a/activesupport/test/xml_mini_test.rb +++ b/activesupport/test/xml_mini_test.rb @@ -5,6 +5,7 @@ require "active_support/xml_mini" require "active_support/builder" require "active_support/core_ext/hash" require "active_support/core_ext/big_decimal" +require "active_support/core_ext/date/conversions" require "yaml" module XmlMiniTest |