diff options
Diffstat (limited to 'activesupport')
166 files changed, 2415 insertions, 2645 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index fac91c31da..6f99155199 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,265 +1 @@ -* Ensure duration parsing is consistent across DST changes - - Previously `ActiveSupport::Duration.parse` used `Time.current` and - `Time#advance` to calculate the number of seconds in the duration - from an arbitrary collection of parts. However as `advance` tries to - be consistent across DST boundaries this meant that either the - duration was shorter or longer depending on the time of year. - - This was fixed by using an absolute reference point in UTC which - isn't subject to DST transitions. An arbitrary date of Jan 1st, 2000 - was chosen for no other reason that it seemed appropriate. - - Additionally, duration parsing should now be marginally faster as we - are no longer creating instances of `ActiveSupport::TimeWithZone` - every time we parse a duration string. - - Fixes #26941. - - *Andrew White* - -* Use `Hash#compact` and `Hash#compact!` from Ruby 2.4. Old Ruby versions - will continue to get these methods from Active Support as before. - - *Prathamesh Sonpatki* - -* Fix `ActiveSupport::TimeZone#strptime`. - Support for timestamps in format of seconds (%s) and milliseconds (%Q). - - Fixes #26840. - - *Lev Denisov* - -* Fix `DateAndTime::Calculations#copy_time_to`. Copy `nsec` instead of `usec`. - - Jumping forward or backward between weeks now preserves nanosecond digits. - - *Josua Schmid* - -* Fix `ActiveSupport::TimeWithZone#in` across DST boundaries. - - Previously calls to `in` were being sent to the non-DST aware - method `Time#since` via `method_missing`. It is now aliased to - the DST aware `ActiveSupport::TimeWithZone#+` which handles - transitions across DST boundaries, e.g: - - Time.zone = "US/Eastern" - - t = Time.zone.local(2016,11,6,1) - # => Sun, 06 Nov 2016 01:00:00 EDT -05:00 - - t.in(1.hour) - # => Sun, 06 Nov 2016 01:00:00 EST -05:00 - - Fixes #26580. - - *Thomas Balthazar* - -* Remove unused parameter `options = nil` for `#clear` of - `ActiveSupport::Cache::Strategy::LocalCache::LocalStore` and - `ActiveSupport::Cache::Strategy::LocalCache`. - - *Yosuke Kabuto* - -* Fix `thread_mattr_accessor` subclass no longer overwrites parent. - - Assigning a value to a subclass using `thread_mattr_accessor` no - longer changes the value of the parent class. This brings the - behavior inline with the documentation. - - Given: - - class Account - thread_mattr_accessor :user - end - - class Customer < Account - end - - Account.user = "DHH" - Customer.user = "Rafael" - - Before: - - Account.user # => "Rafael" - - After: - - Account.user # => "DHH" - - *Shinichi Maeshima* - -* Since weeks are no longer converted to days, add `:weeks` to the list of - parts that `ActiveSupport::TimeWithZone` will recognize as possibly being - of variable duration to take account of DST transitions. - - Fixes #26039. - - *Andrew White* - -* Defines `Regexp.match?` for Ruby versions prior to 2.4. The predicate - has the same interface, but it does not have the performance boost. Its - purpose is to be able to write 2.4 compatible code. - - *Xavier Noria* - -* Allow `MessageEncryptor` to take advantage of authenticated encryption modes. - - AEAD modes like `aes-256-gcm` provide both confidentiality and data - authenticity, eliminating the need to use `MessageVerifier` to check if the - encrypted data has been tampered with. This speeds up encryption/decryption - and results in shorter cipher text. - - *Bart de Water* - -* Introduce `assert_changes` and `assert_no_changes`. - - `assert_changes` is a more general `assert_difference` that works with any - value. - - assert_changes 'Error.current', from: nil, to: 'ERR' do - expected_bad_operation - end - - Can be called with strings, to be evaluated in the binding (context) of - the block given to the assertion, or a lambda. - - assert_changes -> { Error.current }, from: nil, to: 'ERR' do - expected_bad_operation - end - - The `from` and `to` arguments are compared with the case operator (`===`). - - assert_changes 'Error.current', from: nil, to: Error do - expected_bad_operation - end - - This is pretty useful, if you need to loosely compare a value. For example, - you need to test a token has been generated and it has that many random - characters. - - user = User.start_registration - assert_changes 'user.token', to: /\w{32}/ do - user.finish_registration - end - - *Genadi Samokovarov* - -* Add `:fallback_string` option to `Array#to_sentence`. If an empty array - calls the function and a fallback string option is set then it returns the - fallback string other than an empty string. - - *Mohamed Osama* - -* Fix `ActiveSupport::TimeZone#strptime`. Now raises `ArgumentError` when the - given time doesn't match the format. The error is the same as the one given - by Ruby's `Date.strptime`. Previously it raised - `NoMethodError: undefined method empty? for nil:NilClass.` due to a bug. - - Fixes #25701. - - *John Gesimondo* - -* `travel/travel_to` travel time helpers, now raise on nested calls, - as this can lead to confusing time stubbing. - - Instead of: - - travel_to 2.days.from_now do - # 2 days from today - travel_to 3.days.from_now do - # 5 days from today - end - end - - preferred way to achieve above is: - - travel 2.days do - # 2 days from today - end - - travel 5.days do - # 5 days from today - end - - *Vipul A M* - -* Support parsing JSON time in ISO8601 local time strings in - `ActiveSupport::JSON.decode` when `parse_json_times` is enabled. - Strings in the format of `YYYY-MM-DD hh:mm:ss` (without a `Z` at - the end) will be parsed in the local timezone (`Time.zone`). In - addition, date strings (`YYYY-MM-DD`) are now parsed into `Date` - objects. - - *Grzegorz Witek* - -* Fixed `ActiveSupport::Logger.broadcast` so that calls to `#silence` now - properly delegate to all loggers. Silencing now properly suppresses logging - to both the log and the console. - - *Kevin McPhillips* - -* Remove deprecated arguments in `assert_nothing_raised`. - - *Rafel Mendonça França* - -* `Date.to_s` doesn't produce too many spaces. For example, `to_s(:short)` - will now produce `01 Feb` instead of ` 1 Feb`. - - Fixes #25251. - - *Sean Griffin* - -* Introduce `Module#delegate_missing_to`. - - When building a decorator, a common pattern emerges: - - class Partition - def initialize(first_event) - @events = [ first_event ] - end - - def people - if @events.first.detail.people.any? - @events.collect { |e| Array(e.detail.people) }.flatten.uniq - else - @events.collect(&:creator).uniq - end - end - - private - def respond_to_missing?(name, include_private = false) - @events.respond_to?(name, include_private) - end - - def method_missing(method, *args, &block) - @events.send(method, *args, &block) - end - end - - With `Module#delegate_missing_to`, the above is condensed to: - - class Partition - delegate_missing_to :@events - - def initialize(first_event) - @events = [ first_event ] - end - - def people - if @events.first.detail.people.any? - @events.collect { |e| Array(e.detail.people) }.flatten.uniq - else - @events.collect(&:creator).uniq - end - end - end - - *Genadi Samokovarov*, *DHH* - -* Rescuable: If a handler doesn't match the exception, check for handlers - matching the exception's cause. - - *Jeremy Daer* - -Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activesupport/CHANGELOG.md) for previous changes. +Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activesupport/CHANGELOG.md) for previous changes. diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE index 40235833ba..6b3cead1a7 100644 --- a/activesupport/MIT-LICENSE +++ b/activesupport/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2005-2016 David Heinemeier Hansson +Copyright (c) 2005-2017 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables index 5d912f375c..aa36a01b5b 100755 --- a/activesupport/bin/generate_tables +++ b/activesupport/bin/generate_tables @@ -8,6 +8,7 @@ end require "open-uri" require "tmpdir" +require "fileutils" module ActiveSupport module Multibyte @@ -101,9 +102,10 @@ module ActiveSupport def parse SOURCES.each do |type, url| - filename = File.join(Dir.tmpdir, "#{url.split('/').last}") + filename = File.join(Dir.tmpdir, UNICODE_VERSION, "#{url.split('/').last}") unless File.exist?(filename) $stderr.puts "Downloading #{url.split('/').last}" + FileUtils.mkdir_p(File.dirname(filename)) File.open(filename, "wb") do |target| open(url) do |source| source.each_line { |line| target.write line } diff --git a/activesupport/bin/test b/activesupport/bin/test index 84a05bba08..a7beb14b27 100755 --- a/activesupport/bin/test +++ b/activesupport/bin/test @@ -2,5 +2,3 @@ COMPONENT_ROOT = File.expand_path("..", __dir__) require File.expand_path("../tools/test", COMPONENT_ROOT) - -exit Minitest.run(ARGV) diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 2fc42e1975..03e3ce821a 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2005-2016 David Heinemeier Hansson +# Copyright (c) 2005-2017 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -80,11 +80,15 @@ module ActiveSupport cattr_accessor :test_order # :nodoc: def self.halt_callback_chains_on_return_false - Callbacks.halt_and_display_warning_on_return_false + ActiveSupport::Deprecation.warn(<<-MSG.squish) + ActiveSupport.halt_callback_chains_on_return_false is deprecated and will be removed in Rails 5.2. + MSG end def self.halt_callback_chains_on_return_false=(value) - Callbacks.halt_and_display_warning_on_return_false = value + ActiveSupport::Deprecation.warn(<<-MSG.squish) + ActiveSupport.halt_callback_chains_on_return_false= is deprecated and will be removed in Rails 5.2. + MSG end def self.to_time_preserves_timezone diff --git a/activesupport/lib/active_support/array_inquirer.rb b/activesupport/lib/active_support/array_inquirer.rb index 85122e39b2..befa1746c6 100644 --- a/activesupport/lib/active_support/array_inquirer.rb +++ b/activesupport/lib/active_support/array_inquirer.rb @@ -20,7 +20,7 @@ module ActiveSupport # variants.any?(:phone, :tablet) # => true # variants.any?('phone', 'desktop') # => true # variants.any?(:desktop, :watch) # => false - def any?(*candidates, &block) + def any?(*candidates) if candidates.none? super else @@ -32,7 +32,7 @@ module ActiveSupport private def respond_to_missing?(name, include_private = false) - name[-1] == "?" + (name[-1] == "?") || super end def method_missing(name, *args) diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index 169a58ecd1..e47c90597f 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -12,7 +12,7 @@ module ActiveSupport # is to exclude the output of a noisy library from the backtrace, so that you # can focus on the rest. # - # bc = BacktraceCleaner.new + # 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.clean(exception.backtrace) # perform the cleanup diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 02218b8eaa..4d8c2046e8 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -6,7 +6,6 @@ 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/string/inflections" -require "active_support/core_ext/string/strip" module ActiveSupport # See ActiveSupport::Cache::Store for documentation. @@ -71,8 +70,8 @@ module ActiveSupport # each of elements in the array will be turned into parameters/keys and # concatenated into a single key. For example: # - # expand_cache_key([:foo, :bar]) # => "foo/bar" - # expand_cache_key([:foo, :bar], "namespace") # => "namespace/foo/bar" + # ActiveSupport::Cache.expand_cache_key([:foo, :bar]) # => "foo/bar" + # ActiveSupport::Cache.expand_cache_key([:foo, :bar], "namespace") # => "namespace/foo/bar" # # The +key+ argument can also respond to +cache_key+ or +to_param+. def expand_cache_key(key, namespace = nil) @@ -471,12 +470,12 @@ module ActiveSupport raise NotImplementedError.new("#{self.class.name} does not support clear") end - protected + private # Adds the namespace defined in the options to a pattern designed to # match keys. Implementations that support delete_matched should call # this method to translate a pattern that matches names into one that # matches namespaced keys. - def key_matcher(pattern, options) + def key_matcher(pattern, options) # :doc: prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace] if prefix source = pattern.source @@ -493,25 +492,24 @@ module ActiveSupport # Reads an entry from the cache implementation. Subclasses must implement # this method. - def read_entry(key, options) # :nodoc: + def read_entry(key, options) raise NotImplementedError.new end # Writes an entry to the cache implementation. Subclasses must implement # this method. - def write_entry(key, entry, options) # :nodoc: + def write_entry(key, entry, options) raise NotImplementedError.new end # Deletes an entry from the cache implementation. Subclasses must # implement this method. - def delete_entry(key, options) # :nodoc: + def delete_entry(key, options) raise NotImplementedError.new end - private # Merges the default options with ones specific to a method call. - def merged_options(call_options) # :nodoc: + def merged_options(call_options) if call_options options.merge(call_options) else @@ -522,7 +520,7 @@ module ActiveSupport # Expands key to be a consistent string value. Invokes +cache_key+ if # object responds to +cache_key+. Otherwise, +to_param+ method will be # called. If the key is a Hash, then keys will be sorted alphabetically. - def expanded_key(key) # :nodoc: + def expanded_key(key) return key.cache_key.to_s if key.respond_to?(:cache_key) case key @@ -549,14 +547,6 @@ module ActiveSupport key end - def namespaced_key(*args) - ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) - `namespaced_key` is deprecated and will be removed from Rails 5.1. - Please use `normalize_key` which will return a fully resolved key. - MESSAGE - normalize_key(*args) - end - def instrument(operation, key, options = nil) log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" } diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 1971ff182e..d5c8585816 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -66,7 +66,7 @@ module ActiveSupport end end - protected + private def read_entry(key, options) if File.exist?(key) @@ -98,9 +98,8 @@ module ActiveSupport end end - private # Lock a file for a block so only one process can modify it at a time. - def lock_file(file_name, &block) # :nodoc: + def lock_file(file_name, &block) if File.exist?(file_name) File.open(file_name, "r+") do |f| begin @@ -138,14 +137,6 @@ module ActiveSupport File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths) end - def key_file_path(key) - ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) - `key_file_path` is deprecated and will be removed from Rails 5.1. - Please use `normalize_key` which will return a fully resolved key or nothing. - MESSAGE - key - end - # Translate a file path into a key. def file_path_key(path) fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 00f4480308..e09cee3335 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -26,7 +26,7 @@ module ActiveSupport class MemCacheStore < Store # Provide support for raw values in the local cache strategy. module LocalCacheWithRaw # :nodoc: - protected + private def read_entry(key, options) entry = super if options[:raw] && local_cache && entry @@ -35,7 +35,7 @@ module ActiveSupport entry end - def write_entry(key, entry, options) # :nodoc: + def write_entry(key, entry, options) if options[:raw] && local_cache raw_entry = Entry.new(entry.value.to_s) raw_entry.expires_at = entry.expires_at @@ -97,7 +97,7 @@ module ActiveSupport options = merged_options(options) keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }] - raw_values = @data.get_multi(keys_to_names.keys, raw: true) + raw_values = @data.get_multi(keys_to_names.keys) values = {} raw_values.each do |key, value| entry = deserialize_entry(value) @@ -143,14 +143,14 @@ module ActiveSupport @data.stats end - protected + private # Read an entry from the cache. - def read_entry(key, options) # :nodoc: + def read_entry(key, options) rescue_error_with(nil) { deserialize_entry(@data.get(key, options)) } end # Write an entry to the cache. - def write_entry(key, entry, options) # :nodoc: + def write_entry(key, entry, options) method = options && options[:unless_exist] ? :add : :set value = options[:raw] ? entry.value.to_s : entry expires_in = options[:expires_in].to_i @@ -164,12 +164,10 @@ module ActiveSupport end # Delete an entry from the cache. - def delete_entry(key, options) # :nodoc: + def delete_entry(key, options) rescue_error_with(false) { @data.delete(key) } end - private - # Memcache keys are binaries. So we need to force their encoding to binary # before applying the regular expression to ensure we are escaping all # characters properly. @@ -181,14 +179,6 @@ module ActiveSupport key end - def escape_key(key) - ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) - `escape_key` is deprecated and will be removed from Rails 5.1. - Please use `normalize_key` which will return a fully resolved key or nothing. - MESSAGE - key - end - def deserialize_entry(raw_value) if raw_value entry = Marshal.load(raw_value) rescue raw_value diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb index 968e72d765..56fe1457d0 100644 --- a/activesupport/lib/active_support/cache/memory_store.rb +++ b/activesupport/lib/active_support/cache/memory_store.rb @@ -28,6 +28,7 @@ module ActiveSupport @pruning = false end + # Delete all data stored in a given cache store. def clear(options = nil) synchronize do @data.clear @@ -83,6 +84,7 @@ module ActiveSupport modify_value(name, -amount, options) end + # Deletes cache entries if the cache key matches a given pattern. def delete_matched(matcher, options = nil) options = merged_options(options) instrument(:delete_matched, matcher.inspect) do @@ -104,15 +106,15 @@ module ActiveSupport @monitor.synchronize(&block) end - protected + private PER_ENTRY_OVERHEAD = 240 - def cached_size(key, entry) # :nodoc: + def cached_size(key, entry) key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD end - def read_entry(key, options) # :nodoc: + def read_entry(key, options) entry = @data[key] synchronize do if entry @@ -124,7 +126,7 @@ module ActiveSupport entry end - def write_entry(key, entry, options) # :nodoc: + def write_entry(key, entry, options) entry.dup_value! synchronize do old_entry = @data[key] @@ -141,7 +143,7 @@ module ActiveSupport end end - def delete_entry(key, options) # :nodoc: + def delete_entry(key, options) synchronize do @key_access.delete(key) entry = @data.delete(key) @@ -150,8 +152,6 @@ module ActiveSupport end end - private - def modify_value(name, amount, options) synchronize do options = merged_options(options) diff --git a/activesupport/lib/active_support/cache/null_store.rb b/activesupport/lib/active_support/cache/null_store.rb index 0564ce5312..550659fc56 100644 --- a/activesupport/lib/active_support/cache/null_store.rb +++ b/activesupport/lib/active_support/cache/null_store.rb @@ -25,15 +25,15 @@ module ActiveSupport def delete_matched(matcher, options = nil) end - protected - def read_entry(key, options) # :nodoc: + private + def read_entry(key, options) end - def write_entry(key, entry, options) # :nodoc: + def write_entry(key, entry, options) true end - def delete_entry(key, options) # :nodoc: + def delete_entry(key, options) false end end diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index f41cc23eb0..672eb2bb80 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -105,8 +105,8 @@ module ActiveSupport value end - protected - def read_entry(key, options) # :nodoc: + private + def read_entry(key, options) if cache = local_cache cache.fetch_entry(key) { super } else @@ -114,25 +114,17 @@ module ActiveSupport end end - def write_entry(key, entry, options) # :nodoc: + def write_entry(key, entry, options) local_cache.write_entry(key, entry, options) if local_cache super end - def delete_entry(key, options) # :nodoc: + def delete_entry(key, options) local_cache.delete_entry(key, options) if local_cache super end - def set_cache_value(value, name, amount, options) # :nodoc: - ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) - `set_cache_value` is deprecated and will be removed from Rails 5.1. - Please use `write_cache_value` instead. - MESSAGE - write_cache_value name, value, options - end - - def write_cache_value(name, value, options) # :nodoc: + def write_cache_value(name, value, options) name = normalize_key(name, options) cache = local_cache cache.mute do @@ -144,8 +136,6 @@ module ActiveSupport end end - private - def local_cache_key @local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, "_").to_sym end diff --git a/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb b/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb index 174cb72b1e..4c3679e4bf 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb @@ -28,13 +28,13 @@ module ActiveSupport response[2] = ::Rack::BodyProxy.new(response[2]) do LocalCacheRegistry.set_cache_for(local_cache_key, nil) end + cleanup_on_body_close = true response rescue Rack::Utils::InvalidParameterError - LocalCacheRegistry.set_cache_for(local_cache_key, nil) [400, {}, []] - rescue Exception - LocalCacheRegistry.set_cache_for(local_cache_key, nil) - raise + ensure + LocalCacheRegistry.set_cache_for(local_cache_key, nil) unless + cleanup_on_body_close end end end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index af8ddb176f..ea71569ca8 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -4,7 +4,6 @@ require "active_support/core_ext/array/extract_options" 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/module/attribute_accessors" require "active_support/core_ext/string/filters" require "active_support/deprecation" require "thread" @@ -69,12 +68,6 @@ module ActiveSupport CALLBACK_FILTER_TYPES = [:before, :after, :around] - # If true, Active Record and Active Model callbacks returning +false+ will - # halt the entire callback chain and display a deprecation message. - # If false, callback chains will only be halted by calling +throw :abort+. - # Defaults to +true+. - mattr_accessor(:halt_and_display_warning_on_return_false, instance_writer: false) { true } - # Runs the callbacks for the given event. # # Calls the before and around callbacks in the order they were set, yields @@ -109,16 +102,22 @@ module ActiveSupport invoke_sequence = Proc.new do skipped = nil while true - current, next_sequence = next_sequence, next_sequence.nested + current = next_sequence current.invoke_before(env) if current.final? env.value = !env.halted && (!block_given? || yield) elsif current.skip?(env) (skipped ||= []) << current + next_sequence = next_sequence.nested next else - target, block, method, *arguments = current.expand_call_template(env, invoke_sequence) - target.send(method, *arguments, &block) + next_sequence = next_sequence.nested + begin + target, block, method, *arguments = current.expand_call_template(env, invoke_sequence) + target.send(method, *arguments, &block) + ensure + next_sequence = current + end end current.invoke_after(env) skipped.pop.invoke_after(env) while skipped && skipped.first @@ -280,9 +279,9 @@ module ActiveSupport class Callback #:nodoc:# def self.build(chain, filter, kind, options) if filter.is_a?(String) - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Passing string to define callback is deprecated and will be removed - in Rails 5.1 without replacement. + raise ArgumentError, <<-MSG.squish + Passing string to define a callback is not supported. See the `.set_callback` + documentation to see supported values. MSG end @@ -637,9 +636,8 @@ module ActiveSupport # set_callback :save, :before_method # # The callback can be specified as a symbol naming an instance method; as a - # proc, lambda, or block; as a string to be instance evaluated(deprecated); or as an - # object that responds to a certain method determined by the <tt>:scope</tt> - # argument to +define_callbacks+. + # proc, lambda, or block; or as an object that responds to a certain method + # determined by the <tt>:scope</tt> argument to +define_callbacks+. # # If a proc, lambda, or block is given, its body is evaluated in the context # of the current object. It can also optionally accept the current object as @@ -653,16 +651,24 @@ module ActiveSupport # # ===== Options # - # * <tt>:if</tt> - A symbol, a string or an array of symbols and strings, + # * <tt>:if</tt> - A symbol, a string (deprecated) or an array of symbols, # each naming an instance method or a proc; the callback will be called # only when they all return a true value. - # * <tt>:unless</tt> - A symbol, a string or an array of symbols and - # strings, each naming an instance method or a proc; the callback will - # be called only when they all return a false value. + # * <tt>:unless</tt> - A symbol, a string (deprecated) or an array of symbols, + # each naming an instance method or a proc; the callback will be called + # only when they all return a false value. # * <tt>:prepend</tt> - If +true+, the callback will be prepended to the # existing chain rather than appended. def set_callback(name, *filter_list, &block) type, filters, options = normalize_callback_params(filter_list, block) + + if options[:if].is_a?(String) || options[:unless].is_a?(String) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing string to :if and :unless conditional options is deprecated + and will be removed in Rails 5.2 without replacement. + MSG + end + self_chain = get_callbacks name mapped = filters.map do |filter| Callback.build(self_chain, filter, type, options) @@ -686,6 +692,14 @@ module ActiveSupport # already been set (unless the <tt>:raise</tt> option is set to <tt>false</tt>). def skip_callback(name, *filter_list, &block) type, filters, options = normalize_callback_params(filter_list, block) + + if options[:if].is_a?(String) || options[:unless].is_a?(String) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing string to :if and :unless conditional options is deprecated + and will be removed in Rails 5.2 without replacement. + MSG + end + options[:raise] = true unless options.key?(:raise) __update_callbacks(name) do |target, chain| @@ -835,30 +849,6 @@ module ActiveSupport def set_callbacks(name, callbacks) # :nodoc: self.__callbacks = __callbacks.merge(name.to_sym => callbacks) end - - def deprecated_false_terminator # :nodoc: - Proc.new do |target, result_lambda| - terminate = true - catch(:abort) do - result = result_lambda.call if result_lambda.is_a?(Proc) - if Callbacks.halt_and_display_warning_on_return_false && result == false - display_deprecation_warning_for_false_terminator - else - terminate = false - end - end - terminate - end - end - - private - - def display_deprecation_warning_for_false_terminator - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Returning `false` in Active Record and Active Model callbacks will not implicitly halt a callback chain in Rails 5.1. - To explicitly halt the callback chain, please use `throw :abort` instead. - MSG - end end end end diff --git a/activesupport/lib/active_support/concurrency/latch.rb b/activesupport/lib/active_support/concurrency/latch.rb deleted file mode 100644 index 53e09b685c..0000000000 --- a/activesupport/lib/active_support/concurrency/latch.rb +++ /dev/null @@ -1,25 +0,0 @@ -require "concurrent/atomic/count_down_latch" - -module ActiveSupport - module Concurrency - class Latch - def initialize(count = 1) - if count == 1 - ActiveSupport::Deprecation.warn("ActiveSupport::Concurrency::Latch is deprecated. Please use Concurrent::Event instead.") - else - ActiveSupport::Deprecation.warn("ActiveSupport::Concurrency::Latch is deprecated. Please use Concurrent::CountDownLatch instead.") - end - - @inner = Concurrent::CountDownLatch.new(count) - end - - def release - @inner.count_down - end - - def await - @inner.wait(nil) - end - end - end -end diff --git a/activesupport/lib/active_support/core_ext.rb b/activesupport/lib/active_support/core_ext.rb index 52706c3d7a..f397f658f3 100644 --- a/activesupport/lib/active_support/core_ext.rb +++ b/activesupport/lib/active_support/core_ext.rb @@ -1,4 +1,3 @@ -DEPRECATED_FILES = ["#{File.dirname(__FILE__)}/core_ext/struct.rb"] -(Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"] - DEPRECATED_FILES).each do |path| +(Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"]).each do |path| require path end diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index 37d833887a..fca33c9d69 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -31,7 +31,7 @@ class Array # # people = ["David", "Rafael", "Aaron", "Todd"] # people.without "Aaron", "Todd" - # => ["David", "Rafael"] + # # => ["David", "Rafael"] # # Note: This is an optimization of `Enumerable#without` that uses `Array#-` # instead of `Array#reject` for performance reasons. diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 1d4c83ae52..cac15e1100 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -40,12 +40,6 @@ class Array # ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ') # # => "one or two or at least three" # - # [].to_sentence(fallback_string: 'none') - # # => "none" - # - # ['one', 'two'].to_sentence(fallback_string: 'none') - # # => "one and two" - # # Using <tt>:locale</tt> option: # # # Given this locale dictionary: @@ -63,7 +57,7 @@ class Array # ['uno', 'dos', 'tres'].to_sentence(locale: :es) # # => "uno o dos o al menos tres" def to_sentence(options = {}) - options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale, :fallback_string) + options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale) default_connectors = { words_connector: ", ", @@ -78,7 +72,7 @@ class Array case length when 0 - "#{options[:fallback_string] || ''}" + "" when 1 "#{self[0]}" when 2 diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb index ea9d85f6e3..0d798e5c4e 100644 --- a/activesupport/lib/active_support/core_ext/array/grouping.rb +++ b/activesupport/lib/active_support/core_ext/array/grouping.rb @@ -89,7 +89,7 @@ class Array # [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]] # (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]] def split(value = nil) - arr = self.dup + arr = dup result = [] if block_given? while (idx = arr.index { |i| yield i }) diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb index 10a7c787f6..62397d9508 100644 --- a/activesupport/lib/active_support/core_ext/class/subclasses.rb +++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb @@ -22,6 +22,7 @@ class Class def descendants descendants = [] ObjectSpace.each_object(singleton_class) do |k| + next if k.singleton_class? descendants.unshift k unless k == self end descendants diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 2e64097933..d6f60cac04 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -133,7 +133,7 @@ class Date # Allow Date to be compared with Time by converting to DateTime and relying on the <=> from there. def compare_with_coercion(other) if other.is_a?(Time) - self.to_datetime <=> other + to_datetime <=> other else compare_without_coercion(other) end diff --git a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb index db95ae0db5..ab80392460 100644 --- a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb +++ b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb @@ -10,13 +10,5 @@ module DateAndTime # this behavior, but new apps will have an initializer that sets # this to true, because the new behavior is preferred. mattr_accessor(:preserve_timezone, instance_writer: false) { false } - - def to_time - if preserve_timezone - @_to_time_with_instance_offset ||= getlocal(utc_offset) - else - @_to_time_with_system_offset ||= getlocal - end - end end end diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index 70d5c9af8e..7a9eb8c266 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -47,13 +47,23 @@ class DateTime # DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => DateTime.new(1981, 8, 1, 22, 35, 0) # DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => DateTime.new(1981, 8, 29, 0, 0, 0) def change(options) + if new_nsec = options[:nsec] + raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec] + new_fraction = Rational(new_nsec, 1000000000) + else + new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) + new_fraction = Rational(new_usec, 1000000) + end + + raise ArgumentError, "argument out of range" if new_fraction >= 1 + ::DateTime.civil( options.fetch(:year, year), options.fetch(:month, month), options.fetch(:day, day), options.fetch(:hour, hour), options.fetch(:min, options[:hour] ? 0 : min), - options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec + sec_fraction), + options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec) + new_fraction, options.fetch(:offset, offset), options.fetch(:start, start) ) @@ -122,7 +132,7 @@ class DateTime # Returns a new DateTime representing the end of the day (23:59:59). def end_of_day - change(hour: 23, min: 59, sec: 59) + change(hour: 23, min: 59, sec: 59, usec: Rational(999999999, 1000)) end alias :at_end_of_day :end_of_day @@ -134,7 +144,7 @@ class DateTime # Returns a new DateTime representing the end of the hour (hh:59:59). def end_of_hour - change(min: 59, sec: 59) + change(min: 59, sec: 59, usec: Rational(999999999, 1000)) end alias :at_end_of_hour :end_of_hour @@ -146,7 +156,7 @@ class DateTime # Returns a new DateTime representing the end of the minute (hh:mm:59). def end_of_minute - change(sec: 59) + change(sec: 59, usec: Rational(999999999, 1000)) end alias :at_end_of_minute :end_of_minute diff --git a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb index 30bb7f4a60..eb8b8b2c65 100644 --- a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb +++ b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb @@ -1,5 +1,15 @@ require "active_support/core_ext/date_and_time/compatibility" class DateTime - prepend DateAndTime::Compatibility + include DateAndTime::Compatibility + + remove_possible_method :to_time + + # Either return an instance of `Time` with the same UTC offset + # as +self+ or an instance of `Time` representing the same time + # in the the local system timezone depending on the setting of + # on the setting of +ActiveSupport.to_time_preserves_timezone+. + def to_time + preserve_timezone ? getlocal(utc_offset) : getlocal + end end diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 8ebe758078..90d7d2947f 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -12,7 +12,7 @@ module Enumerable # # [5, 15, 10].sum # => 30 # ['foo', 'bar'].sum # => "foobar" - # [[1, 2], [3, 1, 5]].sum => [1, 2, 3, 1, 5] + # [[1, 2], [3, 1, 5]].sum # => [1, 2, 3, 1, 5] # # The default sum of an empty list is zero. You can override this default: # @@ -29,9 +29,9 @@ module Enumerable # Convert an enumerable to a hash. # # people.index_by(&:login) - # => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...} + # # => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...} # people.index_by { |person| "#{person.first_name} #{person.last_name}" } - # => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...} + # # => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...} def index_by if block_given? result = {} @@ -67,10 +67,10 @@ module Enumerable # Returns a copy of the enumerable without the specified elements. # # ["David", "Rafael", "Aaron", "Todd"].without "Aaron", "Todd" - # => ["David", "Rafael"] + # # => ["David", "Rafael"] # # {foo: 1, bar: 2, baz: 3}.without :bar - # => {foo: 1, baz: 3} + # # => {foo: 1, baz: 3} def without(*elements) reject { |element| elements.include?(element) } end @@ -78,10 +78,10 @@ module Enumerable # Convert an enumerable to an array based on the given key. # # [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name) - # => ["David", "Rafael", "Aaron"] + # # => ["David", "Rafael", "Aaron"] # # [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name) - # => [[1, "David"], [2, "Rafael"]] + # # => [[1, "David"], [2, "Rafael"]] def pluck(*keys) if keys.many? map { |element| keys.map { |key| element[key] } } @@ -115,9 +115,14 @@ end # and fall back to the compatible implementation, but that's much slower than # just calling the compat method in the first place. if Array.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false) - class Array - alias :orig_sum :sum + # Using Refinements here in order not to expose our internal method + using Module.new { + refine Array do + alias :orig_sum :sum + end + } + class Array def sum(init = nil, &block) #:nodoc: if init.is_a?(Numeric) || first.is_a?(Numeric) init ||= 0 diff --git a/activesupport/lib/active_support/core_ext/hash/compact.rb b/activesupport/lib/active_support/core_ext/hash/compact.rb index 5cae495bda..e357284be0 100644 --- a/activesupport/lib/active_support/core_ext/hash/compact.rb +++ b/activesupport/lib/active_support/core_ext/hash/compact.rb @@ -14,7 +14,7 @@ class Hash unless Hash.instance_methods(false).include?(:compact!) # Replaces current hash with non +nil+ values. - # Returns nil if no changes were made, otherwise returns the hash. + # Returns +nil+ if no changes were made, otherwise returns the hash. # # hash = { a: true, b: false, c: nil } # hash.compact! # => { a: true, b: false } diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb index 4a64872392..74baae3639 100644 --- a/activesupport/lib/active_support/core_ext/integer/time.rb +++ b/activesupport/lib/active_support/core_ext/integer/time.rb @@ -18,12 +18,12 @@ class Integer # # equivalent to Time.now.advance(months: 4, years: 5) # (4.months + 5.years).from_now def months - ActiveSupport::Duration.new(self * 30.days, [[:months, self]]) + ActiveSupport::Duration.months(self) end alias :month :months def years - ActiveSupport::Duration.new(self * 365.25.days.to_i, [[:years, self]]) + ActiveSupport::Duration.years(self) end alias :year :years end diff --git a/activesupport/lib/active_support/core_ext/kernel/debugger.rb b/activesupport/lib/active_support/core_ext/kernel/debugger.rb deleted file mode 100644 index dccfa3e9bb..0000000000 --- a/activesupport/lib/active_support/core_ext/kernel/debugger.rb +++ /dev/null @@ -1,3 +0,0 @@ -require "active_support/deprecation" - -ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.") diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index d0197af95f..c02618d5f3 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -1,7 +1,7 @@ module Kernel module_function - # Sets $VERBOSE to nil for the duration of the block and back to its original + # Sets $VERBOSE to +nil+ for the duration of the block and back to its original # value afterwards. # # silence_warnings do diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb index cd00d1b662..d273487010 100644 --- a/activesupport/lib/active_support/core_ext/load_error.rb +++ b/activesupport/lib/active_support/core_ext/load_error.rb @@ -1,6 +1,3 @@ -require "active_support/deprecation" -require "active_support/deprecation/proxy_wrappers" - class LoadError REGEXPS = [ /^no such file to load -- (.+)$/i, @@ -9,23 +6,9 @@ class LoadError /^cannot load such file -- (.+)$/i, ] - unless method_defined?(:path) - # Returns the path which was unable to be loaded. - def path - @path ||= begin - REGEXPS.find do |regex| - message =~ regex - end - $1 - end - end - end - # Returns true if the given path name (except perhaps for the ".rb" # extension) is the missing file which caused the exception to be raised. def is_missing?(location) location.sub(/\.rb$/, "".freeze) == path.sub(/\.rb$/, "".freeze) end end - -MissingSourceFile = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("MissingSourceFile", "LoadError") diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb index edfc8296fe..bba2b3be2e 100644 --- a/activesupport/lib/active_support/core_ext/marshal.rb +++ b/activesupport/lib/active_support/core_ext/marshal.rb @@ -1,7 +1,7 @@ module ActiveSupport module MarshalWithAutoloading # :nodoc: - def load(source) - super(source) + def load(source, proc = nil) + super(source, proc) rescue ArgumentError, NameError => exc if exc.message.match(%r|undefined class/module (.+?)(?:::)?\z|) # try loading the class/module diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb index 57feea69a5..2930255557 100644 --- a/activesupport/lib/active_support/core_ext/module.rb +++ b/activesupport/lib/active_support/core_ext/module.rb @@ -9,4 +9,3 @@ require "active_support/core_ext/module/concerning" require "active_support/core_ext/module/delegation" require "active_support/core_ext/module/deprecation" require "active_support/core_ext/module/remove_method" -require "active_support/core_ext/module/qualified_const" diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb index 4a04bdd446..c48bd3354a 100644 --- a/activesupport/lib/active_support/core_ext/module/aliasing.rb +++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb @@ -1,52 +1,4 @@ class Module - # NOTE: This method is deprecated. Please use <tt>Module#prepend</tt> that - # comes with Ruby 2.0 or newer instead. - # - # Encapsulates the common pattern of: - # - # alias_method :foo_without_feature, :foo - # alias_method :foo, :foo_with_feature - # - # With this, you simply do: - # - # alias_method_chain :foo, :feature - # - # And both aliases are set up for you. - # - # Query and bang methods (foo?, foo!) keep the same punctuation: - # - # alias_method_chain :foo?, :feature - # - # is equivalent to - # - # alias_method :foo_without_feature?, :foo? - # alias_method :foo?, :foo_with_feature? - # - # so you can safely chain foo, foo?, foo! and/or foo= with the same feature. - def alias_method_chain(target, feature) - ActiveSupport::Deprecation.warn("alias_method_chain is deprecated. Please, use Module#prepend instead. From module, you can access the original method using super.") - - # Strip out punctuation on predicates, bang or writer methods since - # e.g. target?_without_feature is not a valid method name. - aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ""), $1 - yield(aliased_target, punctuation) if block_given? - - with_method = "#{aliased_target}_with_#{feature}#{punctuation}" - without_method = "#{aliased_target}_without_#{feature}#{punctuation}" - - alias_method without_method, target - alias_method target, with_method - - case - when public_method_defined?(without_method) - public target - when protected_method_defined?(without_method) - protected target - when private_method_defined?(without_method) - private target - end - end - # Allows you to make aliases for attributes, which includes # getter, setter, and a predicate. # diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index 90989f4f3d..2c24081eb9 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -7,7 +7,8 @@ require "active_support/core_ext/regexp" class Module # Defines a class attribute and creates a class and instance reader methods. # The underlying class variable is set to +nil+, if it is not previously - # defined. + # defined. All class and instance methods created will be public, even if + # this method is called with a private or protected access modifier. # # module HairColors # mattr_reader :hair_colors @@ -76,7 +77,9 @@ class Module alias :cattr_reader :mattr_reader # Defines a class attribute and creates a class and instance writer methods to - # allow assignment to the attribute. + # allow assignment to the attribute. All class and instance methods created + # will be public, even if this method is called with a private or protected + # access modifier. # # module HairColors # mattr_writer :hair_colors @@ -142,6 +145,8 @@ class Module alias :cattr_writer :mattr_writer # Defines both class and instance accessors for class attributes. + # All class and instance methods created will be public, even if + # this method is called with a private or protected access modifier. # # module HairColors # mattr_accessor :hair_colors diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index f5f4ba61b7..cdf27f49ad 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -6,11 +6,12 @@ class Module # option is not used. class DelegationError < NoMethodError; end + RUBY_RESERVED_KEYWORDS = %w(alias and BEGIN begin break case class def defined? do + else elsif END end ensure false for if in module next nil not or redo rescue retry + return self super then true undef unless until when while yield) + DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block) DELEGATION_RESERVED_METHOD_NAMES = Set.new( - %w(_ arg args alias and BEGIN begin block break case class def defined? do - else elsif END end ensure false for if in module next nil not or redo - rescue retry return self super then true undef unless until when while - yield) + RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS ).freeze # Provides a +delegate+ class method to easily expose contained objects' @@ -19,7 +20,8 @@ class Module # ==== Options # * <tt>:to</tt> - Specifies the target object # * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix - # * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ from being raised + # * <tt>:allow_nil</tt> - if set to true, prevents a +Module::DelegationError+ + # from being raised # # The macro receives one or more method names (specified as symbols or # strings) and the name of the target object via the <tt>:to</tt> option @@ -111,18 +113,19 @@ class Module # invoice.customer_address # => 'Vimmersvej 13' # # If the target is +nil+ and does not respond to the delegated method a - # +NoMethodError+ is raised, as with any other value. Sometimes, however, it - # makes sense to be robust to that situation and that is the purpose of the - # <tt>:allow_nil</tt> option: If the target is not +nil+, or it is and - # responds to the method, everything works as usual. But if it is +nil+ and - # does not respond to the delegated method, +nil+ is returned. + # +Module::DelegationError+ is raised, as with any other value. Sometimes, + # however, it makes sense to be robust to that situation and that is the + # purpose of the <tt>:allow_nil</tt> option: If the target is not +nil+, or it + # is and responds to the method, everything works as usual. But if it is +nil+ + # and does not respond to the delegated method, +nil+ is returned. # # class User < ActiveRecord::Base # has_one :profile # delegate :age, to: :profile # end # - # User.new.age # raises NoMethodError: undefined method `age' + # User.new.age + # # => Module::DelegationError: User#age delegated to profile.age, but profile is nil # # But if not having a profile yet is fine and should not be an error # condition: @@ -257,7 +260,7 @@ class Module # end # # The target can be anything callable within the object. E.g. instance - # variables, methods, constants ant the likes. + # variables, methods, constants and the likes. def delegate_missing_to(target) target = target.to_s target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target) diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb index 4f854a718b..ca20a6d4c5 100644 --- a/activesupport/lib/active_support/core_ext/module/introspection.rb +++ b/activesupport/lib/active_support/core_ext/module/introspection.rb @@ -5,10 +5,12 @@ class Module # # M::N.parent_name # => "M" def parent_name - if defined? @parent_name + if defined?(@parent_name) @parent_name else - @parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil + parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil + @parent_name = parent_name unless frozen? + parent_name end end @@ -55,12 +57,4 @@ class Module parents << Object unless parents.include? Object parents end - - def local_constants #:nodoc: - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Module#local_constants is deprecated and will be removed in Rails 5.1. - Use Module#constants(false) instead. - MSG - constants(false) - end end diff --git a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb b/activesupport/lib/active_support/core_ext/module/method_transplanting.rb deleted file mode 100644 index dccfa3e9bb..0000000000 --- a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb +++ /dev/null @@ -1,3 +0,0 @@ -require "active_support/deprecation" - -ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.") diff --git a/activesupport/lib/active_support/core_ext/module/qualified_const.rb b/activesupport/lib/active_support/core_ext/module/qualified_const.rb deleted file mode 100644 index b9814e1dbe..0000000000 --- a/activesupport/lib/active_support/core_ext/module/qualified_const.rb +++ /dev/null @@ -1,70 +0,0 @@ -require "active_support/core_ext/string/inflections" - -#-- -# Allows code reuse in the methods below without polluting Module. -#++ - -module ActiveSupport - module QualifiedConstUtils - def self.raise_if_absolute(path) - raise NameError.new("wrong constant name #$&") if path =~ /\A::[^:]+/ - end - - def self.names(path) - path.split("::") - end - end -end - -## -# Extends the API for constants to be able to deal with qualified names. Arguments -# are assumed to be relative to the receiver. -# -#-- -# Qualified names are required to be relative because we are extending existing -# methods that expect constant names, ie, relative paths of length 1. For example, -# Object.const_get('::String') raises NameError and so does qualified_const_get. -#++ -class Module - def qualified_const_defined?(path, search_parents = true) - ActiveSupport::Deprecation.warn(<<-MESSAGE.squish) - Module#qualified_const_defined? is deprecated in favour of the builtin - Module#const_defined? and will be removed in Rails 5.1. - MESSAGE - - ActiveSupport::QualifiedConstUtils.raise_if_absolute(path) - - ActiveSupport::QualifiedConstUtils.names(path).inject(self) do |mod, name| - return unless mod.const_defined?(name, search_parents) - mod.const_get(name) - end - return true - end - - def qualified_const_get(path) - ActiveSupport::Deprecation.warn(<<-MESSAGE.squish) - Module#qualified_const_get is deprecated in favour of the builtin - Module#const_get and will be removed in Rails 5.1. - MESSAGE - - ActiveSupport::QualifiedConstUtils.raise_if_absolute(path) - - ActiveSupport::QualifiedConstUtils.names(path).inject(self) do |mod, name| - mod.const_get(name) - end - end - - def qualified_const_set(path, value) - ActiveSupport::Deprecation.warn(<<-MESSAGE.squish) - Module#qualified_const_set is deprecated in favour of the builtin - Module#const_set and will be removed in Rails 5.1. - MESSAGE - - ActiveSupport::QualifiedConstUtils.raise_if_absolute(path) - - const_name = path.demodulize - mod_name = path.deconstantize - mod = mod_name.empty? ? self : const_get(mod_name) - mod.const_set(const_name, value) - end -end diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb index cebfda8d31..4f6621693e 100644 --- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb +++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb @@ -99,38 +99,32 @@ module ActiveSupport::NumericWithFormat # 1234567.to_s(:human, precision: 1, # separator: ',', # significant: false) # => "1,2 Million" - def to_s(*args) - format, options = args - options ||= {} - + def to_s(format = nil, options = nil) case format + when nil + super() + when Integer, String + super(format) when :phone - return ActiveSupport::NumberHelper.number_to_phone(self, options) + return ActiveSupport::NumberHelper.number_to_phone(self, options || {}) when :currency - return ActiveSupport::NumberHelper.number_to_currency(self, options) + return ActiveSupport::NumberHelper.number_to_currency(self, options || {}) when :percentage - return ActiveSupport::NumberHelper.number_to_percentage(self, options) + return ActiveSupport::NumberHelper.number_to_percentage(self, options || {}) when :delimited - return ActiveSupport::NumberHelper.number_to_delimited(self, options) + return ActiveSupport::NumberHelper.number_to_delimited(self, options || {}) when :rounded - return ActiveSupport::NumberHelper.number_to_rounded(self, options) + return ActiveSupport::NumberHelper.number_to_rounded(self, options || {}) when :human - return ActiveSupport::NumberHelper.number_to_human(self, options) + return ActiveSupport::NumberHelper.number_to_human(self, options || {}) when :human_size - return ActiveSupport::NumberHelper.number_to_human_size(self, options) + return ActiveSupport::NumberHelper.number_to_human_size(self, options || {}) + when Symbol + super() else - if is_a?(Float) || format.is_a?(Symbol) - super() - else - super - end + super(format) end end - - def to_formatted_s(*args) - to_s(*args) - end - deprecate to_formatted_s: :to_s end # Ruby 2.4+ unifies Fixnum & Bignum into Integer. diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb index 809dfd4e07..2e6c70d418 100644 --- a/activesupport/lib/active_support/core_ext/numeric/time.rb +++ b/activesupport/lib/active_support/core_ext/numeric/time.rb @@ -19,7 +19,7 @@ class Numeric # # equivalent to Time.current.advance(months: 4, years: 5) # (4.months + 5.years).from_now def seconds - ActiveSupport::Duration.new(self, [[:seconds, self]]) + ActiveSupport::Duration.seconds(self) end alias :second :seconds @@ -27,7 +27,7 @@ class Numeric # # 2.minutes # => 2 minutes def minutes - ActiveSupport::Duration.new(self * 60, [[:minutes, self]]) + ActiveSupport::Duration.minutes(self) end alias :minute :minutes @@ -35,7 +35,7 @@ class Numeric # # 2.hours # => 2 hours def hours - ActiveSupport::Duration.new(self * 3600, [[:hours, self]]) + ActiveSupport::Duration.hours(self) end alias :hour :hours @@ -43,7 +43,7 @@ class Numeric # # 2.days # => 2 days def days - ActiveSupport::Duration.new(self * 24.hours, [[:days, self]]) + ActiveSupport::Duration.days(self) end alias :day :days @@ -51,7 +51,7 @@ class Numeric # # 2.weeks # => 2 weeks def weeks - ActiveSupport::Duration.new(self * 7.days, [[:weeks, self]]) + ActiveSupport::Duration.weeks(self) end alias :week :weeks @@ -59,7 +59,7 @@ class Numeric # # 2.fortnights # => 4 weeks def fortnights - ActiveSupport::Duration.new(self * 2.weeks, [[:weeks, self * 2]]) + ActiveSupport::Duration.weeks(self * 2) end alias :fortnight :fortnights diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index aa2282cb7e..b028df97ee 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -1,7 +1,7 @@ #-- -# Most objects are cloneable, but not all. For example you can't dup +nil+: +# Most objects are cloneable, but not all. For example you can't dup methods: # -# nil.dup # => TypeError: can't dup NilClass +# method(:puts).dup # => TypeError: allocator undefined for Method # # Classes may signal their instances are not duplicable removing +dup+/+clone+ # or raising exceptions from them. So, to dup an arbitrary object you normally @@ -19,7 +19,7 @@ class Object # Can you safely dup this object? # - # False for +nil+, +false+, +true+, symbol, number, method objects; + # False for method objects; # true otherwise. def duplicable? true @@ -27,52 +27,78 @@ class Object end class NilClass - # +nil+ is not duplicable: - # - # nil.duplicable? # => false - # nil.dup # => TypeError: can't dup NilClass - def duplicable? - false + 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 - # +false+ is not duplicable: - # - # false.duplicable? # => false - # false.dup # => TypeError: can't dup FalseClass - def duplicable? - false + 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 - # +true+ is not duplicable: - # - # true.duplicable? # => false - # true.dup # => TypeError: can't dup TrueClass - def duplicable? - false + 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 - # Symbols are not duplicable: - # - # :my_symbol.duplicable? # => false - # :my_symbol.dup # => TypeError: can't dup Symbol - def duplicable? - false + begin + :symbol.dup # Ruby 2.4.x. + "symbol_from_string".to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0. + 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 - # Numbers are not duplicable: - # - # 3.duplicable? # => false - # 3.dup # => TypeError: can't dup Integer - def duplicable? - false + 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 @@ -80,8 +106,8 @@ require "bigdecimal" class BigDecimal # BigDecimals are duplicable: # - # BigDecimal.new("1.2").duplicable? # => true - # BigDecimal.new("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)> + # BigDecimal.new("1.2").duplicable? # => true + # BigDecimal.new("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)> def duplicable? true end @@ -96,3 +122,33 @@ class Method false 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 + end +end diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb index cf39b1d312..3a44e08630 100644 --- a/activesupport/lib/active_support/core_ext/object/with_options.rb +++ b/activesupport/lib/active_support/core_ext/object/with_options.rb @@ -62,6 +62,17 @@ class Object # # Hence the inherited default for `if` key is ignored. # + # NOTE: You cannot call class methods implicitly inside of with_options. + # You can access these methods using the class name instead: + # + # class Phone < ActiveRecord::Base + # enum phone_number_type: [home: 0, office: 1, mobile: 2] + # + # with_options presence: true do + # validates :phone_number_type, inclusion: { in: Phone.phone_number_types.keys } + # end + # end + # def with_options(options, &block) option_merger = ActiveSupport::OptionMerger.new(self, options) block.arity.zero? ? option_merger.instance_eval(&block) : block.call(option_merger) diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb index b2e4fff79a..a57685bea1 100644 --- a/activesupport/lib/active_support/core_ext/securerandom.rb +++ b/activesupport/lib/active_support/core_ext/securerandom.rb @@ -6,7 +6,7 @@ module SecureRandom # # The argument _n_ specifies the length, of the random string to be generated. # - # If _n_ is not specified or is nil, 16 is assumed. It may be larger in the future. + # If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future. # # The result may contain alphanumeric characters except 0, O, I and l # diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb index caa48e34c5..6133826f37 100644 --- a/activesupport/lib/active_support/core_ext/string/access.rb +++ b/activesupport/lib/active_support/core_ext/string/access.rb @@ -3,7 +3,7 @@ class String # position. The first character of the string is at position 0, the next at # position 1, and so on. If a range is supplied, a substring containing # characters at offsets given by the range is returned. In both cases, if an - # offset is negative, it is counted from the end of the string. Returns nil + # offset is negative, it is counted from the end of the string. Returns +nil+ # if the initial offset falls outside the string. Returns an empty string if # the beginning of the range is greater than the end of the string. # @@ -17,7 +17,7 @@ class String # # If a Regexp is given, the matching portion of the string is returned. # If a String is given, that given string is returned if it occurs in - # the string. In both cases, nil is returned if there is no match. + # the string. In both cases, +nil+ is returned if there is no match. # # str = "hello" # str.at(/lo/) # => "lo" diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 7e12700c8c..7a2fc5c1b5 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -67,7 +67,7 @@ class String end # +safe_constantize+ tries to find a declared constant with the name specified - # in the string. It returns nil when the name is not in CamelCase + # in the string. It returns +nil+ when the name is not in CamelCase # or is not initialized. See ActiveSupport::Inflector.safe_constantize # # 'Module'.safe_constantize # => Module @@ -128,10 +128,10 @@ class String # Removes the module part from the constant expression in the string. # - # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections" - # 'Inflections'.demodulize # => "Inflections" - # '::Inflections'.demodulize # => "Inflections" - # ''.demodulize # => '' + # 'ActiveSupport::Inflector::Inflections'.demodulize # => "Inflections" + # 'Inflections'.demodulize # => "Inflections" + # '::Inflections'.demodulize # => "Inflections" + # ''.demodulize # => '' # # See also +deconstantize+. def demodulize @@ -178,11 +178,7 @@ class String # # <%= link_to(@person.name, person_path) %> # # => <a href="/person/1-Donald-E-Knuth">Donald E. Knuth</a> - def parameterize(sep = :unused, separator: "-", preserve_case: false) - unless sep == :unused - ActiveSupport::Deprecation.warn("Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '#{sep}'` instead.") - separator = sep - end + def parameterize(separator: "-", preserve_case: false) ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case) 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 227c34b032..94ce3f6a61 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -152,18 +152,16 @@ module ActiveSupport #:nodoc: def [](*args) if args.size < 2 super - else - if html_safe? - new_safe_buffer = super - - if new_safe_buffer - new_safe_buffer.instance_variable_set :@html_safe, true - end + elsif html_safe? + new_safe_buffer = super - new_safe_buffer - else - to_str[*args] + if new_safe_buffer + new_safe_buffer.instance_variable_set :@html_safe, true end + + new_safe_buffer + else + to_str[*args] end end diff --git a/activesupport/lib/active_support/core_ext/struct.rb b/activesupport/lib/active_support/core_ext/struct.rb deleted file mode 100644 index dccfa3e9bb..0000000000 --- a/activesupport/lib/active_support/core_ext/struct.rb +++ /dev/null @@ -1,3 +0,0 @@ -require "active_support/deprecation" - -ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.") diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index cbdcb86d6d..7b7aeef25a 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -53,6 +53,29 @@ class Time end alias_method :at_without_coercion, :at alias_method :at, :at_with_coercion + + # Creates a +Time+ instance from an RFC 3339 string. + # + # Time.rfc3339('1999-12-31T14:00:00-10:00') # => 2000-01-01 00:00:00 -1000 + # + # If the time or offset components are missing then an +ArgumentError+ will be raised. + # + # Time.rfc3339('1999-12-31') # => ArgumentError: invalid date + def rfc3339(str) + parts = Date._rfc3339(str) + + raise ArgumentError, "invalid date" if parts.empty? + + Time.new( + parts.fetch(:year), + parts.fetch(:mon), + parts.fetch(:mday), + parts.fetch(:hour), + parts.fetch(:min), + parts.fetch(:sec) + parts.fetch(:sec_fraction, 0), + parts.fetch(:offset) + ) + end end # Returns the number of seconds since 00:00:00. diff --git a/activesupport/lib/active_support/core_ext/time/compatibility.rb b/activesupport/lib/active_support/core_ext/time/compatibility.rb index ca4b9574d5..45e86b77ce 100644 --- a/activesupport/lib/active_support/core_ext/time/compatibility.rb +++ b/activesupport/lib/active_support/core_ext/time/compatibility.rb @@ -1,5 +1,14 @@ require "active_support/core_ext/date_and_time/compatibility" +require "active_support/core_ext/module/remove_method" class Time - prepend DateAndTime::Compatibility + include DateAndTime::Compatibility + + remove_possible_method :to_time + + # Either return +self+ or the time in the local system timezone depending + # on the setting of +ActiveSupport.to_time_preserves_timezone+. + def to_time + preserve_timezone ? self : getlocal + end end diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb index f2bbe55aa6..595bda6b4f 100644 --- a/activesupport/lib/active_support/core_ext/time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/time/conversions.rb @@ -64,4 +64,7 @@ class Time def formatted_offset(colon = true, alternate_utc_string = nil) utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon) end + + # Aliased to +xmlschema+ for compatibility with +DateTime+ + alias_method :rfc3339, :xmlschema end diff --git a/activesupport/lib/active_support/core_ext/time/marshal.rb b/activesupport/lib/active_support/core_ext/time/marshal.rb deleted file mode 100644 index d095d76ebb..0000000000 --- a/activesupport/lib/active_support/core_ext/time/marshal.rb +++ /dev/null @@ -1,3 +0,0 @@ -require "active_support/deprecation" - -ActiveSupport::Deprecation.warn("This is deprecated and will be removed in Rails 5.1 with no replacement.") diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index cad0d6d2e9..e125b657f2 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -6,7 +6,6 @@ require "active_support/core_ext/module/aliasing" require "active_support/core_ext/module/attribute_accessors" require "active_support/core_ext/module/introspection" require "active_support/core_ext/module/anonymous" -require "active_support/core_ext/module/qualified_const" require "active_support/core_ext/object/blank" require "active_support/core_ext/kernel/reporting" require "active_support/core_ext/load_error" @@ -243,7 +242,7 @@ module ActiveSupport #:nodoc: # resolution deterministic for constants with the same relative name in # different namespaces whose evaluation would depend on load order # otherwise. - def require_dependency(file_name, message = "No such file to load -- %s") + def require_dependency(file_name, message = "No such file to load -- %s.rb") file_name = file_name.to_path if file_name.respond_to?(:to_path) unless file_name.is_a?(String) raise ArgumentError, "the file name must either be a String or implement #to_path -- you passed #{file_name.inspect}" diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index 191e582de8..72c74e966a 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -15,6 +15,7 @@ module ActiveSupport require "active_support/deprecation/instance_delegator" require "active_support/deprecation/behaviors" require "active_support/deprecation/reporting" + require "active_support/deprecation/constant_accessor" require "active_support/deprecation/method_wrappers" require "active_support/deprecation/proxy_wrappers" require "active_support/core_ext/module/deprecation" @@ -32,7 +33,7 @@ module ActiveSupport # and the second is a library name # # ActiveSupport::Deprecation.new('2.0', 'MyLibrary') - def initialize(deprecation_horizon = "5.2", gem_name = "Rails") + def initialize(deprecation_horizon = "5.3", gem_name = "Rails") self.gem_name = gem_name self.deprecation_horizon = deprecation_horizon # By default, warnings are not silenced and debugging is off. diff --git a/activesupport/lib/active_support/deprecation/constant_accessor.rb b/activesupport/lib/active_support/deprecation/constant_accessor.rb new file mode 100644 index 0000000000..2b19de365f --- /dev/null +++ b/activesupport/lib/active_support/deprecation/constant_accessor.rb @@ -0,0 +1,50 @@ +require "active_support/inflector/methods" + +module ActiveSupport + class Deprecation + # DeprecatedConstantAccessor transforms a constant into a deprecated one by + # hooking +const_missing+. + # + # It takes the names of an old (deprecated) constant and of a new constant + # (both in string form) and optionally a deprecator. The deprecator defaults + # to +ActiveSupport::Deprecator+ if none is specified. + # + # The deprecated constant now returns the same object as the new one rather + # than a proxy object, so it can be used transparently in +rescue+ blocks + # etc. + # + # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto) + # + # (In a later update, the original implementation of `PLANETS` has been removed.) + # + # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune) + # include ActiveSupport::Deprecation::DeprecatedConstantAccessor + # deprecate_constant 'PLANETS', 'PLANETS_POST_2006' + # + # PLANETS.map { |planet| planet.capitalize } + # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead. + # (Backtrace information…) + # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] + module DeprecatedConstantAccessor + def self.included(base) + extension = Module.new do + def const_missing(missing_const_name) + if class_variable_defined?(:@@_deprecated_constants) + if (replacement = class_variable_get(:@@_deprecated_constants)[missing_const_name.to_s]) + replacement[:deprecator].warn(replacement[:message] || "#{name}::#{missing_const_name} is deprecated! Use #{replacement[:new]} instead.", caller_locations) + return ActiveSupport::Inflector.constantize(replacement[:new].to_s) + end + end + super + end + + def deprecate_constant(const_name, new_constant, message: nil, deprecator: ActiveSupport::Deprecation.instance) + class_variable_set(:@@_deprecated_constants, {}) unless class_variable_defined?(:@@_deprecated_constants) + class_variable_get(:@@_deprecated_constants)[const_name.to_s] = { new: new_constant, message: message, deprecator: deprecator } + end + end + base.singleton_class.prepend extension + end + end + end +end diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb index 7655fa4f99..930d71e8d2 100644 --- a/activesupport/lib/active_support/deprecation/method_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb @@ -18,7 +18,7 @@ module ActiveSupport # # Using the default deprecator: # ActiveSupport::Deprecation.deprecate_methods(Fred, :aaa, bbb: :zzz, ccc: 'use Bar#ccc instead') - # # => [:aaa, :bbb, :ccc] + # # => Fred # # Fred.aaa # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.1. (called from irb_binding at (irb):10) diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index 1c6618b19a..ce39e9a232 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -122,10 +122,11 @@ module ActiveSupport # (Backtrace information…) # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] class DeprecatedConstantProxy < DeprecationProxy - def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance) + def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance, message: "#{old_const} is deprecated! Use #{new_const} instead.") @old_const = old_const @new_const = new_const @deprecator = deprecator + @message = message end # Returns the class of the new constant. @@ -143,7 +144,7 @@ module ActiveSupport end def warn(callstack, called, args) - @deprecator.warn("#{@old_const} is deprecated! Use #{@new_const} instead.", callstack) + @deprecator.warn(@message, callstack) end end end diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb index b8d200ba94..58c5c50e30 100644 --- a/activesupport/lib/active_support/deprecation/reporting.rb +++ b/activesupport/lib/active_support/deprecation/reporting.rb @@ -48,11 +48,11 @@ module ActiveSupport private # Outputs a deprecation warning message # - # ActiveSupport::Deprecation.deprecated_method_warning(:method_name) + # deprecated_method_warning(:method_name) # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon}" - # ActiveSupport::Deprecation.deprecated_method_warning(:method_name, :another_method) + # deprecated_method_warning(:method_name, :another_method) # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (use another_method instead)" - # ActiveSupport::Deprecation.deprecated_method_warning(:method_name, "Optional message") + # deprecated_method_warning(:method_name, "Optional message") # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (Optional message)" def deprecated_method_warning(method_name, message = nil) warning = "#{method_name} is deprecated and will be removed from #{gem_name} #{deprecation_horizon}" diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 82322291d0..99080e34a1 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -1,5 +1,8 @@ require "active_support/core_ext/array/conversions" +require "active_support/core_ext/module/delegation" require "active_support/core_ext/object/acts_like" +require "active_support/core_ext/string/filters" +require "active_support/deprecation" module ActiveSupport # Provides accurate date and time measurements using Date#advance and @@ -7,24 +10,177 @@ module ActiveSupport # # 1.month.ago # equivalent to Time.now.advance(months: -1) class Duration - EPOCH = ::Time.utc(2000) + class Scalar < Numeric #:nodoc: + attr_reader :value + delegate :to_i, :to_f, :to_s, to: :value + + def initialize(value) + @value = value + end + + def coerce(other) + [Scalar.new(other), self] + end + + def -@ + Scalar.new(-value) + end + + def <=>(other) + if Scalar === other || Duration === other + value <=> other.value + elsif Numeric === other + value <=> other + else + nil + end + end + + def +(other) + calculate(:+, other) + end + + def -(other) + calculate(:-, other) + end + + def *(other) + calculate(:*, other) + end + + def /(other) + calculate(:/, other) + end + + private + def calculate(op, other) + if Scalar === other + Scalar.new(value.public_send(op, other.value)) + elsif Duration === other + Duration.seconds(value).public_send(op, other) + elsif Numeric === other + Scalar.new(value.public_send(op, other)) + else + raise_type_error(other) + end + end + + def raise_type_error(other) + raise TypeError, "no implicit conversion of #{other.class} into #{self.class}" + end + end + + SECONDS_PER_MINUTE = 60 + SECONDS_PER_HOUR = 3600 + SECONDS_PER_DAY = 86400 + SECONDS_PER_WEEK = 604800 + SECONDS_PER_MONTH = 2629746 # 1/12 of a gregorian year + SECONDS_PER_YEAR = 31556952 # length of a gregorian year (365.2425 days) + + PARTS_IN_SECONDS = { + seconds: 1, + minutes: SECONDS_PER_MINUTE, + hours: SECONDS_PER_HOUR, + days: SECONDS_PER_DAY, + weeks: SECONDS_PER_WEEK, + months: SECONDS_PER_MONTH, + years: SECONDS_PER_YEAR + }.freeze attr_accessor :value, :parts autoload :ISO8601Parser, "active_support/duration/iso8601_parser" autoload :ISO8601Serializer, "active_support/duration/iso8601_serializer" + class << self + # Creates a new Duration from string formatted according to ISO 8601 Duration. + # + # See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information. + # This method allows negative parts to be present in pattern. + # If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+. + def parse(iso8601duration) + parts = ISO8601Parser.new(iso8601duration).parse! + new(calculate_total_seconds(parts), parts) + end + + def ===(other) #:nodoc: + other.is_a?(Duration) + rescue ::NoMethodError + false + end + + def seconds(value) #:nodoc: + new(value, [[:seconds, value]]) + end + + def minutes(value) #:nodoc: + new(value * SECONDS_PER_MINUTE, [[:minutes, value]]) + end + + def hours(value) #:nodoc: + new(value * SECONDS_PER_HOUR, [[:hours, value]]) + end + + def days(value) #:nodoc: + new(value * SECONDS_PER_DAY, [[:days, value]]) + end + + def weeks(value) #:nodoc: + new(value * SECONDS_PER_WEEK, [[:weeks, value]]) + end + + def months(value) #:nodoc: + new(value * SECONDS_PER_MONTH, [[:months, value]]) + end + + def years(value) #:nodoc: + new(value * SECONDS_PER_YEAR, [[:years, value]]) + end + + private + + def calculate_total_seconds(parts) + parts.inject(0) do |total, (part, value)| + total + value * PARTS_IN_SECONDS[part] + end + end + end + def initialize(value, parts) #:nodoc: - @value, @parts = value, parts + @value, @parts = value, parts.to_h + @parts.default = 0 + end + + def coerce(other) #:nodoc: + if Scalar === other + [other, self] + else + [Scalar.new(other), self] + end + end + + # Compares one Duration with another or a Numeric to this Duration. + # Numeric values are treated as seconds. + def <=>(other) + if Duration === other + value <=> other.value + elsif Numeric === other + value <=> other + end end # Adds another Duration or a Numeric to this Duration. Numeric values # are treated as seconds. def +(other) if Duration === other - Duration.new(value + other.value, @parts + other.parts) + parts = @parts.dup + other.parts.each do |(key, value)| + parts[key] += value + end + Duration.new(value + other.value, parts) else - Duration.new(value + other, @parts + [[:seconds, other]]) + seconds = @parts[:seconds] + other + Duration.new(value + other, @parts.merge(seconds: seconds)) end end @@ -34,6 +190,28 @@ module ActiveSupport self + (-other) end + # Multiplies this Duration by a Numeric and returns a new Duration. + def *(other) + if Scalar === other || Duration === other + Duration.new(value * other.value, parts.map { |type, number| [type, number * other.value] }) + elsif Numeric === other + Duration.new(value * other, parts.map { |type, number| [type, number * other] }) + else + raise_type_error(other) + end + end + + # Divides this Duration by a Numeric and returns a new Duration. + def /(other) + if Scalar === other || Duration === other + Duration.new(value / other.value, parts.map { |type, number| [type, number / other.value] }) + elsif Numeric === other + Duration.new(value / other, parts.map { |type, number| [type, number / other] }) + else + raise_type_error(other) + end + end + def -@ #:nodoc: Duration.new(-value, parts.map { |type, number| [type, -number] }) end @@ -72,14 +250,14 @@ module ActiveSupport # 1.day.to_i # => 86400 # # Note that this conversion makes some assumptions about the - # duration of some periods, e.g. months are always 30 days - # and years are 365.25 days: + # duration of some periods, e.g. months are always 1/12 of year + # and years are 365.2425 days: # - # # equivalent to 30.days.to_i - # 1.month.to_i # => 2592000 + # # equivalent to (1.year / 12).to_i + # 1.month.to_i # => 2629746 # - # # equivalent to 365.25.days.to_i - # 1.year.to_i # => 31557600 + # # equivalent to 365.2425.days.to_i + # 1.year.to_i # => 31556952 # # In such cases, Ruby's core # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and @@ -99,18 +277,13 @@ module ActiveSupport @value.hash end - def self.===(other) #:nodoc: - other.is_a?(Duration) - rescue ::NoMethodError - false - end - # Calculates a new Time or Date that is as far in the future # as this Duration represents. def since(time = ::Time.current) sum(1, time) end alias :from_now :since + alias :after :since # Calculates a new Time or Date that is as far in the past # as this Duration represents. @@ -118,6 +291,7 @@ module ActiveSupport sum(-1, time) end alias :until :ago + alias :before :ago def inspect #:nodoc: parts. @@ -135,27 +309,15 @@ module ActiveSupport @value.respond_to?(method, include_private) end - # Creates a new Duration from string formatted according to ISO 8601 Duration. - # - # See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information. - # This method allows negative parts to be present in pattern. - # If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+. - def self.parse(iso8601duration) - parts = ISO8601Parser.new(iso8601duration).parse! - new(EPOCH.advance(parts) - EPOCH, parts) - end - # Build ISO 8601 Duration string for this duration. # The +precision+ parameter can be used to limit seconds' precision of duration. def iso8601(precision: nil) ISO8601Serializer.new(self, precision: precision).serialize end - delegate :<=>, to: :value - - protected + private - def sum(sign, time = ::Time.current) #:nodoc: + def sum(sign, time = ::Time.current) parts.inject(time) do |t, (type, number)| if t.acts_like?(:time) || t.acts_like?(:date) if type == :seconds @@ -173,10 +335,12 @@ module ActiveSupport end end - private - - def method_missing(method, *args, &block) #:nodoc: + def method_missing(method, *args, &block) value.send(method, *args, &block) end + + def raise_type_error(other) + raise TypeError, "no implicit conversion of #{other.class} into #{self.class}" + end end end diff --git a/activesupport/lib/active_support/duration/iso8601_serializer.rb b/activesupport/lib/active_support/duration/iso8601_serializer.rb index 51d53e2f8d..e5d458b3ab 100644 --- a/activesupport/lib/active_support/duration/iso8601_serializer.rb +++ b/activesupport/lib/active_support/duration/iso8601_serializer.rb @@ -4,7 +4,7 @@ require "active_support/core_ext/hash/transform_values" module ActiveSupport class Duration # Serializes duration to string according to ISO 8601 Duration format. - class ISO8601Serializer + class ISO8601Serializer # :nodoc: def initialize(duration, precision: nil) @duration = duration @precision = precision diff --git a/activesupport/lib/active_support/evented_file_update_checker.rb b/activesupport/lib/active_support/evented_file_update_checker.rb index e5fbc133b0..8d9d19c2d9 100644 --- a/activesupport/lib/active_support/evented_file_update_checker.rb +++ b/activesupport/lib/active_support/evented_file_update_checker.rb @@ -17,7 +17,7 @@ module ActiveSupport # # Example: # - # checker = EventedFileUpdateChecker.new(["/tmp/foo"], -> { puts "changed" }) + # checker = ActiveSupport::EventedFileUpdateChecker.new(["/tmp/foo"]) { puts "changed" } # checker.updated? # # => false # checker.execute_if_updated @@ -32,6 +32,10 @@ module ActiveSupport # class EventedFileUpdateChecker #:nodoc: all def initialize(files, dirs = {}, &block) + unless block + raise ArgumentError, "A block is required to initialize an EventedFileUpdateChecker" + end + @ph = PathHelper.new @files = files.map { |f| @ph.xpath(f) }.to_set diff --git a/activesupport/lib/active_support/execution_wrapper.rb b/activesupport/lib/active_support/execution_wrapper.rb index 3384d12d5b..ca88e7876b 100644 --- a/activesupport/lib/active_support/execution_wrapper.rb +++ b/activesupport/lib/active_support/execution_wrapper.rb @@ -19,14 +19,14 @@ module ActiveSupport set_callback(:complete, *args, &block) end - class RunHook < Struct.new(:hook) # :nodoc: + RunHook = Struct.new(:hook) do # :nodoc: def before(target) hook_state = target.send(:hook_state) hook_state[hook] = hook.run end end - class CompleteHook < Struct.new(:hook) # :nodoc: + CompleteHook = Struct.new(:hook) do # :nodoc: def before(target) hook_state = target.send(:hook_state) if hook_state.key?(hook) diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index 2dbbfadac1..2b5e3c1350 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -38,6 +38,10 @@ module ActiveSupport # changes. The array of files and list of directories cannot be changed # after FileUpdateChecker has been initialized. def initialize(files, dirs = {}, &block) + unless block + raise ArgumentError, "A block is required to initialize a FileUpdateChecker" + end + @files = files.freeze @glob = compile_glob(dirs) @block = block diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb index 74f2d8dd4b..371a39a5e6 100644 --- a/activesupport/lib/active_support/gem_version.rb +++ b/activesupport/lib/active_support/gem_version.rb @@ -6,7 +6,7 @@ module ActiveSupport module VERSION MAJOR = 5 - MINOR = 1 + MINOR = 2 TINY = 0 PRE = "alpha" diff --git a/activesupport/lib/active_support/gzip.rb b/activesupport/lib/active_support/gzip.rb index 84eef6a623..95a86889ec 100644 --- a/activesupport/lib/active_support/gzip.rb +++ b/activesupport/lib/active_support/gzip.rb @@ -21,7 +21,7 @@ module ActiveSupport # Decompresses a gzipped string. def self.decompress(source) - Zlib::GzipReader.new(StringIO.new(source)).read + Zlib::GzipReader.wrap(StringIO.new(source), &:read) end # Compresses a string using gzip. diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 1bed489547..1927cddf34 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -84,15 +84,6 @@ module ActiveSupport end end - def self.new_from_hash_copying_default(hash) - ActiveSupport::Deprecation.warn(<<-MSG.squish) - `ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default` - has been deprecated, and will be removed in Rails 5.1. The behavior of - this method is now identical to the behavior of `.new`. - MSG - new(hash) - end - def self.[](*args) new.merge!(Hash[*args]) end @@ -278,6 +269,10 @@ module ActiveSupport dup.tap { |hash| hash.transform_values!(*args, &block) } end + def compact + dup.tap(&:compact!) + end + # Convert to a regular hash with string keys. def to_hash _new_hash = Hash.new @@ -289,12 +284,12 @@ module ActiveSupport _new_hash end - protected - def convert_key(key) + private + def convert_key(key) # :doc: key.kind_of?(Symbol) ? key.to_s : key end - def convert_value(value, options = {}) + def convert_value(value, options = {}) # :doc: if value.is_a? Hash if options[:for] == :to_hash value.to_hash @@ -311,7 +306,7 @@ module ActiveSupport end end - def set_defaults(target) + def set_defaults(target) # :doc: if default_proc target.default_proc = default_proc.dup else @@ -321,4 +316,6 @@ module ActiveSupport end end +# :stopdoc: + HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb index 08c9ef8f02..b749913ee9 100644 --- a/activesupport/lib/active_support/i18n_railtie.rb +++ b/activesupport/lib/active_support/i18n_railtie.rb @@ -2,6 +2,8 @@ require "active_support" require "active_support/file_update_checker" require "active_support/core_ext/array/wrap" +# :enddoc: + module I18n class Railtie < Rails::Railtie config.i18n = ActiveSupport::OrderedOptions.new @@ -21,8 +23,6 @@ module I18n I18n::Railtie.initialize_i18n(app) end - protected - @i18n_inited = false # Setup i18n configuration. diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index aa68f9ec9e..c47a2e34e1 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -1,5 +1,6 @@ require "concurrent/map" require "active_support/core_ext/array/prepend_and_append" +require "active_support/core_ext/regexp" require "active_support/i18n" module ActiveSupport @@ -43,13 +44,14 @@ module ActiveSupport end def add(words) - concat(words.flatten.map(&:downcase)) - @regex_array += map { |word| to_regex(word) } + words = words.flatten.map(&:downcase) + concat(words) + @regex_array += words.map { |word| to_regex(word) } self end def uncountable?(str) - @regex_array.any? { |regex| regex === str } + @regex_array.any? { |regex| regex.match? str } end private diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index ef3df1240d..51c221ac0e 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -161,7 +161,7 @@ module ActiveSupport # titleize('TheManWithoutAPast') # => "The Man Without A Past" # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark" def titleize(word) - humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { |match| match.capitalize } + humanize(underscore(word)).gsub(/\b(?<!\w['’`])[a-z]/) { |match| match.capitalize } end # Creates the name of a table like Rails does for models to table names. @@ -198,10 +198,10 @@ module ActiveSupport # Removes the module part from the expression in the string. # - # demodulize('ActiveRecord::CoreExtensions::String::Inflections') # => "Inflections" - # demodulize('Inflections') # => "Inflections" - # demodulize('::Inflections') # => "Inflections" - # demodulize('') # => "" + # demodulize('ActiveSupport::Inflector::Inflections') # => "Inflections" + # demodulize('Inflections') # => "Inflections" + # demodulize('::Inflections') # => "Inflections" + # demodulize('') # => "" # # See also #deconstantize. def demodulize(path) @@ -274,7 +274,7 @@ module ActiveSupport # 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 do |const, ancestor| + constant = constant.ancestors.inject(constant) do |const, ancestor| break const if ancestor == Object break ancestor if ancestor.const_defined?(name, false) const @@ -361,7 +361,7 @@ module ActiveSupport # # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?" # const_regexp("::") # => "::" - def const_regexp(camel_cased_word) #:nodoc: + def const_regexp(camel_cased_word) parts = camel_cased_word.split("::".freeze) return Regexp.escape(camel_cased_word) if parts.blank? diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index 85fa83c803..de6dd2720b 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -57,6 +57,8 @@ module ActiveSupport # transliterate('Jürgen') # # => "Juergen" def transliterate(string, replacement = "?".freeze) + raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String) + I18n.transliterate(ActiveSupport::Multibyte::Unicode.normalize( ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c), replacement: replacement) @@ -78,11 +80,7 @@ module ActiveSupport # parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth" # parameterize("^trés|Jolie-- ", preserve_case: true) # => "tres-Jolie" # - def parameterize(string, sep = :unused, separator: "-", preserve_case: false) - unless sep == :unused - ActiveSupport::Deprecation.warn("Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '#{sep}'` instead.") - separator = sep - end + def parameterize(string, separator: "-", preserve_case: false) # Replace accented chars with their ASCII equivalents. parameterized_string = transliterate(string) diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index cee731417f..defaf3f395 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -68,7 +68,8 @@ module ActiveSupport :ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, :EscapedString # Convert an object into a "JSON-ready" representation composed of - # primitives like Hash, Array, String, Numeric, and true/false/nil. + # primitives like Hash, Array, String, Numeric, + # and +true+/+false+/+nil+. # Recursively calls #as_json to the object to recursively build a # fully JSON-ready object. # @@ -84,7 +85,7 @@ module ActiveSupport when String EscapedString.new(value) when Numeric, NilClass, TrueClass, FalseClass - value + value.as_json when Hash Hash[value.map { |k, v| [jsonify(k), jsonify(v)] }] when Array diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index cb6ea095b8..e2c4f33565 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -85,7 +85,7 @@ module ActiveSupport logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}" end - protected + private %w(info debug warn error fatal unknown).each do |level| class_eval <<-METHOD, __FILE__, __LINE__ + 1 @@ -99,7 +99,7 @@ module ActiveSupport # option is set to +true+, it also adds bold to the string. This is based # on the Highline implementation and will automatically append CLEAR to the # end of the returned String. - def color(text, color, bold = false) + def color(text, color, bold = false) # :doc: return text unless colorize_logging color = self.class.const_get(color.upcase) if color.is_a?(Symbol) bold = bold ? BOLD : "" diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb index 3ba6461b57..ea09d7d2df 100644 --- a/activesupport/lib/active_support/logger.rb +++ b/activesupport/lib/active_support/logger.rb @@ -59,14 +59,14 @@ module ActiveSupport define_method(:silence) do |level = Logger::ERROR, &block| if logger.respond_to?(:silence) logger.silence(level) do - if respond_to?(:silence) + if defined?(super) super(level, &block) else block.call(self) end end else - if respond_to?(:silence) + if defined?(super) super(level, &block) else block.call(self) diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 7b33dc3481..24053b4fe5 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -14,10 +14,10 @@ module ActiveSupport # where you don't want users to be able to determine the value of the payload. # # salt = SecureRandom.random_bytes(64) - # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt) # => "\x89\xE0\x156\xAC..." - # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...> - # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..." - # crypt.decrypt_and_verify(encrypted_data) # => "my secret data" + # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt, 32) # => "\x89\xE0\x156\xAC..." + # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...> + # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..." + # crypt.decrypt_and_verify(encrypted_data) # => "my secret data" class MessageEncryptor DEFAULT_CIPHER = "aes-256-cbc" @@ -50,6 +50,11 @@ module ActiveSupport # key by using <tt>ActiveSupport::KeyGenerator</tt> or a similar key # derivation function. # + # First additional parameter is used as the signature key for +MessageVerifier+. + # This allows you to specify keys to encrypt and sign data. + # + # ActiveSupport::MessageEncryptor.new('secret', 'signature_secret') + # # Options: # * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by # <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'. @@ -61,7 +66,7 @@ module ActiveSupport sign_secret = signature_key_or_options.first @secret = secret @sign_secret = sign_secret - @cipher = options[:cipher] || "aes-256-cbc" + @cipher = options[:cipher] || DEFAULT_CIPHER @digest = options[:digest] || "SHA1" unless aead_mode? @verifier = resolve_verifier @serializer = options[:serializer] || Marshal diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 938e4ebb72..8c58466556 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -16,7 +16,8 @@ module ActiveSupport #:nodoc: # through the +mb_chars+ method. Methods which would normally return a # String object now return a Chars object so methods can be chained. # - # 'The Perfect String '.mb_chars.downcase.strip.normalize # => "the perfect string" + # 'The Perfect String '.mb_chars.downcase.strip.normalize + # # => #<ActiveSupport::Multibyte::Chars:0x007fdc434ccc10 @wrapped_string="the perfect string"> # # Chars objects are perfectly interchangeable with String objects as long as # no explicit class checks are made. If certain methods do explicitly check @@ -87,7 +88,7 @@ module ActiveSupport #:nodoc: end # Works like <tt>String#slice!</tt>, but returns an instance of - # Chars, or nil if the string was not modified. The string will not be + # Chars, or +nil+ if the string was not modified. The string will not be # modified if the range given is out of bounds # # string = 'Welcome' @@ -134,7 +135,7 @@ module ActiveSupport #:nodoc: # Converts characters in the string to the opposite case. # - # 'El Cañón".mb_chars.swapcase.to_s # => "eL cAÑÓN" + # 'El Cañón'.mb_chars.swapcase.to_s # => "eL cAÑÓN" def swapcase chars Unicode.swapcase(@wrapped_string) end @@ -148,8 +149,8 @@ module ActiveSupport #:nodoc: # Capitalizes the first letter of every word, when possible. # - # "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró" - # "日本語".mb_chars.titleize # => "日本語" + # "ÉL QUE SE ENTERÓ".mb_chars.titleize.to_s # => "Él Que Se Enteró" + # "日本語".mb_chars.titleize.to_s # => "日本語" def titleize chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1) }) end @@ -210,9 +211,9 @@ module ActiveSupport #:nodoc: end end - protected + private - def translate_offset(byte_offset) #:nodoc: + def translate_offset(byte_offset) return nil if byte_offset.nil? return 0 if @wrapped_string == "" @@ -224,7 +225,7 @@ module ActiveSupport #:nodoc: end end - def chars(string) #:nodoc: + def chars(string) self.class.new(string) end end diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index 7842264b39..0912912aba 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -9,7 +9,7 @@ module ActiveSupport NORMALIZATION_FORMS = [:c, :kc, :d, :kd] # The Unicode version that is supported by the implementation - UNICODE_VERSION = "8.0.0" + UNICODE_VERSION = "9.0.0" # The default normalization used for operations that require # normalization. It can be set to any of the normalizations @@ -57,9 +57,12 @@ module ActiveSupport previous = codepoints[pos - 1] current = codepoints[pos] + # See http://unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules should_break = + if pos == eoc + true # GB3. CR X LF - if previous == database.boundary[:cr] && current == database.boundary[:lf] + elsif previous == database.boundary[:cr] && current == database.boundary[:lf] false # GB4. (Control|CR|LF) ÷ elsif previous && in_char_class?(previous, [:control, :cr, :lf]) @@ -76,11 +79,8 @@ module ActiveSupport # GB8. (LVT|T) X (T) elsif in_char_class?(previous, [:lvt, :t]) && database.boundary[:t] === current false - # GB8a. Regional_Indicator X Regional_Indicator - elsif database.boundary[:regional_indicator] === previous && database.boundary[:regional_indicator] === current - false - # GB9. X Extend - elsif database.boundary[:extend] === current + # GB9. X (Extend | ZWJ) + elsif in_char_class?(current, [:extend, :zwj]) false # GB9a. X SpacingMark elsif database.boundary[:spacingmark] === current @@ -88,7 +88,17 @@ module ActiveSupport # GB9b. Prepend X elsif database.boundary[:prepend] === previous false - # GB10. Any ÷ Any + # GB10. (E_Base | EBG) Extend* X E_Modifier + elsif (marker...pos).any? { |i| in_char_class?(codepoints[i], [:e_base, :e_base_gaz]) && codepoints[i + 1...pos].all? { |c| database.boundary[:extend] === c } } && database.boundary[:e_modifier] === current + false + # GB11. ZWJ X (Glue_After_Zwj | EBG) + elsif database.boundary[:zwj] === previous && in_char_class?(current, [:glue_after_zwj, :e_base_gaz]) + false + # GB12. ^ (RI RI)* RI X RI + # GB13. [^RI] (RI RI)* RI X RI + elsif codepoints[marker..pos].all? { |c| database.boundary[:regional_indicator] === c } && codepoints[marker..pos].count { |c| database.boundary[:regional_indicator] === c }.even? + false + # GB999. Any ÷ Any else true end @@ -358,7 +368,7 @@ module ActiveSupport private - def apply_mapping(string, mapping) #:nodoc: + def apply_mapping(string, mapping) database.codepoints string.each_codepoint.map do |codepoint| cp = database.codepoints[codepoint] diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index bae5f067ae..2df819e554 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -13,7 +13,7 @@ module ActiveSupport # To instrument an event you just need to do: # # ActiveSupport::Notifications.instrument('render', extra: :information) do - # render text: 'Foo' + # render plain: 'Foo' # end # # That first executes the block and then notifies all subscribers once done. @@ -48,7 +48,7 @@ module ActiveSupport # The block is saved and will be called whenever someone instruments "render": # # ActiveSupport::Notifications.instrument('render', extra: :information) do - # render text: 'Foo' + # render plain: 'Foo' # end # # event = events.first diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index 7a49bbb960..880340ca86 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -45,7 +45,7 @@ module ActiveSupport # # number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true) # # => "(755) 6123-4567" - # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/)) + # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/) # # => "133-1234-5678" def number_to_phone(number, options = {}) NumberToPhoneConverter.convert(number, options) @@ -78,7 +78,7 @@ module ActiveSupport # (defaults to "%u%n"). Fields are <tt>%u</tt> for the # currency, and <tt>%n</tt> for the number. # * <tt>:negative_format</tt> - Sets the format for negative - # numbers (defaults to prepending an hyphen to the formatted + # numbers (defaults to prepending a hyphen to the formatted # number given by <tt>:format</tt>). Accepts the same fields # than <tt>:format</tt>, except <tt>%n</tt> is here the # absolute value of the number. @@ -109,7 +109,7 @@ module ActiveSupport # * <tt>:locale</tt> - Sets the locale to be used for formatting # (defaults to current locale). # * <tt>:precision</tt> - Sets the precision of the number - # (defaults to 3). Keeps the number's precision if nil. + # (defaults to 3). Keeps the number's precision if +nil+. # * <tt>:significant</tt> - If +true+, precision will be the number # of significant_digits. If +false+, the number of fractional # digits (defaults to +false+). @@ -183,7 +183,7 @@ module ActiveSupport # * <tt>:locale</tt> - Sets the locale to be used for formatting # (defaults to current locale). # * <tt>:precision</tt> - Sets the precision of the number - # (defaults to 3). Keeps the number's precision if nil. + # (defaults to 3). Keeps the number's precision if +nil+. # * <tt>:significant</tt> - If +true+, precision will be the number # of significant_digits. If +false+, the number of fractional # digits (defaults to +false+). diff --git a/activesupport/lib/active_support/number_helper/number_converter.rb b/activesupport/lib/active_support/number_helper/number_converter.rb index c485a6af63..ce363287cf 100644 --- a/activesupport/lib/active_support/number_helper/number_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_converter.rb @@ -139,17 +139,17 @@ module ActiveSupport @options ||= format_options.merge(opts) end - def format_options #:nodoc: + def format_options default_format_options.merge!(i18n_format_options) end - def default_format_options #:nodoc: + def default_format_options options = DEFAULTS[:format].dup options.merge!(DEFAULTS[namespace][:format]) if namespace options end - def i18n_format_options #:nodoc: + def i18n_format_options locale = opts[:locale] options = I18n.translate(:'number.format', locale: locale, default: {}).dup @@ -160,7 +160,7 @@ module ActiveSupport options end - def translate_number_value_with_default(key, i18n_options = {}) #:nodoc: + def translate_number_value_with_default(key, i18n_options = {}) I18n.translate(key, { default: default_value(key), scope: :number }.merge!(i18n_options)) end @@ -172,7 +172,7 @@ module ActiveSupport key.split(".").reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] } end - def valid_float? #:nodoc: + def valid_float? Float(number) rescue ArgumentError, TypeError false 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 02b4231794..f263dbe9f8 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 @@ -7,10 +7,6 @@ module ActiveSupport self.validate_float = true def convert - if opts.key?(:prefix) - ActiveSupport::Deprecation.warn("The :prefix option of `number_to_human_size` is deprecated and will be removed in Rails 5.1 with no replacement.") - end - @number = Float(number) # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files @@ -54,7 +50,7 @@ module ActiveSupport end def base - opts[:prefix] == :si ? 1000 : 1024 + 1024 end end end diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb index 9e6d8d4fd8..02431704d3 100644 --- a/activesupport/lib/active_support/per_thread_registry.rb +++ b/activesupport/lib/active_support/per_thread_registry.rb @@ -45,8 +45,8 @@ module ActiveSupport Thread.current[@per_thread_registry_key] ||= new end - protected - def method_missing(name, *args, &block) # :nodoc: + private + def method_missing(name, *args, &block) # Caches the method definition as a singleton method of the receiver. # # By letting #delegate handle it, we avoid an enclosure that'll capture args. diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb index dc3f27a16d..ee6592fb5a 100644 --- a/activesupport/lib/active_support/rescuable.rb +++ b/activesupport/lib/active_support/rescuable.rb @@ -36,7 +36,7 @@ module ActiveSupport # render xml: exception, status: 500 # end # - # protected + # private # def deny_access # ... # end @@ -74,7 +74,7 @@ module ActiveSupport # # If no handler matches the exception, check for a handler matching the # (optional) exception.cause. If no handler matches the exception or its - # cause, this returns nil so you can deal with unhandled exceptions. + # cause, this returns +nil+, so you can deal with unhandled exceptions. # Be sure to re-raise unhandled exceptions if this is what you expect. # # begin @@ -83,11 +83,13 @@ module ActiveSupport # rescue_with_handler(exception) || raise # end # - # Returns the exception if it was handled and nil if it was not. + # Returns the exception if it was handled and +nil+ if it was not. def rescue_with_handler(exception, object: self) if handler = handler_for_rescue(exception, object: object) handler.call exception exception + elsif exception + rescue_with_handler(exception.cause, object: object) end end @@ -121,7 +123,7 @@ module ActiveSupport end end - handler || find_rescue_handler(exception.cause) + handler end end diff --git a/activesupport/lib/active_support/string_inquirer.rb b/activesupport/lib/active_support/string_inquirer.rb index 09e1cbb28d..90eac89c9e 100644 --- a/activesupport/lib/active_support/string_inquirer.rb +++ b/activesupport/lib/active_support/string_inquirer.rb @@ -18,7 +18,7 @@ module ActiveSupport private def respond_to_missing?(method_name, include_private = false) - method_name[-1] == "?" + (method_name[-1] == "?") || super end def method_missing(method_name, *arguments) diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb index c018d3e245..2924139755 100644 --- a/activesupport/lib/active_support/subscriber.rb +++ b/activesupport/lib/active_support/subscriber.rb @@ -1,4 +1,5 @@ require "active_support/per_thread_registry" +require "active_support/notifications" module ActiveSupport # ActiveSupport::Subscriber is an object set to consume @@ -51,11 +52,15 @@ module ActiveSupport @@subscribers ||= [] end + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. protected attr_reader :subscriber, :notifier, :namespace - def add_event_subscriber(event) + private + + def add_event_subscriber(event) # :doc: return if %w{ start finish }.include?(event.to_s) pattern = "#{event}.#{namespace}" diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb index 6836378943..ad134c49b6 100644 --- a/activesupport/lib/active_support/tagged_logging.rb +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -48,13 +48,12 @@ module ActiveSupport Thread.current[thread_key] ||= [] end - private - def tags_text - tags = current_tags - if tags.any? - tags.collect { |tag| "[#{tag}] " }.join - end + def tags_text + tags = current_tags + if tags.any? + tags.collect { |tag| "[#{tag}] " }.join end + end end def self.new(logger) diff --git a/activesupport/lib/active_support/testing/autorun.rb b/activesupport/lib/active_support/testing/autorun.rb index 3108e3e549..a18788f38e 100644 --- a/activesupport/lib/active_support/testing/autorun.rb +++ b/activesupport/lib/active_support/testing/autorun.rb @@ -2,8 +2,8 @@ gem "minitest" require "minitest" -if Minitest.respond_to?(:run_via) && !Minitest.run_via[:rails] - Minitest.run_via[:ruby] = true +if Minitest.respond_to?(:run_via) && !Minitest.run_via.set? + Minitest.run_via = :ruby end Minitest.autorun diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index d30b34ecd6..54c3263efa 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -43,7 +43,7 @@ module ActiveSupport end } end - result = Marshal.dump(self.dup) + result = Marshal.dump(dup) end write.puts [result].pack("m") @@ -78,13 +78,15 @@ module ActiveSupport "ISOLATION_OUTPUT" => tmpfile.path } - load_paths = $-I.map { |p| "-I\"#{File.expand_path(p)}\"" }.join(" ") - orig_args = ORIG_ARGV.join(" ") - test_opts = "-n#{self.class.name}##{self.name}" - command = "#{Gem.ruby} #{load_paths} #{$0} '#{orig_args}' #{test_opts}" + test_opts = "-n#{self.class.name}##{name}" - # IO.popen lets us pass env in a cross-platform way - child = IO.popen(env, command) + load_path_args = [] + $-I.each do |p| + load_path_args << "-I" + load_path_args << File.expand_path(p) + end + + child = IO.popen([env, Gem.ruby, *load_path_args, $0, *ORIG_ARGV, test_opts]) begin Process.wait(child.pid) diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index e2f008b4b7..07c9be0604 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -10,7 +10,7 @@ module ActiveSupport @stubs = Concurrent::Map.new { |h, k| h[k] = {} } end - def stub_object(object, method_name, return_value) + def stub_object(object, method_name, &block) if stub = stubbing(object, method_name) unstub_object(stub) end @@ -20,7 +20,7 @@ module ActiveSupport @stubs[object.object_id][method_name] = Stub.new(object, method_name, new_name) object.singleton_class.send :alias_method, new_name, method_name - object.define_singleton_method(method_name) { return_value } + object.define_singleton_method(method_name, &block) end def unstub_all! @@ -134,9 +134,9 @@ module ActiveSupport now = date_or_time.to_time.change(usec: 0) end - simple_stubs.stub_object(Time, :now, now) - simple_stubs.stub_object(Date, :today, now.to_date) - simple_stubs.stub_object(DateTime, :now, now.to_datetime) + simple_stubs.stub_object(Time, :now) { at(now.to_i) } + simple_stubs.stub_object(Date, :today) { jd(now.to_date.jd) } + simple_stubs.stub_object(DateTime, :now) { jd(now.to_date.jd, now.hour, now.min, now.sec, Rational(now.utc_offset, 86400)) } if block_given? begin diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 889f71c4f3..b0dd6b7e8c 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -148,6 +148,7 @@ module ActiveSupport "#{time.strftime(PRECISIONS[fraction_digits.to_i])}#{formatted_offset(true, 'Z'.freeze)}" end alias_method :iso8601, :xmlschema + alias_method :rfc3339, :xmlschema # Coerces time to a string for JSON encoding. The default format is ISO 8601. # You can get %Y/%m/%d %H:%M:%S +offset style by setting @@ -410,6 +411,17 @@ module ActiveSupport @to_datetime ||= utc.to_datetime.new_offset(Rational(utc_offset, 86_400)) end + # Returns an instance of +Time+, either with the same UTC offset + # as +self+ or in the local system timezone depending on the setting + # of +ActiveSupport.to_time_preserves_timezone+. + def to_time + if preserve_timezone + @to_time_with_instance_offset ||= getlocal(utc_offset) + else + @to_time_with_system_offset ||= getlocal + end + end + # So that +self+ <tt>acts_like?(:time)</tt>. def acts_like_time? true @@ -427,7 +439,8 @@ module ActiveSupport end def freeze - period; utc; time # preload instance variables before freezing + # preload instance variables before freezing + period; utc; time; to_datetime; to_time super end diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 09cb9cbbe1..ce5207546d 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -250,14 +250,21 @@ module ActiveSupport # for time zones in the country specified by its ISO 3166-1 Alpha2 code. def country_zones(country_code) code = country_code.to_s.upcase - @country_zones[code] ||= - TZInfo::Country.get(code).zone_identifiers.map do |tz_id| - name = MAPPING.key(tz_id) - name && self[name] - end.compact.sort! + @country_zones[code] ||= load_country_zones(code) end private + def load_country_zones(code) + country = TZInfo::Country.get(code) + country.zone_identifiers.map do |tz_id| + if MAPPING.value?(tz_id) + self[MAPPING.key(tz_id)] + else + create(tz_id, nil, TZInfo::Timezone.new(tz_id)) + end + end.sort! + end + def zones_map @zones_map ||= begin MAPPING.each_key { |place| self[place] } # load all the zones @@ -340,6 +347,41 @@ module ActiveSupport end # Method for creating new ActiveSupport::TimeWithZone instance in time zone + # of +self+ from an ISO 8601 string. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.iso8601('1999-12-31T14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # + # If the time components are missing then they will be set to zero. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.iso8601('1999-12-31') # => Fri, 31 Dec 1999 00:00:00 HST -10:00 + # + # If the string is invalid then an +ArgumentError+ will be raised unlike +parse+ + # which returns +nil+ when given an invalid date string. + def iso8601(str) + parts = Date._iso8601(str) + + raise ArgumentError, "invalid date" if parts.empty? + + time = Time.new( + parts.fetch(:year), + parts.fetch(:mon), + parts.fetch(:mday), + parts.fetch(:hour, 0), + parts.fetch(:min, 0), + parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0), + parts.fetch(:offset, 0) + ) + + if parts[:offset] + TimeWithZone.new(time.utc, self) + else + TimeWithZone.new(nil, self, time) + end + end + + # Method for creating new ActiveSupport::TimeWithZone instance in time zone # of +self+ from parsed string. # # Time.zone = 'Hawaii' # => "Hawaii" @@ -359,6 +401,36 @@ module ActiveSupport parts_to_time(Date._parse(str, false), now) end + # Method for creating new ActiveSupport::TimeWithZone instance in time zone + # of +self+ from an RFC 3339 string. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.rfc3339('2000-01-01T00:00:00Z') # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # + # If the time or zone components are missing then an +ArgumentError+ will + # be raised. This is much stricter than either +parse+ or +iso8601+ which + # allow for missing components. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.rfc3339('1999-12-31') # => ArgumentError: invalid date + def rfc3339(str) + parts = Date._rfc3339(str) + + raise ArgumentError, "invalid date" if parts.empty? + + time = Time.new( + parts.fetch(:year), + parts.fetch(:mon), + parts.fetch(:mday), + parts.fetch(:hour), + parts.fetch(:min), + parts.fetch(:sec) + parts.fetch(:sec_fraction, 0), + parts.fetch(:offset) + ) + + TimeWithZone.new(time.utc, self) + end + # Parses +str+ according to +format+ and returns an ActiveSupport::TimeWithZone. # # Assumes that +str+ is a time in the time zone +self+, diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat Binary files differindex dd2c178fb6..f7d9c48bbe 100644 --- a/activesupport/lib/active_support/values/unicode_tables.dat +++ b/activesupport/lib/active_support/values/unicode_tables.dat diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index e16581d697..782fb41288 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -68,7 +68,17 @@ module ActiveSupport "datetime" => Proc.new { |time| Time.xmlschema(time).utc rescue ::DateTime.parse(time).utc }, "integer" => Proc.new { |integer| integer.to_i }, "float" => Proc.new { |float| float.to_f }, - "decimal" => Proc.new { |number| BigDecimal(number) }, + "decimal" => Proc.new do |number| + if String === number + begin + BigDecimal(number) + rescue ArgumentError + BigDecimal("0") + end + else + BigDecimal(number) + end + end, "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.to_s.strip) }, "string" => Proc.new { |string| string.to_s }, "yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml }, @@ -149,7 +159,7 @@ module ActiveSupport key end - protected + private def _dasherize(key) # $2 must be a non-greedy regex for this to work @@ -158,7 +168,7 @@ module ActiveSupport end # TODO: Add support for other encodings - def _parse_binary(bin, entity) #:nodoc: + def _parse_binary(bin, entity) case entity["encoding"] when "base64" ::Base64.decode64(bin) @@ -175,8 +185,6 @@ module ActiveSupport f end - private - def current_thread_backend Thread.current[:xml_mini_backend] end diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb index 44b0bdb7dc..cde2967132 100644 --- a/activesupport/lib/active_support/xml_mini/libxml.rb +++ b/activesupport/lib/active_support/xml_mini/libxml.rb @@ -74,5 +74,7 @@ module LibXML #:nodoc: end end +# :enddoc: + LibXML::XML::Document.include(LibXML::Conversions::Document) LibXML::XML::Node.include(LibXML::Conversions::Node) diff --git a/activesupport/lib/active_support/xml_mini/libxmlsax.rb b/activesupport/lib/active_support/xml_mini/libxmlsax.rb index 4edb0bd2b1..8a43b05b17 100644 --- a/activesupport/lib/active_support/xml_mini/libxmlsax.rb +++ b/activesupport/lib/active_support/xml_mini/libxmlsax.rb @@ -73,7 +73,7 @@ module ActiveSupport LibXML::XML::Error.set_handler(&LibXML::XML::Error::QUIET_HANDLER) parser = LibXML::XML::SaxParser.io(data) - document = self.document_class.new + document = document_class.new parser.callbacks = document parser.parse diff --git a/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/activesupport/lib/active_support/xml_mini/nokogirisax.rb index b8b85146c5..7388bea5d8 100644 --- a/activesupport/lib/active_support/xml_mini/nokogirisax.rb +++ b/activesupport/lib/active_support/xml_mini/nokogirisax.rb @@ -76,7 +76,7 @@ module ActiveSupport {} else data.ungetc(char) - document = self.document_class.new + document = document_class.new parser = Nokogiri::XML::SAX::Parser.new(document) parser.parse(data) document.hash diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index 4e564591b4..c4f34c0abf 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -3,8 +3,8 @@ ORIG_ARGV = ARGV.dup require "active_support/core_ext/kernel/reporting" silence_warnings do - Encoding.default_internal = "UTF-8" - Encoding.default_external = "UTF-8" + Encoding.default_internal = Encoding::UTF_8 + Encoding.default_external = Encoding::UTF_8 end require "active_support/testing/autorun" @@ -24,16 +24,16 @@ ActiveSupport.to_time_preserves_timezone = ENV["PRESERVE_TIMEZONES"] == "1" # Disable available locale checks to avoid warnings running the test suite. I18n.enforce_available_locales = false -# Skips the current run on Rubinius using Minitest::Assertions#skip -def rubinius_skip(message = "") - skip message if RUBY_ENGINE == "rbx" -end - -# Skips the current run on JRuby using Minitest::Assertions#skip -def jruby_skip(message = "") - skip message if defined?(JRUBY_VERSION) -end - class ActiveSupport::TestCase include ActiveSupport::Testing::MethodCallAssertions + + # Skips the current run on Rubinius using Minitest::Assertions#skip + private def rubinius_skip(message = "") + skip message if RUBY_ENGINE == "rbx" + end + + # Skips the current run on JRuby using Minitest::Assertions#skip + private def jruby_skip(message = "") + skip message if defined?(JRUBY_VERSION) + end end diff --git a/activesupport/test/array_inquirer_test.rb b/activesupport/test/array_inquirer_test.rb index 4d3f5b001c..5b2bc82905 100644 --- a/activesupport/test/array_inquirer_test.rb +++ b/activesupport/test/array_inquirer_test.rb @@ -38,4 +38,24 @@ class ArrayInquirerTest < ActiveSupport::TestCase assert_instance_of ActiveSupport::ArrayInquirer, result assert_equal @array_inquirer, result end + + def test_respond_to_fallback_to_array_respond_to + Array.class_eval do + def respond_to_missing?(name, include_private = false) + (name == :foo) || super + end + end + arr = ActiveSupport::ArrayInquirer.new([:x]) + + assert_respond_to arr, :can_you_hear_me? + assert_respond_to arr, :foo + assert_not_respond_to arr, :nope + ensure + Array.class_eval do + undef_method :respond_to_missing? + def respond_to_missing?(name, include_private = false) + super + end + end + end end diff --git a/activesupport/test/autoloading_fixtures/prepend.rb b/activesupport/test/autoloading_fixtures/prepend.rb new file mode 100644 index 0000000000..3134d1df2b --- /dev/null +++ b/activesupport/test/autoloading_fixtures/prepend.rb @@ -0,0 +1,8 @@ +class SubClassConflict +end + +class Prepend + module PrependedModule + end + prepend PrependedModule +end diff --git a/activesupport/test/autoloading_fixtures/prepend/sub_class_conflict.rb b/activesupport/test/autoloading_fixtures/prepend/sub_class_conflict.rb new file mode 100644 index 0000000000..090dda3043 --- /dev/null +++ b/activesupport/test/autoloading_fixtures/prepend/sub_class_conflict.rb @@ -0,0 +1,2 @@ +class Prepend::SubClassConflict +end diff --git a/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb b/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb index 829ee917d2..e477ab21d0 100644 --- a/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb +++ b/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb @@ -1,4 +1,4 @@ RaisesArbitraryException = 1 _ = A::B # Autoloading recursion, also expected to be watched and discarded. -raise Exception, "arbitray exception message" +raise Exception, "arbitrary exception message" diff --git a/activesupport/test/broadcast_logger_test.rb b/activesupport/test/broadcast_logger_test.rb index d003a33471..184d0ebddd 100644 --- a/activesupport/test/broadcast_logger_test.rb +++ b/activesupport/test/broadcast_logger_test.rb @@ -69,6 +69,20 @@ module ActiveSupport assert_equal ::Logger::FATAL, log2.local_level end + test "#silence does not break custom loggers" do + new_logger = FakeLogger.new + custom_logger = CustomLogger.new + custom_logger.extend(Logger.broadcast(new_logger)) + + custom_logger.silence do + custom_logger.error "from error" + custom_logger.unknown "from unknown" + end + + assert_equal [[::Logger::ERROR, "from error", nil], [::Logger::UNKNOWN, "from unknown", nil]], custom_logger.adds + assert_equal [[::Logger::ERROR, "from error", nil], [::Logger::UNKNOWN, "from unknown", nil]], new_logger.adds + end + test "#silence silences all loggers below the default level of ERROR" do logger.silence do logger.debug "test" @@ -98,9 +112,7 @@ module ActiveSupport assert_equal [[::Logger::FATAL, "seen", nil]], log2.adds end - class FakeLogger - include LoggerSilence - + class CustomLogger attr_reader :adds, :closed, :chevrons attr_accessor :level, :progname, :formatter, :local_level @@ -150,5 +162,9 @@ module ActiveSupport @closed = true end end + + class FakeLogger < CustomLogger + include LoggerSilence + end end end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index f489ded1be..c67ffe69b8 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -47,6 +47,17 @@ module ActiveSupport assert_raises(RuntimeError) { middleware.call({}) } assert_nil LocalCacheRegistry.cache_for(key) end + + def test_local_cache_cleared_on_throw + key = "super awesome key" + assert_nil LocalCacheRegistry.cache_for key + middleware = Middleware.new("<3", key).new(->(env) { + assert LocalCacheRegistry.cache_for(key), "should have a cache" + throw :warden + }) + assert_throws(:warden) { middleware.call({}) } + assert_nil LocalCacheRegistry.cache_for(key) + end end end end @@ -316,7 +327,7 @@ module CacheStoreBehavior def test_should_read_and_write_nil assert @cache.write("foo", nil) - assert_equal nil, @cache.read("foo") + assert_nil @cache.read("foo") end def test_should_read_and_write_false @@ -464,7 +475,7 @@ module CacheStoreBehavior Time.stub(:now, Time.at(time)) do result = @cache.fetch("foo") do - assert_equal nil, @cache.read("foo") + assert_nil @cache.read("foo") "baz" end assert_equal "baz", result @@ -476,7 +487,7 @@ module CacheStoreBehavior @cache.write("foo", "bar", expires_in: 60) Time.stub(:now, time + 71) do result = @cache.fetch("foo", race_condition_ttl: 10) do - assert_equal nil, @cache.read("foo") + assert_nil @cache.read("foo") "baz" end assert_equal "baz", result @@ -566,12 +577,6 @@ module CacheStoreBehavior ensure ActiveSupport::Notifications.unsubscribe "cache_read.active_support" end - - def test_can_call_deprecated_namesaced_key - assert_deprecated "`namespaced_key` is deprecated" do - @cache.send(:namespaced_key, 111, {}) - end - end end # https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters @@ -681,9 +686,9 @@ module LocalCacheBehavior def test_local_cache_of_read_nil @cache.with_local_cache do - assert_equal nil, @cache.read("foo") + assert_nil @cache.read("foo") @cache.send(:bypass_local_cache) { @cache.write "foo", "bar" } - assert_equal nil, @cache.read("foo") + assert_nil @cache.read("foo") end end @@ -747,15 +752,6 @@ module LocalCacheBehavior app = @cache.middleware.new(app) app.call({}) end - - def test_can_call_deprecated_set_cache_value - @cache.with_local_cache do - assert_deprecated "`set_cache_value` is deprecated" do - @cache.send(:set_cache_value, 1, "foo", :ignored, {}) - end - assert_equal 1, @cache.read("foo") - end - end end module AutoloadingCacheBehavior @@ -918,12 +914,6 @@ class FileStoreTest < ActiveSupport::TestCase @cache.write(1, nil) assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true) end - - def test_can_call_deprecated_key_file_path - assert_deprecated "`key_file_path` is deprecated" do - assert_equal 111, @cache.send(:key_file_path, 111) - end - end end class MemoryStoreTest < ActiveSupport::TestCase @@ -1004,7 +994,7 @@ class MemoryStoreTest < ActiveSupport::TestCase end def test_pruning_is_capped_at_a_max_time - def @cache.delete_entry (*args) + def @cache.delete_entry(*args) sleep(0.01) super end @@ -1098,12 +1088,6 @@ class MemCacheStoreTest < ActiveSupport::TestCase value << "bingo" assert_not_equal value, @cache.read("foo") end - - def test_can_call_deprecated_escape_key - assert_deprecated "`escape_key` is deprecated" do - assert_equal 111, @cache.send(:escape_key, 111) - end - end end class NullStoreTest < ActiveSupport::TestCase diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index aadc40ab84..4f00afb581 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -23,10 +23,6 @@ module CallbacksTest method_name end - def callback_string(callback_method) - "history << [#{callback_method.to_sym.inspect}, :string]" - end - def callback_proc(callback_method) Proc.new { |model| model.history << [callback_method, :proc] } end @@ -61,7 +57,6 @@ module CallbacksTest [:before_save, :after_save].each do |callback_method| callback_method_sym = callback_method.to_sym send(callback_method, callback_symbol(callback_method_sym)) - ActiveSupport::Deprecation.silence { send(callback_method, callback_string(callback_method_sym)) } send(callback_method, callback_proc(callback_method_sym)) send(callback_method, callback_object(callback_method_sym.to_s.gsub(/_save/, ""))) send(callback_method, CallbackClass) @@ -197,10 +192,12 @@ module CallbacksTest before_save Proc.new { |r| r.history << [:before_save, :symbol] }, unless: :no before_save Proc.new { |r| r.history << "b00m" }, unless: :yes # string - before_save Proc.new { |r| r.history << [:before_save, :string] }, if: "yes" - before_save Proc.new { |r| r.history << "b00m" }, if: "no" - before_save Proc.new { |r| r.history << [:before_save, :string] }, unless: "no" - before_save Proc.new { |r| r.history << "b00m" }, unless: "yes" + ActiveSupport::Deprecation.silence do + before_save Proc.new { |r| r.history << [:before_save, :string] }, if: "yes" + before_save Proc.new { |r| r.history << "b00m" }, if: "no" + before_save Proc.new { |r| r.history << [:before_save, :string] }, unless: "no" + before_save Proc.new { |r| r.history << "b00m" }, unless: "yes" + end # Combined if and unless before_save Proc.new { |r| r.history << [:before_save, :combined_symbol] }, if: :yes, unless: :no before_save Proc.new { |r| r.history << "b00m" }, if: :yes, unless: :yes @@ -224,14 +221,54 @@ module CallbacksTest define_callbacks :save end - class AroundPerson < MySuper + class MySlate < MySuper attr_reader :history attr_accessor :save_fails + def initialize + @history = [] + end + + def save + run_callbacks :save do + raise "inside save" if save_fails + @history << "running" + end + end + + def no; false; end + def yes; true; end + + def method_missing(sym, *) + case sym + when /^log_(.*)/ + @history << $1 + nil + when /^wrap_(.*)/ + @history << "wrap_#$1" + yield + @history << "unwrap_#$1" + nil + when /^double_(.*)/ + @history << "first_#$1" + yield + @history << "second_#$1" + yield + @history << "third_#$1" + else + super + end + end + + def respond_to_missing?(sym) + sym =~ /^(log|wrap)_/ || super + end + end + + class AroundPerson < MySlate set_callback :save, :before, :nope, if: :no set_callback :save, :before, :nope, unless: :yes set_callback :save, :after, :tweedle - ActiveSupport::Deprecation.silence { set_callback :save, :before, "tweedle_dee" } set_callback :save, :before, proc { |m| m.history << "yup" } set_callback :save, :before, :nope, if: proc { false } set_callback :save, :before, :nope, unless: proc { true } @@ -242,9 +279,6 @@ module CallbacksTest set_callback :save, :around, :w0tno, if: :no set_callback :save, :around, :tweedle_deedle - def no; false; end - def yes; true; end - def nope @history << "boom" end @@ -264,10 +298,6 @@ module CallbacksTest yield end - def tweedle_dee - @history << "tweedle dee" - end - def tweedle_dum @history << "tweedle dum pre" yield @@ -283,17 +313,6 @@ module CallbacksTest yield @history << "tweedle deedle post" end - - def initialize - @history = [] - end - - def save - run_callbacks :save do - raise "inside save" if save_fails - @history << "running" - end - end end class AroundPersonResult < MySuper @@ -394,7 +413,6 @@ module CallbacksTest around = AroundPerson.new around.save assert_equal [ - "tweedle dee", "yup", "yup", "tweedle dum pre", "w0tyes before", @@ -408,6 +426,32 @@ module CallbacksTest end end + class DoubleYieldTest < ActiveSupport::TestCase + class DoubleYieldModel < MySlate + set_callback :save, :around, :wrap_outer + set_callback :save, :around, :double_trouble + set_callback :save, :around, :wrap_inner + end + + def test_double_save + double = DoubleYieldModel.new + double.save + assert_equal [ + "wrap_outer", + "first_trouble", + "wrap_inner", + "running", + "unwrap_inner", + "second_trouble", + "wrap_inner", + "running", + "unwrap_inner", + "third_trouble", + "unwrap_outer", + ], double.history + end + end + class CallStackTest < ActiveSupport::TestCase def test_tidy_call_stack around = AroundPerson.new @@ -487,7 +531,6 @@ module CallbacksTest assert_equal [], person.history person.save assert_equal [ - [:before_save, :string], [:before_save, :proc], [:before_save, :object], [:before_save, :block], @@ -495,7 +538,6 @@ module CallbacksTest [:after_save, :class], [:after_save, :object], [:after_save, :proc], - [:after_save, :string], [:after_save, :symbol] ], person.history end @@ -514,7 +556,6 @@ module CallbacksTest [:after_save, :class], [:after_save, :object], [:after_save, :proc], - [:after_save, :string], [:after_save, :symbol] ], person.history end @@ -527,7 +568,6 @@ module CallbacksTest person.save assert_equal [ [:before_save, :symbol], - [:before_save, :string], [:before_save, :proc], [:before_save, :object], [:before_save, :class], @@ -536,7 +576,6 @@ module CallbacksTest [:after_save, :class], [:after_save, :object], [:after_save, :proc], - [:after_save, :string], [:after_save, :symbol] ], person.history end @@ -830,37 +869,11 @@ module CallbacksTest end end - class CallbackFalseTerminatorWithoutConfigTest < ActiveSupport::TestCase - def test_returning_false_does_not_halt_callback_if_config_variable_is_not_set - obj = CallbackFalseTerminator.new - obj.save - assert_equal nil, obj.halted - assert obj.saved - end - end - - class CallbackFalseTerminatorWithConfigTrueTest < ActiveSupport::TestCase - def setup - ActiveSupport::Callbacks.halt_and_display_warning_on_return_false = true - end - - def test_returning_false_does_not_halt_callback_if_config_variable_is_true - obj = CallbackFalseTerminator.new - obj.save - assert_equal nil, obj.halted - assert obj.saved - end - end - - class CallbackFalseTerminatorWithConfigFalseTest < ActiveSupport::TestCase - def setup - ActiveSupport::Callbacks.halt_and_display_warning_on_return_false = false - end - - def test_returning_false_does_not_halt_callback_if_config_variable_is_false + class CallbackFalseTerminatorTest < ActiveSupport::TestCase + def test_returning_false_does_not_halt_callback obj = CallbackFalseTerminator.new obj.save - assert_equal nil, obj.halted + assert_nil obj.halted assert obj.saved end end @@ -886,7 +899,6 @@ module CallbacksTest writer.save assert_equal [ [:before_save, :symbol], - [:before_save, :string], [:before_save, :proc], [:before_save, :object], [:before_save, :class], @@ -895,7 +907,6 @@ module CallbacksTest [:after_save, :class], [:after_save, :object], [:after_save, :proc], - [:after_save, :string], [:after_save, :symbol] ], writer.history end @@ -987,7 +998,7 @@ module CallbacksTest set_callback :foo, :before, :foo, if: callback def run; run_callbacks :foo; end private - def foo; end + def foo; end } object = klass.new object.run @@ -1109,14 +1120,6 @@ module CallbacksTest assert_equal 1, calls.length end - def test_add_eval - calls = [] - klass = ActiveSupport::Deprecation.silence { build_class("bar") } - klass.class_eval { define_method(:bar) { calls << klass } } - klass.new.run - assert_equal 1, calls.length - end - def test_skip_class # removes one at a time calls = [] callback = Class.new { @@ -1151,7 +1154,7 @@ module CallbacksTest def test_skip_string # raises error calls = [] - klass = ActiveSupport::Deprecation.silence { build_class("bar") } + klass = build_class(:bar) klass.class_eval { define_method(:bar) { calls << klass } } assert_raises(ArgumentError) { klass.skip "bar" } klass.new.run @@ -1178,11 +1181,22 @@ module CallbacksTest end class DeprecatedWarningTest < ActiveSupport::TestCase - def test_deprecate_string_callback + def test_deprecate_string_conditional_options + klass = Class.new(Record) + + assert_deprecated { klass.before_save :tweedle, if: "true" } + assert_deprecated { klass.after_save :tweedle, unless: "false" } + assert_deprecated { klass.skip_callback :save, :before, :tweedle, if: "true" } + assert_deprecated { klass.skip_callback :save, :after, :tweedle, unless: "false" } + end + end + + class NotPermittedStringCallbackTest < ActiveSupport::TestCase + def test_passing_string_callback_is_not_permitted klass = Class.new(Record) - assert_deprecated do - klass.send :before_save, "tweedle_dee" + assert_raises(ArgumentError) do + klass.before_save "tweedle" end end end diff --git a/activesupport/test/class_cache_test.rb b/activesupport/test/class_cache_test.rb index c618fea81a..004b4dc9ce 100644 --- a/activesupport/test/class_cache_test.rb +++ b/activesupport/test/class_cache_test.rb @@ -61,7 +61,7 @@ module ActiveSupport def test_safe_get_constantizes_doesnt_fail_on_invalid_names assert @cache.empty? - assert_equal nil, @cache.safe_get("OmgTotallyInvalidConstantName") + assert_nil @cache.safe_get("OmgTotallyInvalidConstantName") end def test_new_rejects_strings diff --git a/activesupport/test/concern_test.rb b/activesupport/test/concern_test.rb index 95507c815d..7a5a5414a7 100644 --- a/activesupport/test/concern_test.rb +++ b/activesupport/test/concern_test.rb @@ -76,7 +76,7 @@ class ConcernTest < ActiveSupport::TestCase end def test_class_methods_are_extended_only_on_expected_objects - ::Object.__send__(:include, Qux) + ::Object.include(Qux) Object.extend(Qux::ClassMethods) # module needs to be created after Qux is included in Object or bug won't # be triggered diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb index af2db8c991..32b720bcbb 100644 --- a/activesupport/test/constantize_test_cases.rb +++ b/activesupport/test/constantize_test_cases.rb @@ -73,6 +73,11 @@ module ConstantizeTestCases yield("RaisesNoMethodError") end end + + with_autoloading_fixtures do + yield("Prepend::SubClassConflict") + assert_equal "constant", defined?(Prepend::SubClassConflict) + end end def run_safe_constantize_tests_on diff --git a/activesupport/test/core_ext/array/conversions_test.rb b/activesupport/test/core_ext/array/conversions_test.rb index 469efcd934..29e661a99b 100644 --- a/activesupport/test/core_ext/array/conversions_test.rb +++ b/activesupport/test/core_ext/array/conversions_test.rb @@ -25,11 +25,6 @@ class ToSentenceTest < ActiveSupport::TestCase assert_equal "one, two and three", ["one", "two", "three"].to_sentence(last_word_connector: " and ") end - def test_to_sentence_with_fallback_string - assert_equal "none", [].to_sentence(fallback_string: "none") - assert_equal "one, two, and three", ["one", "two", "three"].to_sentence(fallback_string: "none") - end - def test_two_elements assert_equal "one and two", ["one", "two"].to_sentence assert_equal "one two", ["one", "two"].to_sentence(two_words_connector: " ") @@ -63,7 +58,7 @@ class ToSentenceTest < ActiveSupport::TestCase ["one", "two"].to_sentence(passing: "invalid option") end - assert_equal exception.message, "Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale, :fallback_string" + assert_equal exception.message, "Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale" end def test_always_returns_string diff --git a/activesupport/test/core_ext/class_test.rb b/activesupport/test/core_ext/class_test.rb index a9c44907cc..a7905196ae 100644 --- a/activesupport/test/core_ext/class_test.rb +++ b/activesupport/test/core_ext/class_test.rb @@ -25,4 +25,14 @@ class ClassTest < ActiveSupport::TestCase assert_equal [Baz], Bar.subclasses assert_equal [], Baz.subclasses end + + def test_descendants_excludes_singleton_classes + klass = Parent.new.singleton_class + refute Parent.descendants.include?(klass), "descendants should not include singleton classes" + end + + def test_subclasses_excludes_singleton_classes + klass = Parent.new.singleton_class + refute Parent.subclasses.include?(klass), "subclasses should not include singleton classes" + end end diff --git a/activesupport/test/core_ext/date_and_time_compatibility_test.rb b/activesupport/test/core_ext/date_and_time_compatibility_test.rb index 4c90460032..6c6205a4d2 100644 --- a/activesupport/test/core_ext/date_and_time_compatibility_test.rb +++ b/activesupport/test/core_ext/date_and_time_compatibility_test.rb @@ -16,11 +16,13 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase def test_time_to_time_preserves_timezone with_preserve_timezone(true) do with_env_tz "US/Eastern" do - time = Time.new(2016, 4, 23, 15, 11, 12, 3600).to_time + source = Time.new(2016, 4, 23, 15, 11, 12, 3600) + time = source.to_time assert_instance_of Time, time assert_equal @utc_time, time.getutc assert_equal @utc_offset, time.utc_offset + assert_equal source.object_id, time.object_id end end end @@ -28,11 +30,43 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase def test_time_to_time_does_not_preserve_time_zone with_preserve_timezone(false) do with_env_tz "US/Eastern" do - time = Time.new(2016, 4, 23, 15, 11, 12, 3600).to_time + source = Time.new(2016, 4, 23, 15, 11, 12, 3600) + time = source.to_time assert_instance_of Time, time assert_equal @utc_time, time.getutc assert_equal @system_offset, time.utc_offset + assert_not_equal source.object_id, time.object_id + end + end + end + + def test_time_to_time_frozen_preserves_timezone + with_preserve_timezone(true) do + with_env_tz "US/Eastern" do + source = Time.new(2016, 4, 23, 15, 11, 12, 3600).freeze + time = source.to_time + + assert_instance_of Time, time + assert_equal @utc_time, time.getutc + assert_equal @utc_offset, time.utc_offset + assert_equal source.object_id, time.object_id + assert_predicate time, :frozen? + end + end + end + + def test_time_to_time_frozen_does_not_preserve_time_zone + with_preserve_timezone(false) do + with_env_tz "US/Eastern" do + source = Time.new(2016, 4, 23, 15, 11, 12, 3600).freeze + time = source.to_time + + assert_instance_of Time, time + assert_equal @utc_time, time.getutc + assert_equal @system_offset, time.utc_offset + assert_not_equal source.object_id, time.object_id + assert_not_predicate time, :frozen? end end end @@ -40,7 +74,8 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase def test_datetime_to_time_preserves_timezone with_preserve_timezone(true) do with_env_tz "US/Eastern" do - time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).to_time + source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)) + time = source.to_time assert_instance_of Time, time assert_equal @utc_time, time.getutc @@ -52,7 +87,8 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase def test_datetime_to_time_does_not_preserve_time_zone with_preserve_timezone(false) do with_env_tz "US/Eastern" do - time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).to_time + source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)) + time = source.to_time assert_instance_of Time, time assert_equal @utc_time, time.getutc @@ -61,17 +97,47 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase end end + def test_datetime_to_time_frozen_preserves_timezone + with_preserve_timezone(true) do + with_env_tz "US/Eastern" do + source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).freeze + time = source.to_time + + assert_instance_of Time, time + assert_equal @utc_time, time.getutc + assert_equal @utc_offset, time.utc_offset + assert_not_predicate time, :frozen? + end + end + end + + def test_datetime_to_time_frozen_does_not_preserve_time_zone + with_preserve_timezone(false) do + with_env_tz "US/Eastern" do + source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).freeze + time = source.to_time + + assert_instance_of Time, time + assert_equal @utc_time, time.getutc + assert_equal @system_offset, time.utc_offset + assert_not_predicate time, :frozen? + end + end + end + def test_twz_to_time_preserves_timezone with_preserve_timezone(true) do with_env_tz "US/Eastern" do - time = ActiveSupport::TimeWithZone.new(@utc_time, @zone).to_time + source = ActiveSupport::TimeWithZone.new(@utc_time, @zone) + time = source.to_time assert_instance_of Time, time assert_equal @utc_time, time.getutc assert_instance_of Time, time.getutc assert_equal @utc_offset, time.utc_offset - time = ActiveSupport::TimeWithZone.new(@date_time, @zone).to_time + source = ActiveSupport::TimeWithZone.new(@date_time, @zone) + time = source.to_time assert_instance_of Time, time assert_equal @date_time, time.getutc @@ -84,19 +150,69 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase def test_twz_to_time_does_not_preserve_time_zone with_preserve_timezone(false) do with_env_tz "US/Eastern" do - time = ActiveSupport::TimeWithZone.new(@utc_time, @zone).to_time + source = ActiveSupport::TimeWithZone.new(@utc_time, @zone) + time = source.to_time + + assert_instance_of Time, time + assert_equal @utc_time, time.getutc + assert_instance_of Time, time.getutc + assert_equal @system_offset, time.utc_offset + + source = ActiveSupport::TimeWithZone.new(@date_time, @zone) + time = source.to_time + + assert_instance_of Time, time + assert_equal @date_time, time.getutc + assert_instance_of Time, time.getutc + assert_equal @system_offset, time.utc_offset + end + end + end + + def test_twz_to_time_frozen_preserves_timezone + with_preserve_timezone(true) do + with_env_tz "US/Eastern" do + source = ActiveSupport::TimeWithZone.new(@utc_time, @zone).freeze + time = source.to_time + + assert_instance_of Time, time + assert_equal @utc_time, time.getutc + assert_instance_of Time, time.getutc + assert_equal @utc_offset, time.utc_offset + assert_not_predicate time, :frozen? + + source = ActiveSupport::TimeWithZone.new(@date_time, @zone).freeze + time = source.to_time + + assert_instance_of Time, time + assert_equal @date_time, time.getutc + assert_instance_of Time, time.getutc + assert_equal @utc_offset, time.utc_offset + assert_not_predicate time, :frozen? + end + end + end + + def test_twz_to_time_frozen_does_not_preserve_time_zone + with_preserve_timezone(false) do + with_env_tz "US/Eastern" do + source = ActiveSupport::TimeWithZone.new(@utc_time, @zone).freeze + time = source.to_time assert_instance_of Time, time assert_equal @utc_time, time.getutc assert_instance_of Time, time.getutc assert_equal @system_offset, time.utc_offset + assert_not_predicate time, :frozen? - time = ActiveSupport::TimeWithZone.new(@date_time, @zone).to_time + source = ActiveSupport::TimeWithZone.new(@date_time, @zone).freeze + time = source.to_time assert_instance_of Time, time assert_equal @date_time, time.getutc assert_instance_of Time, time.getutc assert_equal @system_offset, time.utc_offset + assert_not_predicate time, :frozen? end end end @@ -104,7 +220,8 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase def test_string_to_time_preserves_timezone with_preserve_timezone(true) do with_env_tz "US/Eastern" do - time = "2016-04-23T15:11:12+01:00".to_time + source = "2016-04-23T15:11:12+01:00" + time = source.to_time assert_instance_of Time, time assert_equal @utc_time, time.getutc @@ -116,11 +233,40 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase def test_string_to_time_does_not_preserve_time_zone with_preserve_timezone(false) do with_env_tz "US/Eastern" do - time = "2016-04-23T15:11:12+01:00".to_time + source = "2016-04-23T15:11:12+01:00" + time = source.to_time + + assert_instance_of Time, time + assert_equal @utc_time, time.getutc + assert_equal @system_offset, time.utc_offset + end + end + end + + def test_string_to_time_frozen_preserves_timezone + with_preserve_timezone(true) do + with_env_tz "US/Eastern" do + source = "2016-04-23T15:11:12+01:00".freeze + time = source.to_time + + assert_instance_of Time, time + assert_equal @utc_time, time.getutc + assert_equal @utc_offset, time.utc_offset + assert_not_predicate time, :frozen? + end + end + end + + def test_string_to_time_frozen_does_not_preserve_time_zone + with_preserve_timezone(false) do + with_env_tz "US/Eastern" do + source = "2016-04-23T15:11:12+01:00".freeze + time = source.to_time assert_instance_of Time, time assert_equal @utc_time, time.getutc assert_equal @system_offset, time.utc_offset + assert_not_predicate time, :frozen? end end end diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index 5a90210bb8..50bb1004f7 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -395,6 +395,10 @@ class DateExtBehaviorTest < ActiveSupport::TestCase assert Date.new.acts_like_date? end + def test_blank? + assert_not Date.new.blank? + end + def test_freeze_doesnt_clobber_memoized_instance_methods assert_nothing_raised do Date.today.freeze.inspect diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 81777889ec..36f0ee22b8 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -4,8 +4,8 @@ require "core_ext/date_and_time_behavior" require "time_zone_test_helpers" class DateTimeExtCalculationsTest < ActiveSupport::TestCase - def date_time_init(year, month, day, hour, minute, second, *args) - DateTime.civil(year, month, day, hour, minute, second) + def date_time_init(year, month, day, hour, minute, second, usec = 0) + DateTime.civil(year, month, day, hour, minute, second + (usec / 1000000)) end include DateAndTimeBehavior @@ -113,7 +113,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end def test_end_of_day - assert_equal DateTime.civil(2005, 2, 4, 23, 59, 59), DateTime.civil(2005, 2, 4, 10, 10, 10).end_of_day + assert_equal DateTime.civil(2005, 2, 4, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 4, 10, 10, 10).end_of_day end def test_beginning_of_hour @@ -121,7 +121,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end def test_end_of_hour - assert_equal DateTime.civil(2005, 2, 4, 19, 59, 59), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_hour + assert_equal DateTime.civil(2005, 2, 4, 19, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_hour end def test_beginning_of_minute @@ -129,13 +129,13 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end def test_end_of_minute - assert_equal DateTime.civil(2005, 2, 4, 19, 30, 59), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_minute + assert_equal DateTime.civil(2005, 2, 4, 19, 30, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_minute end def test_end_of_month - assert_equal DateTime.civil(2005, 3, 31, 23, 59, 59), DateTime.civil(2005, 3, 20, 10, 10, 10).end_of_month - assert_equal DateTime.civil(2005, 2, 28, 23, 59, 59), DateTime.civil(2005, 2, 20, 10, 10, 10).end_of_month - assert_equal DateTime.civil(2005, 4, 30, 23, 59, 59), DateTime.civil(2005, 4, 20, 10, 10, 10).end_of_month + assert_equal DateTime.civil(2005, 3, 31, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 3, 20, 10, 10, 10).end_of_month + assert_equal DateTime.civil(2005, 2, 28, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 20, 10, 10, 10).end_of_month + assert_equal DateTime.civil(2005, 4, 30, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 4, 20, 10, 10, 10).end_of_month end def test_last_year @@ -162,12 +162,19 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal DateTime.civil(2006, 2, 22, 15, 15, 10), DateTime.civil(2005, 2, 22, 15, 15, 10).change(year: 2006) assert_equal DateTime.civil(2005, 6, 22, 15, 15, 10), DateTime.civil(2005, 2, 22, 15, 15, 10).change(month: 6) assert_equal DateTime.civil(2012, 9, 22, 15, 15, 10), DateTime.civil(2005, 2, 22, 15, 15, 10).change(year: 2012, month: 9) - assert_equal DateTime.civil(2005, 2, 22, 16), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16) - assert_equal DateTime.civil(2005, 2, 22, 16, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16, min: 45) - assert_equal DateTime.civil(2005, 2, 22, 15, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(min: 45) + assert_equal DateTime.civil(2005, 2, 22, 16), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16) + assert_equal DateTime.civil(2005, 2, 22, 16, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16, min: 45) + assert_equal DateTime.civil(2005, 2, 22, 15, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(min: 45) # datetime with fractions of a second assert_equal DateTime.civil(2005, 2, 1, 15, 15, 10.7), DateTime.civil(2005, 2, 22, 15, 15, 10.7).change(day: 1) + assert_equal DateTime.civil(2005, 1, 2, 11, 22, Rational(33000008, 1000000)), DateTime.civil(2005, 1, 2, 11, 22, 33).change(usec: 8) + assert_equal DateTime.civil(2005, 1, 2, 11, 22, Rational(33000008, 1000000)), DateTime.civil(2005, 1, 2, 11, 22, 33).change(nsec: 8000) + assert_raise(ArgumentError) { DateTime.civil(2005, 1, 2, 11, 22, 0).change(usec: 1, nsec: 1) } + assert_raise(ArgumentError) { DateTime.civil(2005, 1, 2, 11, 22, 0).change(usec: 1000000) } + assert_raise(ArgumentError) { DateTime.civil(2005, 1, 2, 11, 22, 0).change(nsec: 1000000000) } + assert_nothing_raised { DateTime.civil(2005, 1, 2, 11, 22, 0).change(usec: 999999) } + assert_nothing_raised { DateTime.civil(2005, 1, 2, 11, 22, 0).change(nsec: 999999999) } end def test_advance @@ -318,6 +325,10 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert DateTime.new.acts_like_time? end + def test_blank? + assert_not DateTime.new.blank? + end + def test_utc? assert_equal true, DateTime.civil(2005, 2, 21, 10, 11, 12).utc? assert_equal true, DateTime.civil(2005, 2, 21, 10, 11, 12, 0).utc? @@ -376,7 +387,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal 1, DateTime.civil(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59).to_s assert_equal 0, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0).to_s assert_equal(-1, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 1).to_s) - assert_equal nil, DateTime.civil(2000) <=> "Invalid as Time" + assert_nil DateTime.civil(2000) <=> "Invalid as Time" end def test_compare_with_integer diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index b7b4a9dd00..1648a9b270 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -21,7 +21,7 @@ class DurationTest < ActiveSupport::TestCase end def test_instance_of - assert 1.minute.instance_of?(Fixnum) + assert 1.minute.instance_of?(1.class) assert 2.days.instance_of?(ActiveSupport::Duration) assert !3.second.instance_of?(Numeric) end @@ -84,6 +84,38 @@ class DurationTest < ActiveSupport::TestCase assert_nothing_raised { Date.today - Date.today } end + def test_plus + assert_equal 2.seconds, 1.second + 1.second + assert_instance_of ActiveSupport::Duration, 1.second + 1.second + assert_equal 2.seconds, 1.second + 1 + assert_instance_of ActiveSupport::Duration, 1.second + 1 + end + + def test_minus + assert_equal 1.second, 2.seconds - 1.second + assert_instance_of ActiveSupport::Duration, 2.seconds - 1.second + assert_equal 1.second, 2.seconds - 1 + assert_instance_of ActiveSupport::Duration, 2.seconds - 1 + assert_equal 1.second, 2 - 1.second + assert_instance_of ActiveSupport::Duration, 2.seconds - 1 + end + + def test_multiply + assert_equal 7.days, 1.day * 7 + assert_instance_of ActiveSupport::Duration, 1.day * 7 + assert_equal 86400, 1.day * 1.second + end + + def test_divide + assert_equal 1.day, 7.days / 7 + assert_instance_of ActiveSupport::Duration, 7.days / 7 + assert_equal 1, 1.day / 1.day + end + + def test_date_added_with_multiplied_duration + assert_equal Date.civil(2017, 1, 3), Date.civil(2017, 1, 1) + 1.day * 2 + end + def test_plus_with_time assert_equal 1 + 1.second, 1.second + 1, "Duration + Numeric should == Numeric + Duration" end @@ -179,6 +211,19 @@ class DurationTest < ActiveSupport::TestCase Time.zone = nil end + def test_before_and_afer + t = Time.local(2000) + assert_equal t + 1, 1.second.after(t) + assert_equal t - 1, 1.second.before(t) + end + + def test_before_and_after_without_argument + Time.stub(:now, Time.local(2000)) do + assert_equal Time.now - 1.second, 1.second.before + assert_equal Time.now + 1.second, 1.second.after + end + end + def test_adding_hours_across_dst_boundary with_env_tz "CET" do assert_equal Time.local(2009, 3, 29, 0, 0, 0) + 24.hours, Time.local(2009, 3, 30, 1, 0, 0) @@ -196,7 +241,7 @@ class DurationTest < ActiveSupport::TestCase assert_nothing_raised do 1.minute.times { counter += 1 } end - assert_equal counter, 60 + assert_equal 60, counter end def test_as_json @@ -213,7 +258,7 @@ class DurationTest < ActiveSupport::TestCase when 1.day "ok" end - assert_equal cased, "ok" + assert_equal "ok", cased end def test_respond_to @@ -237,6 +282,141 @@ class DurationTest < ActiveSupport::TestCase assert_equal(1, (61 <=> 1.minute)) end + def test_implicit_coercion + assert_equal 2.days, 2 * 1.day + assert_instance_of ActiveSupport::Duration, 2 * 1.day + assert_equal Time.utc(2017, 1, 3), Time.utc(2017, 1, 1) + 2 * 1.day + assert_equal Date.civil(2017, 1, 3), Date.civil(2017, 1, 1) + 2 * 1.day + end + + def test_scalar_coerce + scalar = ActiveSupport::Duration::Scalar.new(10) + assert_instance_of ActiveSupport::Duration::Scalar, 10 + scalar + assert_instance_of ActiveSupport::Duration, 10.seconds + scalar + end + + def test_scalar_delegations + scalar = ActiveSupport::Duration::Scalar.new(10) + assert_kind_of Float, scalar.to_f + assert_kind_of Integer, scalar.to_i + assert_kind_of String, scalar.to_s + end + + def test_scalar_unary_minus + scalar = ActiveSupport::Duration::Scalar.new(10) + + assert_equal(-10, -scalar) + assert_instance_of ActiveSupport::Duration::Scalar, -scalar + end + + def test_scalar_compare + scalar = ActiveSupport::Duration::Scalar.new(10) + + assert_equal(1, scalar <=> 5) + assert_equal(0, scalar <=> 10) + assert_equal(-1, scalar <=> 15) + assert_equal(nil, scalar <=> "foo") + end + + def test_scalar_plus + scalar = ActiveSupport::Duration::Scalar.new(10) + + assert_equal 20, 10 + scalar + assert_instance_of ActiveSupport::Duration::Scalar, 10 + scalar + assert_equal 20, scalar + 10 + assert_instance_of ActiveSupport::Duration::Scalar, scalar + 10 + assert_equal 20, 10.seconds + scalar + assert_instance_of ActiveSupport::Duration, 10.seconds + scalar + assert_equal 20, scalar + 10.seconds + assert_instance_of ActiveSupport::Duration, scalar + 10.seconds + + exception = assert_raises(TypeError) do + scalar + "foo" + end + + assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message + end + + def test_scalar_minus + scalar = ActiveSupport::Duration::Scalar.new(10) + + assert_equal 10, 20 - scalar + assert_instance_of ActiveSupport::Duration::Scalar, 20 - scalar + assert_equal 5, scalar - 5 + assert_instance_of ActiveSupport::Duration::Scalar, scalar - 5 + assert_equal 10, 20.seconds - scalar + assert_instance_of ActiveSupport::Duration, 20.seconds - scalar + assert_equal 5, scalar - 5.seconds + assert_instance_of ActiveSupport::Duration, scalar - 5.seconds + + exception = assert_raises(TypeError) do + scalar - "foo" + end + + assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message + end + + def test_scalar_multiply + scalar = ActiveSupport::Duration::Scalar.new(5) + + assert_equal 10, 2 * scalar + assert_instance_of ActiveSupport::Duration::Scalar, 2 * scalar + assert_equal 10, scalar * 2 + assert_instance_of ActiveSupport::Duration::Scalar, scalar * 2 + assert_equal 10, 2.seconds * scalar + assert_instance_of ActiveSupport::Duration, 2.seconds * scalar + assert_equal 10, scalar * 2.seconds + assert_instance_of ActiveSupport::Duration, scalar * 2.seconds + + exception = assert_raises(TypeError) do + scalar * "foo" + end + + assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message + end + + def test_scalar_divide + scalar = ActiveSupport::Duration::Scalar.new(10) + + assert_equal 10, 100 / scalar + assert_instance_of ActiveSupport::Duration::Scalar, 100 / scalar + assert_equal 5, scalar / 2 + assert_instance_of ActiveSupport::Duration::Scalar, scalar / 2 + assert_equal 10, 100.seconds / scalar + assert_instance_of ActiveSupport::Duration, 2.seconds * scalar + assert_equal 5, scalar / 2.seconds + assert_instance_of ActiveSupport::Duration, scalar / 2.seconds + + exception = assert_raises(TypeError) do + scalar / "foo" + end + + assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message + end + + def test_twelve_months_equals_one_year + assert_equal 12.months, 1.year + end + + def test_thirty_days_does_not_equal_one_month + assert_not_equal 30.days, 1.month + end + + def test_adding_one_month_maintains_day_of_month + (1..11).each do |month| + [1, 14, 28].each do |day| + assert_equal Date.civil(2016, month + 1, day), Date.civil(2016, month, day) + 1.month + end + end + + assert_equal Date.civil(2017, 1, 1), Date.civil(2016, 12, 1) + 1.month + assert_equal Date.civil(2017, 1, 14), Date.civil(2016, 12, 14) + 1.month + assert_equal Date.civil(2017, 1, 28), Date.civil(2016, 12, 28) + 1.month + + assert_equal Date.civil(2015, 2, 28), Date.civil(2015, 1, 31) + 1.month + assert_equal Date.civil(2016, 2, 29), Date.civil(2016, 1, 31) + 1.month + end + # ISO8601 string examples are taken from ISO8601 gem at https://github.com/arnau/ISO8601/blob/b93d466840/spec/iso8601/duration_spec.rb # published under the conditions of MIT license at https://github.com/arnau/ISO8601/blob/b93d466840/LICENSE # @@ -345,6 +525,32 @@ class DurationTest < ActiveSupport::TestCase end end + def test_iso8601_parsing_equivalence_with_numeric_extensions_over_long_periods + with_env_tz eastern_time_zone do + with_tz_default "Eastern Time (US & Canada)" do + assert_equal 3.months, ActiveSupport::Duration.parse("P3M") + assert_equal 3.months.to_i, ActiveSupport::Duration.parse("P3M").to_i + assert_equal 10.months, ActiveSupport::Duration.parse("P10M") + assert_equal 10.months.to_i, ActiveSupport::Duration.parse("P10M").to_i + assert_equal 3.years, ActiveSupport::Duration.parse("P3Y") + assert_equal 3.years.to_i, ActiveSupport::Duration.parse("P3Y").to_i + assert_equal 10.years, ActiveSupport::Duration.parse("P10Y") + assert_equal 10.years.to_i, ActiveSupport::Duration.parse("P10Y").to_i + end + end + end + + def test_adding_durations_do_not_hold_prior_states + time = Time.parse("Nov 29, 2016") + # If the implementation adds and subtracts 3 months, the + # resulting date would have been in February so the day will + # change to the 29th. + d1 = 3.months - 3.months + d2 = 2.months - 2.months + + assert_equal time + d1, time + d2 + end + private def eastern_time_zone if Gem.win_platform? diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 5b839f1032..4f1ab993b8 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -172,7 +172,7 @@ class EnumerableTests < ActiveSupport::TestCase payments.index_by(&:price)) assert_equal Enumerator, payments.index_by.class if Enumerator.method_defined? :size - assert_equal nil, payments.index_by.size + assert_nil payments.index_by.size assert_equal 42, (1..42).index_by.size end assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) }, diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index d35fa491a2..525ea08abd 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -8,6 +8,8 @@ require "active_support/core_ext/object/deep_dup" require "active_support/inflections" class HashExtTest < ActiveSupport::TestCase + HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess + class IndifferentHash < ActiveSupport::HashWithIndifferentAccess end @@ -111,7 +113,7 @@ class HashExtTest < ActiveSupport::TestCase transformed_hash = @mixed.dup transformed_hash.transform_keys! { |key| key.to_s.upcase } assert_equal @upcase_strings, transformed_hash - assert_equal @mixed, :a => 1, "b" => 2 + assert_equal({ :a => 1, "b" => 2 }, @mixed) end def test_deep_transform_keys! @@ -127,7 +129,7 @@ class HashExtTest < ActiveSupport::TestCase transformed_hash = @nested_mixed.deep_dup transformed_hash.deep_transform_keys! { |key| key.to_s.upcase } assert_equal @nested_upcase_strings, transformed_hash - assert_equal @nested_mixed, "a" => { b: { "c" => 3 } } + assert_equal({ "a" => { b: { "c" => 3 } } }, @nested_mixed) end def test_symbolize_keys @@ -167,7 +169,7 @@ class HashExtTest < ActiveSupport::TestCase transformed_hash = @mixed.dup transformed_hash.deep_symbolize_keys! assert_equal @symbols, transformed_hash - assert_equal @mixed, :a => 1, "b" => 2 + assert_equal({ :a => 1, "b" => 2 }, @mixed) end def test_deep_symbolize_keys! @@ -183,7 +185,7 @@ class HashExtTest < ActiveSupport::TestCase transformed_hash = @nested_mixed.deep_dup transformed_hash.deep_symbolize_keys! assert_equal @nested_symbols, transformed_hash - assert_equal @nested_mixed, "a" => { b: { "c" => 3 } } + assert_equal({ "a" => { b: { "c" => 3 } } }, @nested_mixed) end def test_symbolize_keys_preserves_keys_that_cant_be_symbolized @@ -243,7 +245,7 @@ class HashExtTest < ActiveSupport::TestCase transformed_hash = @mixed.dup transformed_hash.stringify_keys! assert_equal @strings, transformed_hash - assert_equal @mixed, :a => 1, "b" => 2 + assert_equal({ :a => 1, "b" => 2 }, @mixed) end def test_deep_stringify_keys! @@ -259,7 +261,7 @@ class HashExtTest < ActiveSupport::TestCase transformed_hash = @nested_mixed.deep_dup transformed_hash.deep_stringify_keys! assert_equal @nested_strings, transformed_hash - assert_equal @nested_mixed, "a" => { b: { "c" => 3 } } + assert_equal({ "a" => { b: { "c" => 3 } } }, @nested_mixed) end def test_symbolize_keys_for_hash_with_indifferent_access @@ -390,8 +392,8 @@ class HashExtTest < ActiveSupport::TestCase assert_equal 1, hash[:a] assert_equal true, hash[:b] assert_equal false, hash[:c] - assert_equal nil, hash[:d] - assert_equal nil, hash[:e] + assert_nil hash[:d] + assert_nil hash[:e] end def test_indifferent_reading_with_nonnil_default @@ -404,7 +406,7 @@ class HashExtTest < ActiveSupport::TestCase assert_equal 1, hash[:a] assert_equal true, hash[:b] assert_equal false, hash[:c] - assert_equal nil, hash[:d] + assert_nil hash[:d] assert_equal 1, hash[:e] end @@ -414,11 +416,11 @@ class HashExtTest < ActiveSupport::TestCase hash["b"] = 2 hash[3] = 3 - assert_equal hash["a"], 1 - assert_equal hash["b"], 2 - assert_equal hash[:a], 1 - assert_equal hash[:b], 2 - assert_equal hash[3], 3 + assert_equal 1, hash["a"] + assert_equal 2, hash["b"] + assert_equal 1, hash[:a] + assert_equal 2, hash[:b] + assert_equal 3, hash[3] end def test_indifferent_update @@ -430,16 +432,16 @@ class HashExtTest < ActiveSupport::TestCase updated_with_symbols = hash.update(@symbols) updated_with_mixed = hash.update(@mixed) - assert_equal updated_with_strings[:a], 1 - assert_equal updated_with_strings["a"], 1 - assert_equal updated_with_strings["b"], 2 + assert_equal 1, updated_with_strings[:a] + assert_equal 1, updated_with_strings["a"] + assert_equal 2, updated_with_strings["b"] - assert_equal updated_with_symbols[:a], 1 - assert_equal updated_with_symbols["b"], 2 - assert_equal updated_with_symbols[:b], 2 + assert_equal 1, updated_with_symbols[:a] + assert_equal 2, updated_with_symbols["b"] + assert_equal 2, updated_with_symbols[:b] - assert_equal updated_with_mixed[:a], 1 - assert_equal updated_with_mixed["b"], 2 + assert_equal 1, updated_with_mixed[:a] + assert_equal 2, updated_with_mixed["b"] assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 } end @@ -447,7 +449,7 @@ class HashExtTest < ActiveSupport::TestCase def test_update_with_to_hash_conversion hash = HashWithIndifferentAccess.new hash.update HashByConversion.new(a: 1) - assert_equal hash["a"], 1 + assert_equal 1, hash["a"] end def test_indifferent_merging @@ -472,7 +474,7 @@ class HashExtTest < ActiveSupport::TestCase def test_merge_with_to_hash_conversion hash = HashWithIndifferentAccess.new merged = hash.merge HashByConversion.new(a: 1) - assert_equal merged["a"], 1 + assert_equal 1, merged["a"] end def test_indifferent_replace @@ -536,11 +538,11 @@ class HashExtTest < ActiveSupport::TestCase def test_indifferent_deleting get_hash = proc { { a: "foo" }.with_indifferent_access } hash = get_hash.call - assert_equal hash.delete(:a), "foo" - assert_equal hash.delete(:a), nil + assert_equal "foo", hash.delete(:a) + assert_nil hash.delete(:a) hash = get_hash.call - assert_equal hash.delete("a"), "foo" - assert_equal hash.delete("a"), nil + assert_equal "foo", hash.delete("a") + assert_nil hash.delete("a") end def test_indifferent_select @@ -589,6 +591,26 @@ class HashExtTest < ActiveSupport::TestCase assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings end + def test_indifferent_compact + hash_contain_nil_value = @strings.merge("z" => nil) + hash = ActiveSupport::HashWithIndifferentAccess.new(hash_contain_nil_value) + compacted_hash = hash.compact + + assert_equal(@strings, compacted_hash) + assert_equal(hash_contain_nil_value, hash) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, compacted_hash + + empty_hash = ActiveSupport::HashWithIndifferentAccess.new + compacted_hash = empty_hash.compact + + assert_equal compacted_hash, empty_hash + + non_empty_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: :bar) + compacted_hash = non_empty_hash.compact + + assert_equal compacted_hash, non_empty_hash + end + def test_indifferent_to_hash # Should convert to a Hash with String keys. assert_equal @strings, @mixed.with_indifferent_access.to_hash @@ -933,8 +955,8 @@ class HashExtTest < ActiveSupport::TestCase extracted = original.extract!(:a, :x) assert_equal expected, extracted - assert_equal nil, extracted[:a] - assert_equal nil, extracted[:x] + assert_nil extracted[:a] + assert_nil extracted[:x] end def test_indifferent_extract @@ -1017,7 +1039,7 @@ class HashExtTest < ActiveSupport::TestCase assert_equal({}, h) h = @symbols.dup - assert_equal(nil, h.compact!) + assert_nil(h.compact!) assert_equal(@symbols, h) end @@ -1051,13 +1073,6 @@ class HashExtTest < ActiveSupport::TestCase assert_nothing_raised { hash.to_hash } end - def test_new_from_hash_copying_default_should_not_raise_when_default_proc_does - hash = Hash.new - hash.default_proc = proc { |h, k| raise "walrus" } - - assert_deprecated { HashWithIndifferentAccess.new_from_hash_copying_default(hash) } - end - def test_new_with_to_hash_conversion_copies_default normal_hash = Hash.new(3) normal_hash[:a] = 1 @@ -1075,6 +1090,30 @@ class HashExtTest < ActiveSupport::TestCase assert_equal 1, hash[:a] assert_equal 3, hash[:b] end + + def test_inheriting_from_top_level_hash_with_indifferent_access_preserves_ancestors_chain + klass = Class.new(::HashWithIndifferentAccess) + assert_equal ActiveSupport::HashWithIndifferentAccess, klass.ancestors[1] + end + + def test_inheriting_from_hash_with_indifferent_access_properly_dumps_ivars + klass = Class.new(::HashWithIndifferentAccess) do + def initialize(*) + @foo = "bar" + super + end + end + + yaml_output = klass.new.to_yaml + + # `hash-with-ivars` was introduced in 2.0.9 (https://git.io/vyUQW) + if Gem::Version.new(Psych::VERSION) >= Gem::Version.new("2.0.9") + assert_includes yaml_output, "hash-with-ivars" + assert_includes yaml_output, "@foo: bar" + else + assert_includes yaml_output, "hash" + end + end end class IWriteMyOwnXML @@ -1120,6 +1159,8 @@ class HashExtToParamTests < ActiveSupport::TestCase end class HashToXmlTest < ActiveSupport::TestCase + HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess + def setup @xml_options = { root: :person, skip_instruct: true, indent: 0 } end @@ -1602,7 +1643,7 @@ class HashToXmlTest < ActiveSupport::TestCase def test_should_return_nil_if_no_key_is_supplied hash_wia = HashWithIndifferentAccess.new { 1 + 2 } - assert_equal nil, hash_wia.default + assert_nil hash_wia.default end def test_should_use_default_value_for_unknown_key diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb index db0008b735..26f5088ede 100644 --- a/activesupport/test/core_ext/kernel_test.rb +++ b/activesupport/test/core_ext/kernel_test.rb @@ -49,19 +49,3 @@ class KernelSuppressTest < ActiveSupport::TestCase suppress(LoadError, ArgumentError) { raise ArgumentError } end end - -class MockStdErr - attr_reader :output - def puts(message) - @output ||= [] - @output << message - end - - def info(message) - puts(message) - end - - def write(message) - puts(message) - end -end diff --git a/activesupport/test/core_ext/load_error_test.rb b/activesupport/test/core_ext/load_error_test.rb index b50a35b2cb..44ff6bb051 100644 --- a/activesupport/test/core_ext/load_error_test.rb +++ b/activesupport/test/core_ext/load_error_test.rb @@ -1,14 +1,6 @@ require "abstract_unit" require "active_support/core_ext/load_error" -class TestMissingSourceFile < ActiveSupport::TestCase - def test_it_is_deprecated - assert_deprecated do - MissingSourceFile.new - end - end -end - class TestLoadError < ActiveSupport::TestCase def test_with_require assert_raise(LoadError) { require 'no_this_file_don\'t_exist' } diff --git a/activesupport/test/core_ext/marshal_test.rb b/activesupport/test/core_ext/marshal_test.rb index a899f98705..cabeed2fae 100644 --- a/activesupport/test/core_ext/marshal_test.rb +++ b/activesupport/test/core_ext/marshal_test.rb @@ -19,6 +19,19 @@ class MarshalTest < ActiveSupport::TestCase end end + test "that Marshal#load still works when passed a proc" do + example_string = "test" + + example_proc = Proc.new do |o| + if o.is_a?(String) + o.capitalize! + end + end + + dumped = Marshal.dump(example_string) + assert_equal Marshal.load(dumped, example_proc), "Test" + end + test "that a missing class is autoloaded from string" do dumped = nil with_autoloading_fixtures do 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 b816fa50e3..af240bc38d 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 @@ -121,11 +121,11 @@ class ModuleAttributeAccessorPerThreadTest < ActiveSupport::TestCase def test_should_not_affect_superclass_if_subclass_set_value @class.foo = "super" - assert_equal @class.foo, "super" + assert_equal "super", @class.foo assert_nil @subclass.foo @subclass.foo = "sub" - assert_equal @class.foo, "super" - assert_equal @subclass.foo, "sub" + assert_equal "super", @class.foo + assert_equal "sub", @subclass.foo end end diff --git a/activesupport/test/core_ext/module/attribute_aliasing_test.rb b/activesupport/test/core_ext/module/attribute_aliasing_test.rb index d8c2dfd6b8..fdfa868851 100644 --- a/activesupport/test/core_ext/module/attribute_aliasing_test.rb +++ b/activesupport/test/core_ext/module/attribute_aliasing_test.rb @@ -52,8 +52,8 @@ class AttributeAliasingTest < ActiveSupport::TestCase assert_equal "No, really, this is not a joke.", e.Data assert e.Data? - e.Data = "Uppercased methods are teh suck" - assert_equal "Uppercased methods are teh suck", e.body + e.Data = "Uppercased methods are the suck" + assert_equal "Uppercased methods are the suck", e.body assert e.body? end end diff --git a/activesupport/test/core_ext/module/introspection_test.rb b/activesupport/test/core_ext/module/introspection_test.rb new file mode 100644 index 0000000000..db383850cd --- /dev/null +++ b/activesupport/test/core_ext/module/introspection_test.rb @@ -0,0 +1,37 @@ +require "abstract_unit" +require "active_support/core_ext/module/introspection" + +module ParentA + module B + module C; end + module FrozenC; end + FrozenC.freeze + end + + module FrozenB; end + FrozenB.freeze +end + +class IntrospectionTest < ActiveSupport::TestCase + def test_parent_name + assert_equal "ParentA", ParentA::B.parent_name + assert_equal "ParentA::B", ParentA::B::C.parent_name + assert_nil ParentA.parent_name + end + + def test_parent_name_when_frozen + assert_equal "ParentA", ParentA::FrozenB.parent_name + assert_equal "ParentA::B", ParentA::B::FrozenC.parent_name + end + + def test_parent + assert_equal ParentA::B, ParentA::B::C.parent + assert_equal ParentA, ParentA::B.parent + assert_equal Object, ParentA.parent + end + + def test_parents + assert_equal [ParentA::B, ParentA, Object], ParentA::B::C.parents + assert_equal [ParentA, Object], ParentA::B.parents + end +end diff --git a/activesupport/test/core_ext/module/qualified_const_test.rb b/activesupport/test/core_ext/module/qualified_const_test.rb deleted file mode 100644 index 418bc80ab9..0000000000 --- a/activesupport/test/core_ext/module/qualified_const_test.rb +++ /dev/null @@ -1,118 +0,0 @@ -require "abstract_unit" -require "active_support/core_ext/module/qualified_const" - -module QualifiedConstTestMod - X = false - - module M - X = 1 - - class C - X = 2 - end - end - - module N - include M - end -end - -class QualifiedConstTest < ActiveSupport::TestCase - test "Object.qualified_const_defined?" do - assert_deprecated do - assert Object.qualified_const_defined?("QualifiedConstTestMod") - assert !Object.qualified_const_defined?("NonExistingQualifiedConstTestMod") - - assert Object.qualified_const_defined?("QualifiedConstTestMod::X") - assert !Object.qualified_const_defined?("QualifiedConstTestMod::Y") - - assert Object.qualified_const_defined?("QualifiedConstTestMod::M::X") - assert !Object.qualified_const_defined?("QualifiedConstTestMod::M::Y") - - if Module.method(:const_defined?).arity == 1 - assert !Object.qualified_const_defined?("QualifiedConstTestMod::N::X") - else - assert Object.qualified_const_defined?("QualifiedConstTestMod::N::X") - assert !Object.qualified_const_defined?("QualifiedConstTestMod::N::X", false) - assert Object.qualified_const_defined?("QualifiedConstTestMod::N::X", true) - end - end - end - - test "mod.qualified_const_defined?" do - assert_deprecated do - assert QualifiedConstTestMod.qualified_const_defined?("M") - assert !QualifiedConstTestMod.qualified_const_defined?("NonExistingM") - - assert QualifiedConstTestMod.qualified_const_defined?("M::X") - assert !QualifiedConstTestMod.qualified_const_defined?("M::Y") - - assert QualifiedConstTestMod.qualified_const_defined?("M::C::X") - assert !QualifiedConstTestMod.qualified_const_defined?("M::C::Y") - - if Module.method(:const_defined?).arity == 1 - assert !QualifiedConstTestMod.qualified_const_defined?("QualifiedConstTestMod::N::X") - else - assert QualifiedConstTestMod.qualified_const_defined?("N::X") - assert !QualifiedConstTestMod.qualified_const_defined?("N::X", false) - assert QualifiedConstTestMod.qualified_const_defined?("N::X", true) - end - end - end - - test "qualified_const_get" do - assert_deprecated do - assert_equal false, Object.qualified_const_get("QualifiedConstTestMod::X") - assert_equal false, QualifiedConstTestMod.qualified_const_get("X") - assert_equal 1, QualifiedConstTestMod.qualified_const_get("M::X") - assert_equal 1, QualifiedConstTestMod.qualified_const_get("N::X") - assert_equal 2, QualifiedConstTestMod.qualified_const_get("M::C::X") - - assert_raise(NameError) { QualifiedConstTestMod.qualified_const_get("M::C::Y") } - end - end - - test "qualified_const_set" do - assert_deprecated do - begin - m = Module.new - assert_equal m, Object.qualified_const_set("QualifiedConstTestMod2", m) - assert_equal m, ::QualifiedConstTestMod2 - - # We are going to assign to existing constants on purpose, so silence warnings. - silence_warnings do - assert_equal true, QualifiedConstTestMod.qualified_const_set("QualifiedConstTestMod::X", true) - assert_equal true, QualifiedConstTestMod::X - - assert_equal 10, QualifiedConstTestMod::M.qualified_const_set("X", 10) - assert_equal 10, QualifiedConstTestMod::M::X - end - ensure - silence_warnings do - QualifiedConstTestMod.qualified_const_set("QualifiedConstTestMod::X", false) - QualifiedConstTestMod::M.qualified_const_set("X", 1) - end - end - end - end - - test "reject absolute paths" do - assert_deprecated do - assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X") } - assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X::Y") } - - assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X") } - assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X::Y") } - - assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X", nil) } - assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X::Y", nil) } - end - end - - private - - def assert_raise_with_message(expected_exception, expected_message, &block) - exception = assert_raise(expected_exception, &block) - assert_equal expected_message, exception.message - end -end diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index e1e5236e65..a17438bf4d 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -1,30 +1,11 @@ require "abstract_unit" require "active_support/core_ext/module" -module One - Constant1 = "Hello World" - Constant2 = "What's up?" -end - -class Ab - include One - Constant1 = "Hello World" # Will have different object id than One::Constant1 - Constant3 = "Goodbye World" -end - -module Yz - module Zy - class Cd - include One - end - end -end - Somewhere = Struct.new(:street, :city) do attr_accessor :name end -class Someone < Struct.new(:name, :place) +Someone = Struct.new(:name, :place) do delegate :street, :city, :to_f, to: :place delegate :name=, to: :place, prefix: true delegate :upcase, to: "place.city" @@ -35,10 +16,10 @@ class Someone < Struct.new(:name, :place) "some_table" end - FAILED_DELEGATE_LINE = __LINE__ + 1 + self::FAILED_DELEGATE_LINE = __LINE__ + 1 delegate :foo, to: :place - FAILED_DELEGATE_LINE_2 = __LINE__ + 1 + self::FAILED_DELEGATE_LINE_2 = __LINE__ + 1 delegate :bar, to: :place, allow_nil: true private @@ -210,21 +191,21 @@ class ModuleTest < ActiveSupport::TestCase def test_delegation_prefix invoice = Invoice.new(@david) - assert_equal invoice.client_name, "David" - assert_equal invoice.client_street, "Paulina" - assert_equal invoice.client_city, "Chicago" + assert_equal "David", invoice.client_name + assert_equal "Paulina", invoice.client_street + assert_equal "Chicago", invoice.client_city end def test_delegation_custom_prefix invoice = Invoice.new(@david) - assert_equal invoice.customer_name, "David" - assert_equal invoice.customer_street, "Paulina" - assert_equal invoice.customer_city, "Chicago" + assert_equal "David", invoice.customer_name + assert_equal "Paulina", invoice.customer_street + assert_equal "Chicago", invoice.customer_city end def test_delegation_prefix_with_nil_or_false - assert_equal Developer.new(@david).name, "David" - assert_equal Tester.new(@david).name, "David" + assert_equal "David", Developer.new(@david).name + assert_equal "David", Tester.new(@david).name end def test_delegation_prefix_with_instance_variable @@ -240,7 +221,7 @@ class ModuleTest < ActiveSupport::TestCase def test_delegation_with_allow_nil rails = Project.new("Rails", Someone.new("David")) - assert_equal rails.name, "David" + assert_equal "David", rails.name end def test_delegation_with_allow_nil_and_nil_value @@ -375,242 +356,6 @@ class ModuleTest < ActiveSupport::TestCase assert_match(/undefined method `my_fake_method' for/, e.message) end - def test_parent - assert_equal Yz::Zy, Yz::Zy::Cd.parent - assert_equal Yz, Yz::Zy.parent - assert_equal Object, Yz.parent - end - - def test_parents - assert_equal [Yz::Zy, Yz, Object], Yz::Zy::Cd.parents - assert_equal [Yz, Object], Yz::Zy.parents - end - - def test_local_constants - ActiveSupport::Deprecation.silence do - assert_equal %w(Constant1 Constant3), Ab.local_constants.sort.map(&:to_s) - end - end - - def test_local_constants_is_deprecated - assert_deprecated { Ab.local_constants.sort.map(&:to_s) } - end -end - -module BarMethodAliaser - def self.included(foo_class) - foo_class.class_eval do - include BarMethods - alias_method_chain :bar, :baz - end - end -end - -module BarMethods - def bar_with_baz - bar_without_baz << "_with_baz" - end - - def quux_with_baz! - quux_without_baz! << "_with_baz" - end - - def quux_with_baz? - false - end - - def quux_with_baz=(v) - send(:quux_without_baz=, v) << "_with_baz" - end - - def duck_with_orange - duck_without_orange << "_with_orange" - end -end - -class MethodAliasingTest < ActiveSupport::TestCase - def setup - Object.const_set :FooClassWithBarMethod, Class.new { def bar() "bar" end } - @instance = FooClassWithBarMethod.new - end - - def teardown - Object.instance_eval { remove_const :FooClassWithBarMethod } - end - - def test_alias_method_chain_deprecated - assert_deprecated(/alias_method_chain/) do - Module.new do - def base - end - - def base_with_deprecated - end - - alias_method_chain :base, :deprecated - end - end - end - - def test_alias_method_chain - assert_deprecated(/alias_method_chain/) do - assert @instance.respond_to?(:bar) - feature_aliases = [:bar_with_baz, :bar_without_baz] - - feature_aliases.each do |method| - assert !@instance.respond_to?(method) - end - - assert_equal "bar", @instance.bar - - FooClassWithBarMethod.class_eval { include BarMethodAliaser } - - feature_aliases.each do |method| - assert_respond_to @instance, method - end - - assert_equal "bar_with_baz", @instance.bar - assert_equal "bar", @instance.bar_without_baz - end - end - - def test_alias_method_chain_with_punctuation_method - assert_deprecated(/alias_method_chain/) do - FooClassWithBarMethod.class_eval do - def quux!; "quux" end - end - - assert !@instance.respond_to?(:quux_with_baz!) - FooClassWithBarMethod.class_eval do - include BarMethodAliaser - alias_method_chain :quux!, :baz - end - assert_respond_to @instance, :quux_with_baz! - - assert_equal "quux_with_baz", @instance.quux! - assert_equal "quux", @instance.quux_without_baz! - end - end - - def test_alias_method_chain_with_same_names_between_predicates_and_bang_methods - assert_deprecated(/alias_method_chain/) do - FooClassWithBarMethod.class_eval do - def quux!; "quux!" end - def quux?; true end - def quux=(v); "quux=" end - end - - assert !@instance.respond_to?(:quux_with_baz!) - assert !@instance.respond_to?(:quux_with_baz?) - assert !@instance.respond_to?(:quux_with_baz=) - - FooClassWithBarMethod.class_eval { include BarMethodAliaser } - assert_respond_to @instance, :quux_with_baz! - assert_respond_to @instance, :quux_with_baz? - assert_respond_to @instance, :quux_with_baz= - - FooClassWithBarMethod.alias_method_chain :quux!, :baz - assert_equal "quux!_with_baz", @instance.quux! - assert_equal "quux!", @instance.quux_without_baz! - - FooClassWithBarMethod.alias_method_chain :quux?, :baz - assert_equal false, @instance.quux? - assert_equal true, @instance.quux_without_baz? - - FooClassWithBarMethod.alias_method_chain :quux=, :baz - assert_equal "quux=_with_baz", @instance.send(:quux=, 1234) - assert_equal "quux=", @instance.send(:quux_without_baz=, 1234) - end - end - - def test_alias_method_chain_with_feature_punctuation - assert_deprecated(/alias_method_chain/) do - FooClassWithBarMethod.class_eval do - def quux; "quux" end - def quux?; "quux?" end - include BarMethodAliaser - alias_method_chain :quux, :baz! - end - - assert_nothing_raised do - assert_equal "quux_with_baz", @instance.quux_with_baz! - end - - assert_raise(NameError) do - FooClassWithBarMethod.alias_method_chain :quux?, :baz! - end - end - end - - def test_alias_method_chain_yields_target_and_punctuation - assert_deprecated(/alias_method_chain/) do - args = nil - - FooClassWithBarMethod.class_eval do - def quux?; end - include BarMethods - - FooClassWithBarMethod.alias_method_chain :quux?, :baz do |target, punctuation| - args = [target, punctuation] - end - end - - assert_not_nil args - assert_equal "quux", args[0] - assert_equal "?", args[1] - end - end - - def test_alias_method_chain_preserves_private_method_status - assert_deprecated(/alias_method_chain/) do - FooClassWithBarMethod.class_eval do - def duck; "duck" end - include BarMethodAliaser - private :duck - alias_method_chain :duck, :orange - end - - assert_raise NoMethodError do - @instance.duck - end - - assert_equal "duck_with_orange", @instance.instance_eval { duck } - assert FooClassWithBarMethod.private_method_defined?(:duck) - end - end - - def test_alias_method_chain_preserves_protected_method_status - assert_deprecated(/alias_method_chain/) do - FooClassWithBarMethod.class_eval do - def duck; "duck" end - include BarMethodAliaser - protected :duck - alias_method_chain :duck, :orange - end - - assert_raise NoMethodError do - @instance.duck - end - - assert_equal "duck_with_orange", @instance.instance_eval { duck } - assert FooClassWithBarMethod.protected_method_defined?(:duck) - end - end - - def test_alias_method_chain_preserves_public_method_status - assert_deprecated(/alias_method_chain/) do - FooClassWithBarMethod.class_eval do - def duck; "duck" end - include BarMethodAliaser - public :duck - alias_method_chain :duck, :orange - end - - assert_equal "duck_with_orange", @instance.duck - assert FooClassWithBarMethod.public_method_defined?(:duck) - end - end - def test_delegate_with_case event = Event.new(Tester.new) assert_equal 1, event.foo diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb index dfc9bc24c7..3cfbe6e7e6 100644 --- a/activesupport/test/core_ext/numeric_ext_test.rb +++ b/activesupport/test/core_ext/numeric_ext_test.rb @@ -12,7 +12,7 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase 10.minutes => 600, 1.hour + 15.minutes => 4500, 2.days + 4.hours + 30.minutes => 189000, - 5.years + 1.month + 1.fortnight => 161589600 + 5.years + 1.month + 1.fortnight => 161624106 } end @@ -61,10 +61,10 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase end def test_duration_after_conversion_is_no_longer_accurate - assert_equal 30.days.to_i.seconds.since(@now), 1.month.to_i.seconds.since(@now) - assert_equal 365.25.days.to_f.seconds.since(@now), 1.year.to_f.seconds.since(@now) - assert_equal 30.days.to_i.seconds.since(@dtnow), 1.month.to_i.seconds.since(@dtnow) - assert_equal 365.25.days.to_f.seconds.since(@dtnow), 1.year.to_f.seconds.since(@dtnow) + assert_equal (1.year / 12).to_i.seconds.since(@now), 1.month.to_i.seconds.since(@now) + assert_equal 365.2425.days.to_f.seconds.since(@now), 1.year.to_f.seconds.since(@now) + assert_equal (1.year / 12).to_i.seconds.since(@dtnow), 1.month.to_i.seconds.since(@dtnow) + assert_equal 365.2425.days.to_f.seconds.since(@dtnow), 1.year.to_f.seconds.since(@dtnow) end def test_add_one_year_to_leap_day @@ -287,21 +287,6 @@ class NumericExtFormattingTest < ActiveSupport::TestCase assert_equal "10 Bytes", 10.to_s(:human_size) end - def test_to_s__human_size_with_si_prefix - assert_deprecated do - assert_equal "3 Bytes", 3.14159265.to_s(:human_size, prefix: :si) - assert_equal "123 Bytes", 123.0.to_s(:human_size, prefix: :si) - assert_equal "123 Bytes", 123.to_s(:human_size, prefix: :si) - assert_equal "1.23 KB", 1234.to_s(:human_size, prefix: :si) - assert_equal "12.3 KB", 12345.to_s(:human_size, prefix: :si) - assert_equal "1.23 MB", 1234567.to_s(:human_size, prefix: :si) - assert_equal "1.23 GB", 1234567890.to_s(:human_size, prefix: :si) - assert_equal "1.23 TB", 1234567890123.to_s(:human_size, prefix: :si) - assert_equal "1.23 PB", 1234567890123456.to_s(:human_size, prefix: :si) - assert_equal "1.23 EB", 1234567890123456789.to_s(:human_size, prefix: :si) - end - end - def test_to_s__human_size_with_options_hash assert_equal "1.2 MB", 1234567.to_s(:human_size, precision: 2) assert_equal "3 Bytes", 3.14159265.to_s(:human_size, precision: 4) @@ -391,12 +376,6 @@ class NumericExtFormattingTest < ActiveSupport::TestCase assert_equal "1 Million", BigDecimal("1000010").to_s(:human) end - def test_to_formatted_s_is_deprecated - assert_deprecated do - 5551234.to_formatted_s(:phone) - end - end - def test_to_s_with_invalid_formatter assert_equal "123", 123.to_s(:invalid) assert_equal "2.5", 2.5.to_s(:invalid) @@ -415,6 +394,10 @@ class NumericExtFormattingTest < ActiveSupport::TestCase assert_equal "1000010.0", BigDecimal("1000010").to_s assert_equal "10000 10.0", BigDecimal("1000010").to_s("5F") + + assert_raises TypeError do + 1.to_s({}) + end end def test_in_milliseconds @@ -423,7 +406,6 @@ class NumericExtFormattingTest < ActiveSupport::TestCase # TODO: Remove positive and negative tests when we drop support to ruby < 2.3 b = 2**64 - b *= b until Bignum === b T_ZERO = b.coerce(0).first T_ONE = b.coerce(1).first diff --git a/activesupport/test/core_ext/object/blank_test.rb b/activesupport/test/core_ext/object/blank_test.rb index ab0676524e..7fd3fed042 100644 --- a/activesupport/test/core_ext/object/blank_test.rb +++ b/activesupport/test/core_ext/object/blank_test.rb @@ -15,7 +15,7 @@ class BlankTest < ActiveSupport::TestCase end BLANK = [ EmptyTrue.new, nil, false, "", " ", " \n\t \r ", " ", "\u00a0", [], {} ] - NOT = [ EmptyFalse.new, Object.new, true, 0, 1, "a", [nil], { nil => 0 } ] + NOT = [ EmptyFalse.new, Object.new, true, 0, 1, "a", [nil], { nil => 0 }, Time.now ] def test_blank BLANK.each { |v| assert_equal true, v.blank?, "#{v.inspect} should be blank" } @@ -28,7 +28,7 @@ class BlankTest < ActiveSupport::TestCase end def test_presence - BLANK.each { |v| assert_equal nil, v.presence, "#{v.inspect}.presence should return nil" } + BLANK.each { |v| assert_nil v.presence, "#{v.inspect}.presence should return nil" } NOT.each { |v| assert_equal v, v.presence, "#{v.inspect}.presence should return self" } end end diff --git a/activesupport/test/core_ext/object/deep_dup_test.rb b/activesupport/test/core_ext/object/deep_dup_test.rb index e335ec1b40..f247ee16de 100644 --- a/activesupport/test/core_ext/object/deep_dup_test.rb +++ b/activesupport/test/core_ext/object/deep_dup_test.rb @@ -6,7 +6,7 @@ class DeepDupTest < ActiveSupport::TestCase array = [1, [2, 3]] dup = array.deep_dup dup[1][2] = 4 - assert_equal nil, array[1][2] + assert_nil array[1][2] assert_equal 4, dup[1][2] end @@ -14,7 +14,7 @@ class DeepDupTest < ActiveSupport::TestCase hash = { a: { b: "b" } } dup = hash.deep_dup dup[:a][:c] = "c" - assert_equal nil, hash[:a][:c] + assert_nil hash[:a][:c] assert_equal "c", dup[:a][:c] end @@ -22,7 +22,7 @@ class DeepDupTest < ActiveSupport::TestCase array = [1, { a: 2, b: 3 } ] dup = array.deep_dup dup[1][:c] = 4 - assert_equal nil, array[1][:c] + assert_nil array[1][:c] assert_equal 4, dup[1][:c] end @@ -30,7 +30,7 @@ class DeepDupTest < ActiveSupport::TestCase hash = { a: [1, 2] } dup = hash.deep_dup dup[:a][2] = "c" - assert_equal nil, hash[:a][2] + assert_nil hash[:a][2] assert_equal "c", dup[:a][2] end diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb index 677e32db1d..68b0129980 100644 --- a/activesupport/test/core_ext/object/duplicable_test.rb +++ b/activesupport/test/core_ext/object/duplicable_test.rb @@ -4,16 +4,26 @@ require "active_support/core_ext/object/duplicable" require "active_support/core_ext/numeric/time" class DuplicableTest < ActiveSupport::TestCase - RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, method(:puts)] - ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new] - ALLOW_DUP << BigDecimal.new("4.56") + 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.new("4.56"), nil, false, true, 1, 2.3, Complex(1), Rational(1)] + elsif RUBY_VERSION >= "2.4.1" + 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.new("4.56"), nil, false, true, 1, 2.3] + elsif RUBY_VERSION >= "2.4.0" # Due to 2.4.0 bug. This elsif cannot be removed unless we drop 2.4.0 support... + RAISE_DUP = [method(:puts), Complex(1), Rational(1), "symbol_from_string".to_sym] + ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56"), nil, false, true, 1, 2.3] + else + RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, method(:puts), Complex(1), Rational(1)] + ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56")] + end def test_duplicable rubinius_skip "* Method#dup is allowed at the moment on Rubinius\n" \ "* https://github.com/rubinius/rubinius/issues/3089" RAISE_DUP.each do |v| - assert !v.duplicable? + assert !v.duplicable?, "#{ v.inspect } should not be duplicable" assert_raises(TypeError, v.class.name) { v.dup } end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 4b40503d22..41cc9888c6 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -1,5 +1,6 @@ require "date" require "abstract_unit" +require "timeout" require "inflector_test_cases" require "constantize_test_cases" @@ -152,53 +153,37 @@ class StringInflectionsTest < ActiveSupport::TestCase def test_string_parameterized_normal StringToParameterized.each do |normal, slugged| - assert_equal(normal.parameterize, slugged) + assert_equal(slugged, normal.parameterize) end end def test_string_parameterized_normal_preserve_case StringToParameterizedPreserveCase.each do |normal, slugged| - assert_equal(normal.parameterize(preserve_case: true), slugged) + assert_equal(slugged, normal.parameterize(preserve_case: true)) end end def test_string_parameterized_no_separator StringToParameterizeWithNoSeparator.each do |normal, slugged| - assert_equal(normal.parameterize(separator: ""), slugged) - end - end - - def test_string_parameterized_no_separator_deprecated - StringToParameterizeWithNoSeparator.each do |normal, slugged| - assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: ''` instead./i) do - assert_equal(normal.parameterize(""), slugged) - end + assert_equal(slugged, normal.parameterize(separator: "")) end end def test_string_parameterized_no_separator_preserve_case StringToParameterizePreserveCaseWithNoSeparator.each do |normal, slugged| - assert_equal(normal.parameterize(separator: "", preserve_case: true), slugged) + assert_equal(slugged, normal.parameterize(separator: "", preserve_case: true)) end end def test_string_parameterized_underscore StringToParameterizeWithUnderscore.each do |normal, slugged| - assert_equal(normal.parameterize(separator: "_"), slugged) - end - end - - def test_string_parameterized_underscore_deprecated - StringToParameterizeWithUnderscore.each do |normal, slugged| - assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '_'` instead./i) do - assert_equal(normal.parameterize("_"), slugged) - end + assert_equal(slugged, normal.parameterize(separator: "_")) end end def test_string_parameterized_underscore_preserve_case StringToParameterizePreserceCaseWithUnderscore.each do |normal, slugged| - assert_equal(normal.parameterize(separator: "_", preserve_case: true), slugged) + assert_equal(slugged, normal.parameterize(separator: "_", preserve_case: true)) end end @@ -238,7 +223,7 @@ class StringInflectionsTest < ActiveSupport::TestCase original = %{\u205f\u3000 A string surrounded by various unicode spaces, with tabs(\t\t), newlines(\n\n), unicode nextlines(\u0085\u0085) and many spaces( ). \u00a0\u2007} - expected = "A string surrounded by various unicode spaces, " + + expected = "A string surrounded by various unicode spaces, " \ "with tabs( ), newlines( ), unicode nextlines( ) and many spaces( )." # Make sure squish returns what we expect: @@ -299,7 +284,7 @@ class StringInflectionsTest < ActiveSupport::TestCase def test_truncate_words_with_complex_string Timeout.timeout(10) do complex_string = "aa aa aaa aa aaa aaa aaa aa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaaa aaaaa aaaaa aaaaaa aa aa aa aaa aa aaa aa aa aa aa a aaa aaa \n a aaa <<s" - assert_equal complex_string.truncate_words(80), complex_string + assert_equal complex_string, complex_string.truncate_words(80) end rescue Timeout::Error assert false @@ -355,7 +340,7 @@ class StringAccessTest < ActiveSupport::TestCase test "#at with Regex, returns the matching portion of the string" do assert_equal "lo", "hello".at(/lo/) - assert_equal nil, "hello".at(/nonexisting/) + assert_nil "hello".at(/nonexisting/) end test "#from with positive Integer, returns substring from the given position to the end" do @@ -726,14 +711,14 @@ class OutputSafetyTest < ActiveSupport::TestCase test "Prepending safe onto unsafe yields unsafe" do @string.prepend "other".html_safe assert !@string.html_safe? - assert_equal @string, "otherhello" + assert_equal "otherhello", @string end test "Prepending unsafe onto safe yields escaped safe" do other = "other".html_safe other.prepend "<foo>" assert other.html_safe? - assert_equal other, "<foo>other" + assert_equal "<foo>other", other end test "Concatting safe onto unsafe yields unsafe" do diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index c11804c3dc..bd644c8457 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -569,6 +569,11 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase Time::DATE_FORMATS.delete(:custom) end + def test_rfc3339_with_fractional_seconds + time = Time.new(1999, 12, 31, 19, 0, Rational(1, 8), -18000) + assert_equal "1999-12-31T19:00:00.125-05:00", time.rfc3339(3) + end + def test_to_date assert_equal Date.new(2005, 2, 21), Time.local(2005, 2, 21, 17, 44, 30).to_date end @@ -771,7 +776,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal 1, Time.utc(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59, 999).to_s assert_equal 0, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0).to_s assert_equal(-1, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 1, 0).to_s) - assert_equal nil, Time.utc(2000) <=> "Invalid as Time" + assert_nil Time.utc(2000) <=> "Invalid as Time" end def test_at_with_datetime @@ -910,6 +915,37 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase def test_all_year assert_equal Time.local(2011, 1, 1, 0, 0, 0)..Time.local(2011, 12, 31, 23, 59, 59, Rational(999999999, 1000)), Time.local(2011, 6, 7, 10, 10, 10).all_year end + + def test_rfc3339_parse + time = Time.rfc3339("1999-12-31T19:00:00.125-05:00") + + assert_equal 1999, time.year + assert_equal 12, time.month + assert_equal 31, time.day + assert_equal 19, time.hour + assert_equal 0, time.min + assert_equal 0, time.sec + assert_equal 125000, time.usec + assert_equal(-18000, time.utc_offset) + + exception = assert_raises(ArgumentError) do + Time.rfc3339("1999-12-31") + end + + assert_equal "invalid date", exception.message + + exception = assert_raises(ArgumentError) do + Time.rfc3339("1999-12-31T19:00:00") + end + + assert_equal "invalid date", exception.message + + exception = assert_raises(ArgumentError) do + Time.rfc3339("foobar") + end + + assert_equal "invalid date", exception.message + end end class TimeExtMarshalingTest < ActiveSupport::TestCase diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 620084f27d..c3afe68378 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -144,6 +144,16 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal "1999-12-31T19:00:00-05:00", @twz.xmlschema(nil) end + def test_iso8601_with_fractional_seconds + @twz += Rational(1, 8) + assert_equal "1999-12-31T19:00:00.125-05:00", @twz.iso8601(3) + end + + def test_rfc3339_with_fractional_seconds + @twz += Rational(1, 8) + assert_equal "1999-12-31T19:00:00.125-05:00", @twz.rfc3339(3) + end + def test_to_yaml yaml = <<-EOF.strip_heredoc --- !ruby/object:ActiveSupport::TimeWithZone @@ -421,11 +431,29 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal time, Time.at(time) end - def test_to_time - with_env_tz "US/Eastern" do - assert_equal Time, @twz.to_time.class - assert_equal Time.local(1999, 12, 31, 19), @twz.to_time - assert_equal Time.local(1999, 12, 31, 19).utc_offset, @twz.to_time.utc_offset + def test_to_time_with_preserve_timezone + with_preserve_timezone(true) do + with_env_tz "US/Eastern" do + time = @twz.to_time + + assert_equal Time, time.class + assert_equal time.object_id, @twz.to_time.object_id + assert_equal Time.local(1999, 12, 31, 19), time + assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset + end + end + end + + def test_to_time_without_preserve_timezone + with_preserve_timezone(false) do + with_env_tz "US/Eastern" do + time = @twz.to_time + + assert_equal Time, time.class + assert_equal time.object_id, @twz.to_time.object_id + assert_equal Time.local(1999, 12, 31, 19), time + assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset + end end end @@ -455,6 +483,10 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal false, ActiveSupport::TimeWithZone.new(DateTime.civil(2000), @time_zone).acts_like?(:date) end + def test_blank? + assert_not @twz.blank? + end + def test_is_a assert_kind_of Time, @twz assert_kind_of Time, @twz @@ -503,6 +535,8 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_nothing_raised do @twz.period @twz.time + @twz.to_datetime + @twz.to_time end end @@ -1057,7 +1091,7 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase Time.zone = -9.hours assert_equal ActiveSupport::TimeZone["Alaska"], Time.zone Time.zone = nil - assert_equal nil, Time.zone + assert_nil Time.zone end def test_time_zone_getter_and_setter_with_zone_default_set diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index f333a46d75..e38d4e83e5 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -121,7 +121,7 @@ class DependenciesTest < ActiveSupport::TestCase silence_warnings { require_dependency filename } assert_equal 2, $check_warnings_load_count - assert_equal nil, $checked_verbose, "After first load warnings should be left alone." + assert_nil $checked_verbose, "After first load warnings should be left alone." assert_includes ActiveSupport::Dependencies.loaded, expanded ActiveSupport::Dependencies.clear @@ -271,7 +271,8 @@ class DependenciesTest < ActiveSupport::TestCase def test_raising_discards_autoloaded_constants with_autoloading_fixtures do - assert_raises(Exception, "arbitray exception message") { RaisesArbitraryException } + e = assert_raises(Exception) { RaisesArbitraryException } + assert_equal("arbitrary exception message", e.message) assert_not defined?(A) assert_not defined?(RaisesArbitraryException) end @@ -397,14 +398,17 @@ class DependenciesTest < ActiveSupport::TestCase end end - def failing_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 } + # 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) end - ensure - remove_constants(:ModuleFolder) end def test_non_existing_const_raises_name_error_with_fully_qualified_name @@ -526,8 +530,8 @@ class DependenciesTest < ActiveSupport::TestCase def test_file_search with_loading "dependencies" do root = ActiveSupport::Dependencies.autoload_paths.first - assert_equal nil, ActiveSupport::Dependencies.search_for_file("service_three") - assert_equal nil, ActiveSupport::Dependencies.search_for_file("service_three.rb") + assert_nil ActiveSupport::Dependencies.search_for_file("service_three") + assert_nil ActiveSupport::Dependencies.search_for_file("service_three.rb") assert_equal root + "/service_one.rb", ActiveSupport::Dependencies.search_for_file("service_one") assert_equal root + "/service_one.rb", ActiveSupport::Dependencies.search_for_file("service_one.rb") end @@ -548,7 +552,6 @@ class DependenciesTest < ActiveSupport::TestCase assert_equal autoload + "/conflict.rb", ActiveSupport::Dependencies.search_for_file("conflict") end - end def test_custom_const_missing_should_work diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index 5be93f3a1a..36d1ef0849 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -35,6 +35,18 @@ class Deprecatee A = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("Deprecatee::A", "Deprecatee::B::C") end +class DeprecateeWithAccessor + include ActiveSupport::Deprecation::DeprecatedConstantAccessor + + module B + C = 1 + end + deprecate_constant "A", "DeprecateeWithAccessor::B::C" + + class NewException < StandardError; end + deprecate_constant "OldException", "DeprecateeWithAccessor::NewException" +end + class DeprecationTest < ActiveSupport::TestCase include ActiveSupport::Testing::Stream @@ -162,6 +174,17 @@ class DeprecationTest < ActiveSupport::TestCase assert_not_deprecated { assert_equal Deprecatee::B::C.class, Deprecatee::A.class } end + def test_deprecated_constant_accessor + assert_not_deprecated { DeprecateeWithAccessor::B::C } + assert_deprecated("DeprecateeWithAccessor::A") { assert_equal DeprecateeWithAccessor::B::C, DeprecateeWithAccessor::A } + end + + def test_deprecated_constant_accessor_exception + raise DeprecateeWithAccessor::NewException.new("Test") + rescue DeprecateeWithAccessor::OldException => e + assert_kind_of DeprecateeWithAccessor::NewException, e + end + def test_assert_deprecated_raises_when_method_not_deprecated assert_raises(Minitest::Assertion) { assert_deprecated { @dtc.not } } end @@ -285,6 +308,16 @@ class DeprecationTest < ActiveSupport::TestCase end end + def test_deprecated_constant_with_custom_message + deprecator = deprecator_with_messages + + klass = Class.new + klass.const_set(:OLD, ActiveSupport::Deprecation::DeprecatedConstantProxy.new("klass::OLD", "Object", deprecator, message: "foo")) + + klass::OLD.to_s + assert_match "foo", deprecator.messages.last + end + def test_deprecated_instance_variable_with_instance_deprecator deprecator = deprecator_with_messages diff --git a/activesupport/test/descendants_tracker_test_cases.rb b/activesupport/test/descendants_tracker_test_cases.rb index 09c5ce1f07..cf349d53ee 100644 --- a/activesupport/test/descendants_tracker_test_cases.rb +++ b/activesupport/test/descendants_tracker_test_cases.rb @@ -40,7 +40,7 @@ module DescendantsTrackerTestCases end end - protected + private def assert_equal_sets(expected, actual) assert_equal Set.new(expected), Set.new(actual) diff --git a/activesupport/test/evented_file_update_checker_test.rb b/activesupport/test/evented_file_update_checker_test.rb index 77d8dcb0f8..f33a5f5764 100644 --- a/activesupport/test/evented_file_update_checker_test.rb +++ b/activesupport/test/evented_file_update_checker_test.rb @@ -7,6 +7,7 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase def setup skip if ENV["LISTEN"] == "0" + require "listen" super end @@ -30,11 +31,6 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase wait # wait for the events to fire end - def rm_f(files) - super - wait - end - test "notifies forked processes" do jruby_skip "Forking not available on JRuby" @@ -43,7 +39,7 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase checker = new_checker(tmpfiles) {} assert !checker.updated? - # Pipes used for flow controll across fork. + # Pipes used for flow control across fork. boot_reader, boot_writer = IO.pipe touch_reader, touch_writer = IO.pipe diff --git a/activesupport/test/executor_test.rb b/activesupport/test/executor_test.rb index 03ec6020c3..7fefc066b3 100644 --- a/activesupport/test/executor_test.rb +++ b/activesupport/test/executor_test.rb @@ -105,7 +105,7 @@ class ExecutorTest < ActiveSupport::TestCase executor.wrap {} - assert_equal nil, supplied_state + assert_nil supplied_state end def test_exception_skips_uninvoked_hook diff --git a/activesupport/test/file_update_checker_shared_tests.rb b/activesupport/test/file_update_checker_shared_tests.rb index 48cd387196..361e7e2349 100644 --- a/activesupport/test/file_update_checker_shared_tests.rb +++ b/activesupport/test/file_update_checker_shared_tests.rb @@ -273,4 +273,10 @@ module FileUpdateCheckerSharedTests 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([]) + end + end end diff --git a/activesupport/test/gzip_test.rb b/activesupport/test/gzip_test.rb index f51d3cdf65..33e0cd2a04 100644 --- a/activesupport/test/gzip_test.rb +++ b/activesupport/test/gzip_test.rb @@ -30,4 +30,14 @@ class GzipTest < ActiveSupport::TestCase assert_equal true, (gzipped_by_best_compression.bytesize < gzipped_by_speed.bytesize) end + + def test_decompress_checks_crc + compressed = ActiveSupport::Gzip.compress("Hello World") + first_crc_byte_index = compressed.bytesize - 8 + compressed.setbyte(first_crc_byte_index, compressed.getbyte(first_crc_byte_index) ^ 0xff) + + assert_raises(Zlib::GzipFile::CRCError) do + ActiveSupport::Gzip.decompress(compressed) + end + end end diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 1c5a4f378c..03d7b3fe94 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -31,6 +31,32 @@ class InflectorTest < ActiveSupport::TestCase assert_equal "", ActiveSupport::Inflector.pluralize("") end + test "uncountability of ascii word" do + word = "HTTP" + ActiveSupport::Inflector.inflections do |inflect| + inflect.uncountable word + end + + assert_equal word, ActiveSupport::Inflector.pluralize(word) + assert_equal word, ActiveSupport::Inflector.singularize(word) + assert_equal ActiveSupport::Inflector.pluralize(word), ActiveSupport::Inflector.singularize(word) + + ActiveSupport::Inflector.inflections.uncountables.pop + end + + test "uncountability of non-ascii word" do + word = "猫" + ActiveSupport::Inflector.inflections do |inflect| + inflect.uncountable word + end + + assert_equal word, ActiveSupport::Inflector.pluralize(word) + assert_equal word, ActiveSupport::Inflector.singularize(word) + assert_equal ActiveSupport::Inflector.pluralize(word), ActiveSupport::Inflector.singularize(word) + + ActiveSupport::Inflector.inflections.uncountables.pop + end + ActiveSupport::Inflector.inflections.uncountable.each do |word| define_method "test_uncountability_of_#{word}" do assert_equal word, ActiveSupport::Inflector.singularize(word) @@ -245,58 +271,30 @@ class InflectorTest < ActiveSupport::TestCase end end - # FIXME: get following tests to pass on jruby, currently skipped - # - # Currently this fails because ActiveSupport::Multibyte::Unicode#tidy_bytes - # required a specific Encoding::Converter(UTF-8 to UTF8-MAC) which unavailable on JRuby - # causing our tests to error out. - # related bug http://jira.codehaus.org/browse/JRUBY-7194 def test_parameterize - jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" StringToParameterized.each do |some_string, parameterized_string| assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string)) end end def test_parameterize_and_normalize - jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" StringToParameterizedAndNormalized.each do |some_string, parameterized_string| assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string)) end end def test_parameterize_with_custom_separator - jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" StringToParameterizeWithUnderscore.each do |some_string, parameterized_string| assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, separator: "_")) end end - def test_parameterize_with_custom_separator_deprecated - jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" - StringToParameterizeWithUnderscore.each do |some_string, parameterized_string| - assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '_'` instead./i) do - assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, "_")) - end - end - end - def test_parameterize_with_multi_character_separator - jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" StringToParameterized.each do |some_string, parameterized_string| assert_equal(parameterized_string.gsub("-", "__sep__"), ActiveSupport::Inflector.parameterize(some_string, separator: "__sep__")) end end - def test_parameterize_with_multi_character_separator_deprecated - jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" - StringToParameterized.each do |some_string, parameterized_string| - assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '__sep__'` instead./i) do - assert_equal(parameterized_string.gsub("-", "__sep__"), ActiveSupport::Inflector.parameterize(some_string, "__sep__")) - end - end - end - def test_classify ClassNameToTableName.each do |class_name, table_name| assert_equal(class_name, ActiveSupport::Inflector.classify(table_name)) @@ -525,12 +523,4 @@ class InflectorTest < ActiveSupport::TestCase end end end - - def test_inflections_with_uncountable_words - ActiveSupport::Inflector.inflections do |inflect| - inflect.uncountable "HTTP" - end - - assert_equal "HTTP", ActiveSupport::Inflector.pluralize("HTTP") - end end diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index b660987d92..f3352e3301 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -271,6 +271,7 @@ module InflectorTestCases "¿por qué?" => "¿Por Qué?", "Fred’s" => "Fred’s", "Fred`s" => "Fred`s", + "this was 'fake news'" => "This Was 'Fake News'", ActiveSupport::SafeBuffer.new("confirmation num") => "Confirmation Num" } diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb index 6d1d8f1b95..6f5051c312 100644 --- a/activesupport/test/json/decoding_test.rb +++ b/activesupport/test/json/decoding_test.rb @@ -75,12 +75,17 @@ class TestJSONDecoding < ActiveSupport::TestCase } TESTS.each_with_index do |(json, expected), index| + fail_message = "JSON decoding failed for #{json}" + test "json decodes #{index}" do with_tz_default "Eastern Time (US & Canada)" do with_parse_json_times(true) do silence_warnings do - assert_equal expected, ActiveSupport::JSON.decode(json), "JSON decoding \ - failed for #{json}" + if expected.nil? + assert_nil ActiveSupport::JSON.decode(json), fail_message + else + assert_equal expected, ActiveSupport::JSON.decode(json), fail_message + end end end end diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index fc3af02cbc..6d8f7cfbd0 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -329,7 +329,7 @@ class TestJSONEncoding < ActiveSupport::TestCase end def test_nil_true_and_false_represented_as_themselves - assert_equal nil, nil.as_json + assert_nil nil.as_json assert_equal true, true.as_json assert_equal false, false.as_json end @@ -432,7 +432,27 @@ EXPECTED assert_equal '"foo"', ActiveSupport::JSON.encode(exception) end - protected + class InfiniteNumber + def as_json(options = nil) + { "number" => Float::INFINITY } + end + end + + def test_to_json_works_when_as_json_returns_infinite_number + assert_equal '{"number":null}', InfiniteNumber.new.to_json + end + + class NaNNumber + def as_json(options = nil) + { "number" => Float::NAN } + end + end + + def test_to_json_works_when_as_json_returns_NaN_number + assert_equal '{"number":null}', NaNNumber.new.to_json + end + + private def object_keys(json_object) json_object[1..-2].scan(/([^{}:,\s]+):/).flatten.sort diff --git a/activesupport/test/json/encoding_test_cases.rb b/activesupport/test/json/encoding_test_cases.rb index 8eac246937..7e4775cec8 100644 --- a/activesupport/test/json/encoding_test_cases.rb +++ b/activesupport/test/json/encoding_test_cases.rb @@ -1,4 +1,8 @@ require "bigdecimal" +require "date" +require "time" +require "pathname" +require "uri" module JSONTest class Foo @@ -23,7 +27,7 @@ module JSONTest end end - class MyStruct < Struct.new(:name, :value) + MyStruct = Struct.new(:name, :value) do def initialize(*) @unused = "unused instance variable" super diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb index d56a46b250..d6109c761d 100644 --- a/activesupport/test/message_verifier_test.rb +++ b/activesupport/test/message_verifier_test.rb @@ -80,6 +80,6 @@ class MessageVerifierTest < ActiveSupport::TestCase exception = assert_raise(ArgumentError) do ActiveSupport::MessageVerifier.new(nil) end - assert_equal exception.message, "Secret should not be nil." + assert_equal "Secret should not be nil.", exception.message end end diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index 2ee4a1ce68..d80d340986 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -50,7 +50,7 @@ class MultibyteCharsTest < ActiveSupport::TestCase assert_equal BYTE_STRING.length, BYTE_STRING.mb_chars.length end - def test_forwarded_method_with_non_string_result_should_be_returned_vertabim + def test_forwarded_method_with_non_string_result_should_be_returned_verbatim str = "" str.singleton_class.class_eval { def __method_for_multibyte_testing_with_integer_result; 1; end } @chars.wrapped_string.singleton_class.class_eval { def __method_for_multibyte_testing_with_integer_result; 1; end } @@ -231,7 +231,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase assert_equal 0, @chars.index("こに") assert_equal 2, @chars.index("ち") assert_equal 2, @chars.index("ち", -2) - assert_equal nil, @chars.index("ち", -1) + assert_nil @chars.index("ち", -1) assert_equal 3, @chars.index("わ") assert_equal 5, "ééxééx".mb_chars.index("x", 4) end @@ -390,11 +390,11 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase end def test_slice_should_take_character_offsets - assert_equal nil, "".mb_chars.slice(0) + assert_nil "".mb_chars.slice(0) assert_equal "こ", @chars.slice(0) assert_equal "わ", @chars.slice(3) - assert_equal nil, "".mb_chars.slice(-1..1) - assert_equal nil, "".mb_chars.slice(-1, 1) + assert_nil "".mb_chars.slice(-1..1) + assert_nil "".mb_chars.slice(-1, 1) assert_equal "", "".mb_chars.slice(0..10) assert_equal "にちわ", @chars.slice(1..3) assert_equal "にちわ", @chars.slice(1, 3) @@ -403,10 +403,10 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase assert_equal "", @chars.slice(4..10) assert_equal "に", @chars.slice(/に/u) assert_equal "にち", @chars.slice(/に./u) - assert_equal nil, @chars.slice(/unknown/u) + assert_nil @chars.slice(/unknown/u) assert_equal "にち", @chars.slice(/(にち)/u, 1) - assert_equal nil, @chars.slice(/(にち)/u, 2) - assert_equal nil, @chars.slice(7..6) + assert_nil @chars.slice(/(にち)/u, 2) + assert_nil @chars.slice(7..6) end def test_slice_bang_returns_sliced_out_substring @@ -414,7 +414,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase end def test_slice_bang_returns_nil_on_out_of_bound_arguments - assert_equal nil, @chars.mb_chars.slice!(9..10) + assert_nil @chars.mb_chars.slice!(9..10) end def test_slice_bang_removes_the_slice_from_the_receiver @@ -664,7 +664,6 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase end def test_tidy_bytes_should_tidy_bytes - single_byte_cases = { "\x21" => "!", # Valid ASCII byte, low "\x41" => "A", # Valid ASCII byte, mid diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb index bf004d7924..ef1a26135f 100644 --- a/activesupport/test/multibyte_conformance_test.rb +++ b/activesupport/test/multibyte_conformance_test.rb @@ -82,7 +82,7 @@ class MultibyteConformanceTest < ActiveSupport::TestCase end end - protected + private def each_line_of_norm_tests(&block) File.open(File.join(CACHE_DIR, UNIDATA_FILE), "r") do | f | until f.eof? diff --git a/activesupport/test/multibyte_grapheme_break_conformance_test.rb b/activesupport/test/multibyte_grapheme_break_conformance_test.rb index 04a7d290d9..b3328987ae 100644 --- a/activesupport/test/multibyte_grapheme_break_conformance_test.rb +++ b/activesupport/test/multibyte_grapheme_break_conformance_test.rb @@ -28,7 +28,7 @@ class MultibyteGraphemeBreakConformanceTest < ActiveSupport::TestCase end end - protected + private def each_line_of_break_tests(&block) lines = 0 max_test_lines = 0 # Don't limit below 21, because that's the header of the testfile diff --git a/activesupport/test/multibyte_normalization_conformance_test.rb b/activesupport/test/multibyte_normalization_conformance_test.rb index e013bd578f..ebc9f92d23 100644 --- a/activesupport/test/multibyte_normalization_conformance_test.rb +++ b/activesupport/test/multibyte_normalization_conformance_test.rb @@ -83,7 +83,7 @@ class MultibyteNormalizationConformanceTest < ActiveSupport::TestCase end end - protected + private def each_line_of_norm_tests(&block) lines = 0 max_test_lines = 0 # Don't limit below 38, because that's the header of the testfile diff --git a/activesupport/test/multibyte_test_helpers.rb b/activesupport/test/multibyte_test_helpers.rb index 2201860d8a..a70516bb08 100644 --- a/activesupport/test/multibyte_test_helpers.rb +++ b/activesupport/test/multibyte_test_helpers.rb @@ -18,7 +18,7 @@ module MultibyteTestHelpers end UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd" - CACHE_DIR = "#{Dir.tmpdir}/cache/unicode_conformance" + CACHE_DIR = "#{Dir.tmpdir}/cache/unicode_conformance/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}" FileUtils.mkdir_p(CACHE_DIR) UNICODE_STRING = "こにちわ".freeze diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index a6f0d82e8a..11f743519f 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -273,7 +273,7 @@ module Notifications assert !not_child.parent_of?(parent) end - protected + private def random_id @random_id ||= SecureRandom.hex(10) end diff --git a/activesupport/test/number_helper_i18n_test.rb b/activesupport/test/number_helper_i18n_test.rb index 4f58e6607a..a1d1c41dc2 100644 --- a/activesupport/test/number_helper_i18n_test.rb +++ b/activesupport/test/number_helper_i18n_test.rb @@ -1,5 +1,6 @@ require "abstract_unit" require "active_support/number_helper" +require "active_support/core_ext/hash/keys" module ActiveSupport class NumberHelperI18nTest < ActiveSupport::TestCase diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb index 8f6eb91e71..dc0c34d4e2 100644 --- a/activesupport/test/number_helper_test.rb +++ b/activesupport/test/number_helper_test.rb @@ -244,23 +244,6 @@ module ActiveSupport end end - def test_number_to_human_size_with_si_prefix - assert_deprecated do - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal "3 Bytes", number_helper.number_to_human_size(3.14159265, prefix: :si) - assert_equal "123 Bytes", number_helper.number_to_human_size(123.0, prefix: :si) - assert_equal "123 Bytes", number_helper.number_to_human_size(123, prefix: :si) - assert_equal "1.23 KB", number_helper.number_to_human_size(1234, prefix: :si) - assert_equal "12.3 KB", number_helper.number_to_human_size(12345, prefix: :si) - assert_equal "1.23 MB", number_helper.number_to_human_size(1234567, prefix: :si) - assert_equal "1.23 GB", number_helper.number_to_human_size(1234567890, prefix: :si) - assert_equal "1.23 TB", number_helper.number_to_human_size(1234567890123, prefix: :si) - assert_equal "1.23 PB", number_helper.number_to_human_size(1234567890123456, prefix: :si) - assert_equal "1.23 EB", number_helper.number_to_human_size(1234567890123456789, prefix: :si) - end - end - end - def test_number_to_human_size_with_options_hash [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| assert_equal "1.2 MB", number_helper.number_to_human_size(1234567, precision: 2) diff --git a/activesupport/test/reloader_test.rb b/activesupport/test/reloader_test.rb index 67d8c4b0e3..bdd80307c7 100644 --- a/activesupport/test/reloader_test.rb +++ b/activesupport/test/reloader_test.rb @@ -2,12 +2,15 @@ require "abstract_unit" class ReloaderTest < ActiveSupport::TestCase def test_prepare_callback - prepared = false + prepared = completed = false reloader.to_prepare { prepared = true } + reloader.to_complete { completed = true } assert !prepared + assert !completed reloader.prepare! assert prepared + assert !completed prepared = false reloader.wrap do @@ -17,6 +20,15 @@ class ReloaderTest < ActiveSupport::TestCase assert !prepared end + def test_prepend_prepare_callback + i = 10 + reloader.to_prepare { i += 1 } + reloader.to_prepare(prepend: true) { i = 0 } + + reloader.prepare! + assert_equal 1, i + end + def test_only_run_when_check_passes r = new_reloader { true } invoked = false diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb index 7e5c3d1a8f..f7eb047d44 100644 --- a/activesupport/test/rescuable_test.rb +++ b/activesupport/test/rescuable_test.rb @@ -137,6 +137,6 @@ class RescuableTest < ActiveSupport::TestCase def test_rescue_falls_back_to_exception_cause @stargate.dispatch :fall_back_to_cause - assert_equal "unhandled RuntimeError with a handleable cause", @stargate.result + assert_equal "dex", @stargate.result end end diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb index e046b8d773..36c068b91f 100644 --- a/activesupport/test/safe_buffer_test.rb +++ b/activesupport/test/safe_buffer_test.rb @@ -175,6 +175,6 @@ class SafeBufferTest < ActiveSupport::TestCase test "Should not affect frozen objects when accessing characters" do x = "Hello".html_safe - assert_equal x[/a/, 1], nil + assert_nil x[/a/, 1] end end diff --git a/activesupport/test/security_utils_test.rb b/activesupport/test/security_utils_test.rb index 842bdd469d..e8f762da22 100644 --- a/activesupport/test/security_utils_test.rb +++ b/activesupport/test/security_utils_test.rb @@ -4,6 +4,11 @@ require "active_support/security_utils" class SecurityUtilsTest < ActiveSupport::TestCase def test_secure_compare_should_perform_string_comparison assert ActiveSupport::SecurityUtils.secure_compare("a", "a") - assert !ActiveSupport::SecurityUtils.secure_compare("a", "b") + assert_not ActiveSupport::SecurityUtils.secure_compare("a", "b") + end + + def test_variable_size_secure_compare_should_perform_string_comparison + assert ActiveSupport::SecurityUtils.variable_size_secure_compare("a", "a") + assert_not ActiveSupport::SecurityUtils.variable_size_secure_compare("a", "b") end end diff --git a/activesupport/test/string_inquirer_test.rb b/activesupport/test/string_inquirer_test.rb index d41e4d6800..79a715349c 100644 --- a/activesupport/test/string_inquirer_test.rb +++ b/activesupport/test/string_inquirer_test.rb @@ -20,4 +20,25 @@ class StringInquirerTest < ActiveSupport::TestCase def test_respond_to assert_respond_to @string_inquirer, :development? end + + def test_respond_to_fallback_to_string_respond_to + String.class_eval do + def respond_to_missing?(name, include_private = false) + (name == :bar) || super + end + end + str = ActiveSupport::StringInquirer.new("hello") + + assert_respond_to str, :are_you_ready? + assert_respond_to str, :bar + assert_not_respond_to str, :nope + + ensure + String.class_eval do + undef_method :respond_to_missing? + def respond_to_missing?(name, include_private = false) + super + end + end + end end diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb index d769a8c145..af7fc44d66 100644 --- a/activesupport/test/test_case_test.rb +++ b/activesupport/test/test_case_test.rb @@ -257,7 +257,7 @@ class SetupAndTeardownTest < ActiveSupport::TestCase def teardown end - protected + private def reset_callback_record @called_back = [] @@ -282,7 +282,7 @@ class SubclassSetupAndTeardownTest < SetupAndTeardownTest assert_equal [:foo, :sentinel, :bar], self.class._teardown_callbacks.map(&:raw_filter) end - protected + private def bar @called_back << :bar end diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb index c68be329bc..e0d3fb0cf5 100644 --- a/activesupport/test/time_travel_test.rb +++ b/activesupport/test/time_travel_test.rb @@ -3,6 +3,10 @@ require "active_support/core_ext/date_time" require "active_support/core_ext/numeric/time" class TimeTravelTest < ActiveSupport::TestCase + class TimeSubclass < ::Time; end + class DateSubclass < ::Date; end + class DateTimeSubclass < ::DateTime; end + def test_time_helper_travel Time.stub(:now, Time.now) do begin @@ -90,11 +94,12 @@ class TimeTravelTest < ActiveSupport::TestCase outer_expected_time = Time.new(2004, 11, 24, 01, 04, 44) inner_expected_time = Time.new(2004, 10, 24, 01, 04, 44) travel_to outer_expected_time do - assert_raises(RuntimeError, /Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing./) do + e = assert_raises(RuntimeError) do travel_to(inner_expected_time) do #noop end end + assert_match(/Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing./, e.message) end end end @@ -142,4 +147,19 @@ class TimeTravelTest < ActiveSupport::TestCase end end end + + def test_time_helper_travel_with_time_subclass + assert_equal TimeSubclass, TimeSubclass.now.class + assert_equal DateSubclass, DateSubclass.today.class + assert_equal DateTimeSubclass, DateTimeSubclass.now.class + + travel 1.day do + assert_equal TimeSubclass, TimeSubclass.now.class + assert_equal DateSubclass, DateSubclass.today.class + assert_equal DateTimeSubclass, DateTimeSubclass.now.class + assert_equal Time.now.to_s, TimeSubclass.now.to_s + assert_equal Date.today.to_s, DateSubclass.today.to_s + assert_equal DateTime.now.to_s, DateTimeSubclass.now.to_s + end + end end diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 914893ea10..de111cc40e 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -215,6 +215,95 @@ class TimeZoneTest < ActiveSupport::TestCase assert_equal secs, twz.to_f end + def test_iso8601 + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.iso8601("1999-12-31T19:00:00") + assert_equal Time.utc(1999, 12, 31, 19), twz.time + assert_equal Time.utc(2000), twz.utc + assert_equal zone, twz.time_zone + end + + def test_iso8601_with_fractional_seconds + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.iso8601("1999-12-31T19:00:00.750") + assert_equal 750000, twz.time.usec + assert_equal Time.utc(1999, 12, 31, 19, 0, 0 + Rational(3, 4)), twz.time + assert_equal Time.utc(2000, 1, 1, 0, 0, 0 + Rational(3, 4)), twz.utc + assert_equal zone, twz.time_zone + end + + def test_iso8601_with_zone + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.iso8601("1999-12-31T14:00:00-10:00") + assert_equal Time.utc(1999, 12, 31, 19), twz.time + assert_equal Time.utc(2000), twz.utc + assert_equal zone, twz.time_zone + end + + def test_iso8601_with_invalid_string + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + + exception = assert_raises(ArgumentError) do + zone.iso8601("foobar") + end + + assert_equal "invalid date", exception.message + end + + def test_iso8601_with_missing_time_components + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.iso8601("1999-12-31") + assert_equal Time.utc(1999, 12, 31, 0, 0, 0), twz.time + assert_equal Time.utc(1999, 12, 31, 5, 0, 0), twz.utc + assert_equal zone, twz.time_zone + end + + def test_iso8601_with_old_date + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.iso8601("1883-12-31T19:00:00") + assert_equal [0, 0, 19, 31, 12, 1883], twz.to_a[0, 6] + assert_equal zone, twz.time_zone + end + + def test_iso8601_far_future_date_with_time_zone_offset_in_string + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.iso8601("2050-12-31T19:00:00-10:00") # i.e., 2050-01-01 05:00:00 UTC + assert_equal [0, 0, 0, 1, 1, 2051], twz.to_a[0, 6] + assert_equal zone, twz.time_zone + end + + def test_iso8601_should_not_black_out_system_timezone_dst_jump + with_env_tz("EET") do + zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"] + twz = zone.iso8601("2012-03-25T03:29:00") + assert_equal [0, 29, 3, 25, 3, 2012], twz.to_a[0, 6] + end + end + + def test_iso8601_should_black_out_app_timezone_dst_jump + with_env_tz("EET") do + zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"] + twz = zone.iso8601("2012-03-11T02:29:00") + assert_equal [0, 29, 3, 11, 3, 2012], twz.to_a[0, 6] + end + end + + def test_iso8601_doesnt_use_local_dst + with_env_tz "US/Eastern" do + zone = ActiveSupport::TimeZone["UTC"] + twz = zone.iso8601("2013-03-10T02:00:00") + assert_equal Time.utc(2013, 3, 10, 2, 0, 0), twz.time + end + end + + def test_iso8601_handles_dst_jump + with_env_tz "US/Eastern" do + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.iso8601("2013-03-10T02:00:00") + assert_equal Time.utc(2013, 3, 10, 3, 0, 0), twz.time + end + end + def test_parse zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] twz = zone.parse("1999-12-31 19:00:00") @@ -314,6 +403,99 @@ class TimeZoneTest < ActiveSupport::TestCase end end + def test_rfc3339 + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.rfc3339("1999-12-31T14:00:00-10:00") + assert_equal Time.utc(1999, 12, 31, 19), twz.time + assert_equal Time.utc(2000), twz.utc + assert_equal zone, twz.time_zone + end + + def test_rfc3339_with_fractional_seconds + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.iso8601("1999-12-31T14:00:00.750-10:00") + assert_equal 750000, twz.time.usec + assert_equal Time.utc(1999, 12, 31, 19, 0, 0 + Rational(3, 4)), twz.time + assert_equal Time.utc(2000, 1, 1, 0, 0, 0 + Rational(3, 4)), twz.utc + assert_equal zone, twz.time_zone + end + + def test_rfc3339_with_missing_time + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + + exception = assert_raises(ArgumentError) do + zone.rfc3339("1999-12-31") + end + + assert_equal "invalid date", exception.message + end + + def test_rfc3339_with_missing_offset + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + + exception = assert_raises(ArgumentError) do + zone.rfc3339("1999-12-31T19:00:00") + end + + assert_equal "invalid date", exception.message + end + + def test_rfc3339_with_invalid_string + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + + exception = assert_raises(ArgumentError) do + zone.rfc3339("foobar") + end + + assert_equal "invalid date", exception.message + end + + def test_rfc3339_with_old_date + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.rfc3339("1883-12-31T19:00:00-05:00") + assert_equal [0, 0, 19, 31, 12, 1883], twz.to_a[0, 6] + assert_equal zone, twz.time_zone + end + + def test_rfc3339_far_future_date_with_time_zone_offset_in_string + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.rfc3339("2050-12-31T19:00:00-10:00") # i.e., 2050-01-01 05:00:00 UTC + assert_equal [0, 0, 0, 1, 1, 2051], twz.to_a[0, 6] + assert_equal zone, twz.time_zone + end + + def test_rfc3339_should_not_black_out_system_timezone_dst_jump + with_env_tz("EET") do + zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"] + twz = zone.rfc3339("2012-03-25T03:29:00-07:00") + assert_equal [0, 29, 3, 25, 3, 2012], twz.to_a[0, 6] + end + end + + def test_rfc3339_should_black_out_app_timezone_dst_jump + with_env_tz("EET") do + zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"] + twz = zone.rfc3339("2012-03-11T02:29:00-08:00") + assert_equal [0, 29, 3, 11, 3, 2012], twz.to_a[0, 6] + end + end + + def test_rfc3339_doesnt_use_local_dst + with_env_tz "US/Eastern" do + zone = ActiveSupport::TimeZone["UTC"] + twz = zone.rfc3339("2013-03-10T02:00:00Z") + assert_equal Time.utc(2013, 3, 10, 2, 0, 0), twz.time + end + end + + def test_rfc3339_handles_dst_jump + with_env_tz "US/Eastern" do + zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + twz = zone.iso8601("2013-03-10T02:00:00-05:00") + assert_equal Time.utc(2013, 3, 10, 3, 0, 0), twz.time + end + end + def test_strptime zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] twz = zone.strptime("1999-12-31 12:00:00", "%Y-%m-%d %H:%M:%S") @@ -416,7 +598,7 @@ class TimeZoneTest < ActiveSupport::TestCase def test_utc_offset_lazy_loaded_from_tzinfo_when_not_passed_in_to_initialize tzinfo = TZInfo::Timezone.get("America/New_York") zone = ActiveSupport::TimeZone.create(tzinfo.name, nil, tzinfo) - assert_equal nil, zone.instance_variable_get("@utc_offset") + assert_nil zone.instance_variable_get("@utc_offset") assert_equal(-18_000, zone.utc_offset) end @@ -532,6 +714,10 @@ class TimeZoneTest < ActiveSupport::TestCase assert_not_includes ActiveSupport::TimeZone.country_zones(:ru), ActiveSupport::TimeZone["Kuala Lumpur"] end + def test_country_zones_without_mappings + assert_includes ActiveSupport::TimeZone.country_zones(:sv), ActiveSupport::TimeZone["America/El_Salvador"] + end + def test_to_yaml assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Pacific/Honolulu\n", ActiveSupport::TimeZone["Hawaii"].to_yaml) assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Europe/London\n", ActiveSupport::TimeZone["Europe/London"].to_yaml) diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb index 040ddd25fc..466b69bcef 100644 --- a/activesupport/test/transliterate_test.rb +++ b/activesupport/test/transliterate_test.rb @@ -31,4 +31,22 @@ class TransliterateTest < ActiveSupport::TestCase def test_transliterate_should_allow_a_custom_replacement_char assert_equal "a*b", ActiveSupport::Inflector.transliterate("a索b", "*") end + + def test_transliterate_handles_empty_string + assert_equal "", ActiveSupport::Inflector.transliterate("") + end + + def test_transliterate_handles_nil + exception = assert_raises ArgumentError do + ActiveSupport::Inflector.transliterate(nil) + end + assert_equal "Can only transliterate strings. Received NilClass", exception.message + end + + def test_transliterate_handles_unknown_object + exception = assert_raises ArgumentError do + ActiveSupport::Inflector.transliterate(Object.new) + end + assert_equal "Can only transliterate strings. Received Object", exception.message + end end diff --git a/activesupport/test/xml_mini/jdom_engine_test.rb b/activesupport/test/xml_mini/jdom_engine_test.rb index 816d57972c..e783cea67c 100644 --- a/activesupport/test/xml_mini/jdom_engine_test.rb +++ b/activesupport/test/xml_mini/jdom_engine_test.rb @@ -1,37 +1,9 @@ -if RUBY_PLATFORM.include?("java") - require "abstract_unit" - require "active_support/xml_mini" - require "active_support/core_ext/hash/conversions" - - class JDOMEngineTest < ActiveSupport::TestCase - include ActiveSupport +require_relative "xml_mini_engine_test" +XMLMiniEngineTest.run_with_platform("java") do + class JDOMEngineTest < XMLMiniEngineTest FILES_DIR = File.dirname(__FILE__) + "/../fixtures/xml" - def setup - @default_backend = XmlMini.backend - XmlMini.backend = "JDOM" - end - - def teardown - XmlMini.backend = @default_backend - end - - def test_file_from_xml - hash = Hash.from_xml(<<-eoxml) - <blog> - <logo type="file" name="logo.png" content_type="image/png"> - </logo> - </blog> - eoxml - assert hash.has_key?("blog") - assert hash["blog"].has_key?("logo") - - file = hash["blog"]["logo"] - assert_equal "logo.png", file.original_filename - assert_equal "image/png", file.content_type - end - def test_not_allowed_to_expand_entities_to_files attack_xml = <<-EOT <!DOCTYPE member [ @@ -63,121 +35,17 @@ if RUBY_PLATFORM.include?("java") assert_equal "x", Hash.from_xml(attack_xml)["member"] end - def test_exception_thrown_on_expansion_attack - assert_raise Java::OrgXmlSax::SAXParseException do - attack_xml = <<-EOT - <!DOCTYPE member [ - <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"> - <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;"> - <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;"> - <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;"> - <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;"> - <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;"> - <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"> - ]> - <member> - &a; - </member> - EOT - Hash.from_xml(attack_xml) + private + def engine + "JDOM" end - end - - def test_setting_JDOM_as_backend - XmlMini.backend = "JDOM" - assert_equal XmlMini_JDOM, XmlMini.backend - end - - def test_blank_returns_empty_hash - assert_equal({}, XmlMini.parse(nil)) - assert_equal({}, XmlMini.parse("")) - end - - def test_array_type_makes_an_array - assert_equal_rexml(<<-eoxml) - <blog> - <posts type="array"> - <post>a post</post> - <post>another post</post> - </posts> - </blog> - eoxml - end - - def test_one_node_document_as_hash - assert_equal_rexml(<<-eoxml) - <products/> - eoxml - end - - def test_one_node_with_attributes_document_as_hash - assert_equal_rexml(<<-eoxml) - <products foo="bar"/> - eoxml - end - - def test_products_node_with_book_node_as_hash - assert_equal_rexml(<<-eoxml) - <products> - <book name="awesome" id="12345" /> - </products> - eoxml - end - - def test_products_node_with_two_book_nodes_as_hash - assert_equal_rexml(<<-eoxml) - <products> - <book name="awesome" id="12345" /> - <book name="america" id="67890" /> - </products> - eoxml - end - def test_single_node_with_content_as_hash - assert_equal_rexml(<<-eoxml) - <products> - hello world - </products> - eoxml - end - - def test_children_with_children - assert_equal_rexml(<<-eoxml) - <root> - <products> - <book name="america" id="67890" /> - </products> - </root> - eoxml - end - - def test_children_with_text - assert_equal_rexml(<<-eoxml) - <root> - <products> - hello everyone - </products> - </root> - eoxml - end - - def test_children_with_non_adjacent_text - assert_equal_rexml(<<-eoxml) - <root> - good - <products> - hello everyone - </products> - morning - </root> - eoxml - end + def expansion_attack_error + Java::OrgXmlSax::SAXParseException + end - private - def assert_equal_rexml(xml) - parsed_xml = XmlMini.parse(xml) - hash = XmlMini.with_backend("REXML") { XmlMini.parse(xml) } - assert_equal(hash, parsed_xml) + def extended_engine? + false end end end diff --git a/activesupport/test/xml_mini/libxml_engine_test.rb b/activesupport/test/xml_mini/libxml_engine_test.rb index 81b0d3c407..f3394ad7f2 100644 --- a/activesupport/test/xml_mini/libxml_engine_test.rb +++ b/activesupport/test/xml_mini/libxml_engine_test.rb @@ -1,203 +1,19 @@ -begin - require "libxml" -rescue LoadError - # Skip libxml tests -else - require "abstract_unit" - require "active_support/xml_mini" - require "active_support/core_ext/hash/conversions" - - class LibxmlEngineTest < ActiveSupport::TestCase - include ActiveSupport +require_relative "xml_mini_engine_test" +XMLMiniEngineTest.run_with_gem("libxml") do + class LibxmlEngineTest < XMLMiniEngineTest def setup - @default_backend = XmlMini.backend - XmlMini.backend = "LibXML" - + super LibXML::XML::Error.set_handler(&lambda { |error| }) #silence libxml, exceptions will do end - def teardown - XmlMini.backend = @default_backend - end - - def test_exception_thrown_on_expansion_attack - assert_raise LibXML::XML::Error do - attack_xml = %{<?xml version="1.0" encoding="UTF-8"?> - <!DOCTYPE member [ - <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"> - <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;"> - <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;"> - <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;"> - <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;"> - <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;"> - <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"> - ]> - <member> - &a; - </member> - } - Hash.from_xml(attack_xml) + private + def engine + "LibXML" end - end - - def test_setting_libxml_as_backend - XmlMini.backend = "LibXML" - assert_equal XmlMini_LibXML, XmlMini.backend - end - - def test_blank_returns_empty_hash - assert_equal({}, XmlMini.parse(nil)) - assert_equal({}, XmlMini.parse("")) - end - - def test_array_type_makes_an_array - assert_equal_rexml(<<-eoxml) - <blog> - <posts type="array"> - <post>a post</post> - <post>another post</post> - </posts> - </blog> - eoxml - end - - def test_one_node_document_as_hash - assert_equal_rexml(<<-eoxml) - <products/> - eoxml - end - - def test_one_node_with_attributes_document_as_hash - assert_equal_rexml(<<-eoxml) - <products foo="bar"/> - eoxml - end - - def test_products_node_with_book_node_as_hash - assert_equal_rexml(<<-eoxml) - <products> - <book name="awesome" id="12345" /> - </products> - eoxml - end - - def test_products_node_with_two_book_nodes_as_hash - assert_equal_rexml(<<-eoxml) - <products> - <book name="awesome" id="12345" /> - <book name="america" id="67890" /> - </products> - eoxml - end - - def test_single_node_with_content_as_hash - assert_equal_rexml(<<-eoxml) - <products> - hello world - </products> - eoxml - end - - def test_children_with_children - assert_equal_rexml(<<-eoxml) - <root> - <products> - <book name="america" id="67890" /> - </products> - </root> - eoxml - end - - def test_children_with_text - assert_equal_rexml(<<-eoxml) - <root> - <products> - hello everyone - </products> - </root> - eoxml - end - - def test_children_with_non_adjacent_text - assert_equal_rexml(<<-eoxml) - <root> - good - <products> - hello everyone - </products> - morning - </root> - eoxml - end - - def test_parse_from_io - io = StringIO.new(<<-eoxml) - <root> - good - <products> - hello everyone - </products> - morning - </root> - eoxml - assert_equal_rexml(io) - end - - def test_children_with_simple_cdata - assert_equal_rexml(<<-eoxml) - <root> - <products> - <![CDATA[cdatablock]]> - </products> - </root> - eoxml - end - def test_children_with_multiple_cdata - assert_equal_rexml(<<-eoxml) - <root> - <products> - <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]> - </products> - </root> - eoxml - end - - def test_children_with_text_and_cdata - assert_equal_rexml(<<-eoxml) - <root> - <products> - hello <![CDATA[cdatablock]]> - morning - </products> - </root> - eoxml - end - - def test_children_with_blank_text - assert_equal_rexml(<<-eoxml) - <root> - <products> </products> - </root> - eoxml - end - - def test_children_with_blank_text_and_attribute - assert_equal_rexml(<<-eoxml) - <root> - <products type="file"> </products> - </root> - eoxml - end - - private - def assert_equal_rexml(xml) - parsed_xml = XmlMini.parse(xml) - xml.rewind if xml.respond_to?(:rewind) - hash = XmlMini.with_backend("REXML") { XmlMini.parse(xml) } - assert_equal(hash, parsed_xml) + def expansion_attack_error + LibXML::XML::Error end end - end diff --git a/activesupport/test/xml_mini/libxmlsax_engine_test.rb b/activesupport/test/xml_mini/libxmlsax_engine_test.rb index e25fa2813c..f457e160d6 100644 --- a/activesupport/test/xml_mini/libxmlsax_engine_test.rb +++ b/activesupport/test/xml_mini/libxmlsax_engine_test.rb @@ -1,195 +1,14 @@ -begin - require "libxml" -rescue LoadError - # Skip libxml tests -else - require "abstract_unit" - require "active_support/xml_mini" - require "active_support/core_ext/hash/conversions" +require_relative "xml_mini_engine_test" - class LibXMLSAXEngineTest < ActiveSupport::TestCase - include ActiveSupport - - def setup - @default_backend = XmlMini.backend - XmlMini.backend = "LibXMLSAX" - end - - def teardown - XmlMini.backend = @default_backend - end - - def test_exception_thrown_on_expansion_attack - assert_raise LibXML::XML::Error do - attack_xml = <<-EOT - <?xml version="1.0" encoding="UTF-8"?> - <!DOCTYPE member [ - <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"> - <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;"> - <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;"> - <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;"> - <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;"> - <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;"> - <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"> - ]> - <member> - &a; - </member> - EOT - - Hash.from_xml(attack_xml) +XMLMiniEngineTest.run_with_gem("libxml") do + class LibXMLSAXEngineTest < XMLMiniEngineTest + private + def engine + "LibXMLSAX" end - end - - def test_setting_libxml_as_backend - XmlMini.backend = "LibXMLSAX" - assert_equal XmlMini_LibXMLSAX, XmlMini.backend - end - - def test_blank_returns_empty_hash - assert_equal({}, XmlMini.parse(nil)) - assert_equal({}, XmlMini.parse("")) - end - - def test_array_type_makes_an_array - assert_equal_rexml(<<-eoxml) - <blog> - <posts type="array"> - <post>a post</post> - <post>another post</post> - </posts> - </blog> - eoxml - end - - def test_one_node_document_as_hash - assert_equal_rexml(<<-eoxml) - <products/> - eoxml - end - - def test_one_node_with_attributes_document_as_hash - assert_equal_rexml(<<-eoxml) - <products foo="bar"/> - eoxml - end - - def test_products_node_with_book_node_as_hash - assert_equal_rexml(<<-eoxml) - <products> - <book name="awesome" id="12345" /> - </products> - eoxml - end - - def test_products_node_with_two_book_nodes_as_hash - assert_equal_rexml(<<-eoxml) - <products> - <book name="awesome" id="12345" /> - <book name="america" id="67890" /> - </products> - eoxml - end - def test_single_node_with_content_as_hash - assert_equal_rexml(<<-eoxml) - <products> - hello world - </products> - eoxml - end - - def test_children_with_children - assert_equal_rexml(<<-eoxml) - <root> - <products> - <book name="america" id="67890" /> - </products> - </root> - eoxml - end - - def test_children_with_text - assert_equal_rexml(<<-eoxml) - <root> - <products> - hello everyone - </products> - </root> - eoxml - end - - def test_children_with_non_adjacent_text - assert_equal_rexml(<<-eoxml) - <root> - good - <products> - hello everyone - </products> - morning - </root> - eoxml - end - - def test_parse_from_io - io = StringIO.new(<<-eoxml) - <root> - good - <products> - hello everyone - </products> - morning - </root> - eoxml - assert_equal_rexml(io) - end - - def test_children_with_simple_cdata - assert_equal_rexml(<<-eoxml) - <root> - <products> - <![CDATA[cdatablock]]> - </products> - </root> - eoxml - end - - def test_children_with_multiple_cdata - assert_equal_rexml(<<-eoxml) - <root> - <products> - <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]> - </products> - </root> - eoxml - end - - def test_children_with_text_and_cdata - assert_equal_rexml(<<-eoxml) - <root> - <products> - hello <![CDATA[cdatablock]]> - morning - </products> - </root> - eoxml - end - - def test_children_with_blank_text - assert_equal_rexml(<<-eoxml) - <root> - <products> </products> - </root> - eoxml - end - - private - def assert_equal_rexml(xml) - parsed_xml = XmlMini.parse(xml) - xml.rewind if xml.respond_to?(:rewind) - hash = XmlMini.with_backend("REXML") { XmlMini.parse(xml) } - assert_equal(hash, parsed_xml) + def expansion_attack_error + LibXML::XML::Error end end - end diff --git a/activesupport/test/xml_mini/nokogiri_engine_test.rb b/activesupport/test/xml_mini/nokogiri_engine_test.rb index 44b82da4e4..3151e75fc0 100644 --- a/activesupport/test/xml_mini/nokogiri_engine_test.rb +++ b/activesupport/test/xml_mini/nokogiri_engine_test.rb @@ -1,215 +1,14 @@ -begin - require "nokogiri" -rescue LoadError - # Skip nokogiri tests -else - require "abstract_unit" - require "active_support/xml_mini" - require "active_support/core_ext/hash/conversions" +require_relative "xml_mini_engine_test" - class NokogiriEngineTest < ActiveSupport::TestCase - def setup - @default_backend = ActiveSupport::XmlMini.backend - ActiveSupport::XmlMini.backend = "Nokogiri" - end - - def teardown - ActiveSupport::XmlMini.backend = @default_backend - end - - def test_file_from_xml - hash = Hash.from_xml(<<-eoxml) - <blog> - <logo type="file" name="logo.png" content_type="image/png"> - </logo> - </blog> - eoxml - assert hash.has_key?("blog") - assert hash["blog"].has_key?("logo") - - file = hash["blog"]["logo"] - assert_equal "logo.png", file.original_filename - assert_equal "image/png", file.content_type - end - - def test_exception_thrown_on_expansion_attack - assert_raise Nokogiri::XML::SyntaxError do - attack_xml = <<-EOT - <?xml version="1.0" encoding="UTF-8"?> - <!DOCTYPE member [ - <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"> - <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;"> - <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;"> - <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;"> - <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;"> - <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;"> - <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"> - ]> - <member> - &a; - </member> - EOT - Hash.from_xml(attack_xml) +XMLMiniEngineTest.run_with_gem("nokogiri") do + class NokogiriEngineTest < XMLMiniEngineTest + private + def engine + "Nokogiri" end - end - - def test_setting_nokogiri_as_backend - ActiveSupport::XmlMini.backend = "Nokogiri" - assert_equal ActiveSupport::XmlMini_Nokogiri, ActiveSupport::XmlMini.backend - end - - def test_blank_returns_empty_hash - assert_equal({}, ActiveSupport::XmlMini.parse(nil)) - assert_equal({}, ActiveSupport::XmlMini.parse("")) - end - - def test_array_type_makes_an_array - assert_equal_rexml(<<-eoxml) - <blog> - <posts type="array"> - <post>a post</post> - <post>another post</post> - </posts> - </blog> - eoxml - end - - def test_one_node_document_as_hash - assert_equal_rexml(<<-eoxml) - <products/> - eoxml - end - - def test_one_node_with_attributes_document_as_hash - assert_equal_rexml(<<-eoxml) - <products foo="bar"/> - eoxml - end - - def test_products_node_with_book_node_as_hash - assert_equal_rexml(<<-eoxml) - <products> - <book name="awesome" id="12345" /> - </products> - eoxml - end - - def test_products_node_with_two_book_nodes_as_hash - assert_equal_rexml(<<-eoxml) - <products> - <book name="awesome" id="12345" /> - <book name="america" id="67890" /> - </products> - eoxml - end - def test_single_node_with_content_as_hash - assert_equal_rexml(<<-eoxml) - <products> - hello world - </products> - eoxml - end - - def test_children_with_children - assert_equal_rexml(<<-eoxml) - <root> - <products> - <book name="america" id="67890" /> - </products> - </root> - eoxml - end - - def test_children_with_text - assert_equal_rexml(<<-eoxml) - <root> - <products> - hello everyone - </products> - </root> - eoxml - end - - def test_children_with_non_adjacent_text - assert_equal_rexml(<<-eoxml) - <root> - good - <products> - hello everyone - </products> - morning - </root> - eoxml - end - - def test_parse_from_io - io = StringIO.new(<<-eoxml) - <root> - good - <products> - hello everyone - </products> - morning - </root> - eoxml - assert_equal_rexml(io) - end - - def test_children_with_simple_cdata - assert_equal_rexml(<<-eoxml) - <root> - <products> - <![CDATA[cdatablock]]> - </products> - </root> - eoxml - end - - def test_children_with_multiple_cdata - assert_equal_rexml(<<-eoxml) - <root> - <products> - <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]> - </products> - </root> - eoxml - end - - def test_children_with_text_and_cdata - assert_equal_rexml(<<-eoxml) - <root> - <products> - hello <![CDATA[cdatablock]]> - morning - </products> - </root> - eoxml - end - - def test_children_with_blank_text - assert_equal_rexml(<<-eoxml) - <root> - <products> </products> - </root> - eoxml - end - - def test_children_with_blank_text_and_attribute - assert_equal_rexml(<<-eoxml) - <root> - <products type="file"> </products> - </root> - eoxml - end - - private - def assert_equal_rexml(xml) - parsed_xml = ActiveSupport::XmlMini.parse(xml) - xml.rewind if xml.respond_to?(:rewind) - hash = ActiveSupport::XmlMini.with_backend("REXML") { ActiveSupport::XmlMini.parse(xml) } - assert_equal(hash, parsed_xml) + def expansion_attack_error + Nokogiri::XML::SyntaxError end end - end diff --git a/activesupport/test/xml_mini/nokogirisax_engine_test.rb b/activesupport/test/xml_mini/nokogirisax_engine_test.rb index 24b35cadf6..7dafbdaf48 100644 --- a/activesupport/test/xml_mini/nokogirisax_engine_test.rb +++ b/activesupport/test/xml_mini/nokogirisax_engine_test.rb @@ -1,216 +1,14 @@ -begin - require "nokogiri" -rescue LoadError - # Skip nokogiri tests -else - require "abstract_unit" - require "active_support/xml_mini" - require "active_support/core_ext/hash/conversions" +require_relative "xml_mini_engine_test" - class NokogiriSAXEngineTest < ActiveSupport::TestCase - def setup - @default_backend = ActiveSupport::XmlMini.backend - ActiveSupport::XmlMini.backend = "NokogiriSAX" - end - - def teardown - ActiveSupport::XmlMini.backend = @default_backend - end - - def test_file_from_xml - hash = Hash.from_xml(<<-eoxml) - <blog> - <logo type="file" name="logo.png" content_type="image/png"> - </logo> - </blog> - eoxml - assert hash.has_key?("blog") - assert hash["blog"].has_key?("logo") - - file = hash["blog"]["logo"] - assert_equal "logo.png", file.original_filename - assert_equal "image/png", file.content_type - end - - def test_exception_thrown_on_expansion_attack - assert_raise RuntimeError do - attack_xml = <<-EOT - <?xml version="1.0" encoding="UTF-8"?> - <!DOCTYPE member [ - <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"> - <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;"> - <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;"> - <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;"> - <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;"> - <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;"> - <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"> - ]> - <member> - &a; - </member> - EOT - - Hash.from_xml(attack_xml) +XMLMiniEngineTest.run_with_gem("nokogiri") do + class NokogiriSAXEngineTest < XMLMiniEngineTest + private + def engine + "NokogiriSAX" end - end - - def test_setting_nokogirisax_as_backend - ActiveSupport::XmlMini.backend = "NokogiriSAX" - assert_equal ActiveSupport::XmlMini_NokogiriSAX, ActiveSupport::XmlMini.backend - end - - def test_blank_returns_empty_hash - assert_equal({}, ActiveSupport::XmlMini.parse(nil)) - assert_equal({}, ActiveSupport::XmlMini.parse("")) - end - - def test_array_type_makes_an_array - assert_equal_rexml(<<-eoxml) - <blog> - <posts type="array"> - <post>a post</post> - <post>another post</post> - </posts> - </blog> - eoxml - end - - def test_one_node_document_as_hash - assert_equal_rexml(<<-eoxml) - <products/> - eoxml - end - - def test_one_node_with_attributes_document_as_hash - assert_equal_rexml(<<-eoxml) - <products foo="bar"/> - eoxml - end - - def test_products_node_with_book_node_as_hash - assert_equal_rexml(<<-eoxml) - <products> - <book name="awesome" id="12345" /> - </products> - eoxml - end - - def test_products_node_with_two_book_nodes_as_hash - assert_equal_rexml(<<-eoxml) - <products> - <book name="awesome" id="12345" /> - <book name="america" id="67890" /> - </products> - eoxml - end - def test_single_node_with_content_as_hash - assert_equal_rexml(<<-eoxml) - <products> - hello world - </products> - eoxml - end - - def test_children_with_children - assert_equal_rexml(<<-eoxml) - <root> - <products> - <book name="america" id="67890" /> - </products> - </root> - eoxml - end - - def test_children_with_text - assert_equal_rexml(<<-eoxml) - <root> - <products> - hello everyone - </products> - </root> - eoxml - end - - def test_children_with_non_adjacent_text - assert_equal_rexml(<<-eoxml) - <root> - good - <products> - hello everyone - </products> - morning - </root> - eoxml - end - - def test_parse_from_io - io = StringIO.new(<<-eoxml) - <root> - good - <products> - hello everyone - </products> - morning - </root> - eoxml - assert_equal_rexml(io) - end - - def test_children_with_simple_cdata - assert_equal_rexml(<<-eoxml) - <root> - <products> - <![CDATA[cdatablock]]> - </products> - </root> - eoxml - end - - def test_children_with_multiple_cdata - assert_equal_rexml(<<-eoxml) - <root> - <products> - <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]> - </products> - </root> - eoxml - end - - def test_children_with_text_and_cdata - assert_equal_rexml(<<-eoxml) - <root> - <products> - hello <![CDATA[cdatablock]]> - morning - </products> - </root> - eoxml - end - - def test_children_with_blank_text - assert_equal_rexml(<<-eoxml) - <root> - <products> </products> - </root> - eoxml - end - - def test_children_with_blank_text_and_attribute - assert_equal_rexml(<<-eoxml) - <root> - <products type="file"> </products> - </root> - eoxml - end - - private - def assert_equal_rexml(xml) - parsed_xml = ActiveSupport::XmlMini.parse(xml) - xml.rewind if xml.respond_to?(:rewind) - hash = ActiveSupport::XmlMini.with_backend("REXML") { ActiveSupport::XmlMini.parse(xml) } - assert_equal(hash, parsed_xml) + def expansion_attack_error + RuntimeError end end - end diff --git a/activesupport/test/xml_mini/rexml_engine_test.rb b/activesupport/test/xml_mini/rexml_engine_test.rb index dc62f3f671..c51f0d3c20 100644 --- a/activesupport/test/xml_mini/rexml_engine_test.rb +++ b/activesupport/test/xml_mini/rexml_engine_test.rb @@ -1,44 +1,25 @@ -require "abstract_unit" -require "active_support/xml_mini" +require_relative "xml_mini_engine_test" -class REXMLEngineTest < ActiveSupport::TestCase +class REXMLEngineTest < XMLMiniEngineTest def test_default_is_rexml assert_equal ActiveSupport::XmlMini_REXML, ActiveSupport::XmlMini.backend end - def test_set_rexml_as_backend - ActiveSupport::XmlMini.backend = "REXML" - assert_equal ActiveSupport::XmlMini_REXML, ActiveSupport::XmlMini.backend - end - - def test_parse_from_io - ActiveSupport::XmlMini.backend = "REXML" - io = StringIO.new(<<-eoxml) - <root> - good - <products> - hello everyone - </products> - morning - </root> - eoxml - hash = ActiveSupport::XmlMini.parse(io) - assert hash.has_key?("root") - assert hash["root"].has_key?("products") - assert_match "good", hash["root"]["__content__"] - products = hash["root"]["products"] - assert products.has_key?("__content__") - assert_match "hello everyone", products["__content__"] - end - def test_parse_from_empty_string - ActiveSupport::XmlMini.backend = "REXML" assert_equal({}, ActiveSupport::XmlMini.parse("")) end def test_parse_from_frozen_string - ActiveSupport::XmlMini.backend = "REXML" xml_string = "<root></root>".freeze assert_equal({ "root" => {} }, ActiveSupport::XmlMini.parse(xml_string)) end + + private + def engine + "REXML" + end + + def expansion_attack_error + RuntimeError + end end diff --git a/activesupport/test/xml_mini/xml_mini_engine_test.rb b/activesupport/test/xml_mini/xml_mini_engine_test.rb new file mode 100644 index 0000000000..5be9084c9d --- /dev/null +++ b/activesupport/test/xml_mini/xml_mini_engine_test.rb @@ -0,0 +1,257 @@ +require "abstract_unit" +require "active_support/xml_mini" +require "active_support/core_ext/hash/conversions" + +class XMLMiniEngineTest < ActiveSupport::TestCase + def self.run_with_gem(gem_name) + require gem_name + yield + rescue LoadError + # Skip tests unless gem is available + end + + def self.run_with_platform(platform_name) + yield if RUBY_PLATFORM.include?(platform_name) + end + + def self.inherited(base) + base.include EngineTests + super + end + + def setup + @default_backend = ActiveSupport::XmlMini.backend + ActiveSupport::XmlMini.backend = engine + super + end + + def teardown + ActiveSupport::XmlMini.backend = @default_backend + super + end + + module EngineTests + def test_file_from_xml + hash = Hash.from_xml(<<-eoxml) + <blog> + <logo type="file" name="logo.png" content_type="image/png"> + </logo> + </blog> + eoxml + assert hash.key?("blog") + assert hash["blog"].key?("logo") + + file = hash["blog"]["logo"] + assert_equal "logo.png", file.original_filename + assert_equal "image/png", file.content_type + end + + def test_exception_thrown_on_expansion_attack + assert_raise expansion_attack_error do + Hash.from_xml(<<-eoxml) + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE member [ + <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"> + <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;"> + <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;"> + <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;"> + <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;"> + <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;"> + <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"> + ]> + <member> + &a; + </member> + eoxml + end + end + + def test_setting_backend + assert_engine_class ActiveSupport::XmlMini.backend + end + + def test_blank_returns_empty_hash + assert_equal({}, ActiveSupport::XmlMini.parse(nil)) + assert_equal({}, ActiveSupport::XmlMini.parse("")) + end + + def test_array_type_makes_an_array + assert_equal_rexml(<<-eoxml) + <blog> + <posts type="array"> + <post>a post</post> + <post>another post</post> + </posts> + </blog> + eoxml + end + + def test_one_node_document_as_hash + assert_equal_rexml(<<-eoxml) + <products/> + eoxml + end + + def test_one_node_with_attributes_document_as_hash + assert_equal_rexml(<<-eoxml) + <products foo="bar"/> + eoxml + end + + def test_products_node_with_book_node_as_hash + assert_equal_rexml(<<-eoxml) + <products> + <book name="awesome" id="12345" /> + </products> + eoxml + end + + def test_products_node_with_two_book_nodes_as_hash + assert_equal_rexml(<<-eoxml) + <products> + <book name="awesome" id="12345" /> + <book name="america" id="67890" /> + </products> + eoxml + end + + def test_single_node_with_content_as_hash + assert_equal_rexml(<<-eoxml) + <products> + hello world + </products> + eoxml + end + + def test_children_with_children + assert_equal_rexml(<<-eoxml) + <root> + <products> + <book name="america" id="67890" /> + </products> + </root> + eoxml + end + + def test_children_with_text + assert_equal_rexml(<<-eoxml) + <root> + <products> + hello everyone + </products> + </root> + eoxml + end + + def test_children_with_non_adjacent_text + assert_equal_rexml(<<-eoxml) + <root> + good + <products> + hello everyone + </products> + morning + </root> + eoxml + end + + def test_parse_from_io + skip_unless_extended_engine + + assert_equal_rexml(StringIO.new(<<-eoxml)) + <root> + good + <products> + hello everyone + </products> + morning + </root> + eoxml + end + + def test_children_with_simple_cdata + skip_unless_extended_engine + + assert_equal_rexml(<<-eoxml) + <root> + <products> + <![CDATA[cdatablock]]> + </products> + </root> + eoxml + end + + def test_children_with_multiple_cdata + skip_unless_extended_engine + + assert_equal_rexml(<<-eoxml) + <root> + <products> + <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]> + </products> + </root> + eoxml + end + + def test_children_with_text_and_cdata + skip_unless_extended_engine + + assert_equal_rexml(<<-eoxml) + <root> + <products> + hello <![CDATA[cdatablock]]> + morning + </products> + </root> + eoxml + end + + def test_children_with_blank_text + skip_unless_extended_engine + + assert_equal_rexml(<<-eoxml) + <root> + <products> </products> + </root> + eoxml + end + + def test_children_with_blank_text_and_attribute + skip_unless_extended_engine + + assert_equal_rexml(<<-eoxml) + <root> + <products type="file"> </products> + </root> + eoxml + end + + private + def engine + raise NotImplementedError + end + + def assert_engine_class(actual) + assert_equal ActiveSupport.const_get("XmlMini_#{engine}"), actual + end + + def assert_equal_rexml(xml) + parsed_xml = ActiveSupport::XmlMini.parse(xml) + xml.rewind if xml.respond_to?(:rewind) + hash = ActiveSupport::XmlMini.with_backend("REXML") { ActiveSupport::XmlMini.parse(xml) } + assert_equal(hash, parsed_xml) + end + + def expansion_attack_error + raise NotImplementedError + end + + def extended_engine? + true + end + + def skip_unless_extended_engine + skip "#{engine} is not an extended engine" unless extended_engine? + end + end +end |