diff options
Diffstat (limited to 'activesupport')
41 files changed, 1053 insertions, 480 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 2fb7f29d73..6f99155199 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,407 +1 @@ -* Deprecate `.halt_callback_chains_on_return_false`. - - *Rafael Mendonça França* - -* Remove deprecated behavior that halts callbacks when the return is false. - - *Rafael Mendonça França* - -* Deprecate passing string to `:if` and `:unless` conditional options - on `set_callback` and `skip_callback`. - - *Ryuta Kamizono* - -* Raise `ArgumentError` when passing string to define callback. - - *Ryuta Kamizono* - -* Updated Unicode version to 9.0.0 - - Now we can handle new emojis such like "👩👩👧👦" ("\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}"). - - version 8.0.0 - - "👩👩👧👦".mb_chars.grapheme_length # => 4 - "👩👩👧👦".mb_chars.reverse # => "👦👧👩👩" - - version 9.0.0 - - "👩👩👧👦".mb_chars.grapheme_length # => 1 - "👩👩👧👦".mb_chars.reverse # => "👩👩👧👦" - - *Fumiaki MATSUSHIMA* - -* Changed `ActiveSupport::Inflector#transliterate` to raise `ArgumentError` when it receives - anything except a string. - - *Kevin McPhillips* - -* Fixed bugs that `StringInquirer#respond_to_missing?` and - `ArrayInquirer#respond_to_missing?` do not fallback to `super`. - - *Akira Matsuda* - -* Fix inconsistent results when parsing large durations and constructing durations from code - - ActiveSupport::Duration.parse('P3Y') == 3.years # It should be true - - Duration parsing made independent from any moment of time: - Fixed length in seconds is assigned to each duration part during parsing. - - Changed duration of months and years in seconds to more accurate and logical: - - 1. The value of 365.2425 days in Gregorian year is more accurate - as it accounts for every 400th non-leap year. - - 2. Month's length is bound to year's duration, which makes - sensible comparisons like `12.months == 1.year` to be `true` - and nonsensical ones like `30.days == 1.month` to be `false`. - - Calculations on times and dates with durations shouldn't be affected as - duration's numeric value isn't used in calculations, only parts are used. - - Methods on `Numeric` like `2.days` now use these predefined durations - to avoid duplicating of duration constants through the codebase and - eliminate creation of intermediate durations. - - *Andrey Novikov, Andrew White* - -* Change return value of `Rational#duplicable?`, `ComplexClass#duplicable?` - to false. - - *utilum* - -* Change return value of `NilClass#duplicable?`, `FalseClass#duplicable?`, - `TrueClass#duplicable?`, `Symbol#duplicable?` and `Numeric#duplicable?` - to true with Ruby 2.4+. These classes can dup with Ruby 2.4+. - - *Yuji Yaginuma* - -* Remove deprecated class `ActiveSupport::Concurrency::Latch`. - - *Andrew White* - -* Remove deprecated separator argument from `parameterize`. - - *Andrew White* - -* Remove deprecated method `Numeric#to_formatted_s`. - - *Andrew White* - -* Remove deprecated method `alias_method_chain`. - - *Andrew White* - -* Remove deprecated constant `MissingSourceFile`. - - *Andrew White* - -* Remove deprecated methods `Module.qualified_const_defined?`, - `Module.qualified_const_get` and `Module.qualified_const_set`. - - *Andrew White* - -* Remove deprecated `:prefix` option from `number_to_human_size`. - - *Andrew White* - -* Remove deprecated method `ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default`. - - *Andrew White* - -* Remove deprecated file `active_support/core_ext/time/marshal.rb`. - - *Andrew White* - -* Remove deprecated file `active_support/core_ext/struct.rb`. - - *Andrew White* - -* Remove deprecated file `active_support/core_ext/module/method_transplanting.rb`. - - *Andrew White* - -* Remove deprecated method `Module.local_constants`. - - *Andrew White* - -* Remove deprecated file `active_support/core_ext/kernel/debugger.rb`. - - *Andrew White* - -* Remove deprecated method `ActiveSupport::Cache::Store#namespaced_key`. - - *Andrew White* - -* Remove deprecated method `ActiveSupport::Cache::Strategy::LocalCache::LocalStore#set_cache_value`. - - *Andrew White* - -* Remove deprecated method `ActiveSupport::Cache::MemCacheStore#escape_key`. - - *Andrew White* - -* Remove deprecated method `ActiveSupport::Cache::FileStore#key_file_path`. - - *Andrew White* - -* 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* - -* 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/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb index fea072d91c..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 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/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/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/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index ea81df2bd8..b028df97ee 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -106,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 @@ -124,21 +124,31 @@ class Method end class Complex - # Complexes are not duplicable: - # - # Complex(1).duplicable? # => false - # Complex(1).dup # => TypeError: can't copy Complex - def duplicable? - false + 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 - # Rationals are not duplicable: - # - # Rational(1).duplicable? # => false - # Rational(1).dup # => TypeError: can't copy Rational - def duplicable? - false + 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/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/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/duration.rb b/activesupport/lib/active_support/duration.rb index 003f6203ef..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,6 +10,66 @@ module ActiveSupport # # 1.month.ago # equivalent to Time.now.advance(months: -1) class Duration + 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 @@ -88,6 +151,24 @@ module ActiveSupport @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) @@ -109,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 @@ -180,6 +283,7 @@ module ActiveSupport 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. @@ -187,6 +291,7 @@ module ActiveSupport sum(-1, time) end alias :until :ago + alias :before :ago def inspect #:nodoc: parts. @@ -210,8 +315,6 @@ module ActiveSupport ISO8601Serializer.new(self, precision: precision).serialize end - delegate :<=>, to: :value - private def sum(sign, time = ::Time.current) @@ -235,5 +338,9 @@ module ActiveSupport 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/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 79e7feaf47..1927cddf34 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -270,7 +270,7 @@ module ActiveSupport end def compact - dup.compact! + dup.tap(&:compact!) end # Convert to a regular hash with string keys. @@ -316,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 b94368df14..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 diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 8ccb735c6d..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. diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 0671469788..24053b4fe5 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -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/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/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..18477b9f6b 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -340,6 +340,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 +394,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/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/test/caching_test.rb b/activesupport/test/caching_test.rb index c543122d91..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 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_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index e3b31c10f5..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 diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index 6a275d1d5b..1648a9b270 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -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) @@ -237,6 +282,118 @@ 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 diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 05813ad388..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 @@ -597,6 +599,16 @@ class HashExtTest < ActiveSupport::TestCase 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 @@ -1078,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 @@ -1123,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 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/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb index e6f3c8aef2..68b0129980 100644 --- a/activesupport/test/core_ext/object/duplicable_test.rb +++ b/activesupport/test/core_ext/object/duplicable_test.rb @@ -4,7 +4,10 @@ require "active_support/core_ext/object/duplicable" require "active_support/core_ext/numeric/time" class DuplicableTest < ActiveSupport::TestCase - if RUBY_VERSION >= "2.4.1" + 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... diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 5d90fa2509..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" diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index a399e36dc9..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 @@ -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 ab5ec98642..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 @@ -507,6 +535,8 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_nothing_raised do @twz.period @twz.time + @twz.to_datetime + @twz.to_time end end diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index 5f72fbf662..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 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_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/encoding_test_cases.rb b/activesupport/test/json/encoding_test_cases.rb index b2f0cf3048..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 diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 4794b55742..1615d8fdb2 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") |