diff options
Diffstat (limited to 'activesupport')
139 files changed, 2186 insertions, 1666 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 10095ee1bd..2cee8375b7 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,73 +1,348 @@ -* Remove deprecated class `ActiveSupport::Concurrency::Latch` +* Update `titleize` regex to allow apostrophes + + In 4b685aa the regex in `titleize` was updated to not match apostrophes to + better reflect the nature of the transformation. Unfortunately this had the + side effect of breaking capitalization on the first word of a sub-string, e.g: + + >> "This was 'fake news'".titleize + => "This Was 'fake News'" + + This is fixed by extending the look-behind to also check for a word + character on the other side of the apostrophe. + + Fixes #28312. + + *Andrew White* + +* Add `rfc3339` aliases to `xmlschema` for `Time` and `ActiveSupport::TimeWithZone` + + For naming consistency when using the RFC 3339 profile of ISO 8601 in applications. + + *Andrew White* + +* Add `Time.rfc3339` parsing method + + The `Time.xmlschema` and consequently its alias `iso8601` accepts timestamps + without a offset in contravention of the RFC 3339 standard. This method + enforces that constraint and raises an `ArgumentError` if it doesn't. + + *Andrew White* + +* Add `ActiveSupport::TimeZone.rfc3339` parsing method + + Previously there was no way to get a RFC 3339 timestamp into a specific + timezone without either using `parse` or chaining methods. The new method + allows parsing directly into the timezone, e.g: + + >> Time.zone = "Hawaii" + => "Hawaii" + >> Time.zone.rfc3339("1999-12-31T14:00:00Z") + => Fri, 31 Dec 1999 14:00:00 HST -10:00 + + This new method has stricter semantics than the current `parse` method + and will raise an `ArgumentError` instead of returning nil, e.g: + + >> Time.zone = "Hawaii" + => "Hawaii" + >> Time.zone.rfc3339("foobar") + ArgumentError: invalid date + >> Time.zone.parse("foobar") + => nil + + It will also raise an `ArgumentError` when either the time or offset + components are missing, e.g: + + >> Time.zone = "Hawaii" + => "Hawaii" + >> Time.zone.rfc3339("1999-12-31") + ArgumentError: invalid date + >> Time.zone.rfc3339("1999-12-31T14:00:00") + ArgumentError: invalid date + + *Andrew White* + +* Add `ActiveSupport::TimeZone.iso8601` parsing method + + Previously there was no way to get a ISO 8601 timestamp into a specific + timezone without either using `parse` or chaining methods. The new method + allows parsing directly into the timezone, e.g: + + >> Time.zone = "Hawaii" + => "Hawaii" + >> Time.zone.iso8601("1999-12-31T14:00:00Z") + => Fri, 31 Dec 1999 14:00:00 HST -10:00 + + If the timestamp is a ISO 8601 date (YYYY-MM-DD) then the time is set + to midnight, e.g: + + >> Time.zone = "Hawaii" + => "Hawaii" + >> Time.zone.iso8601("1999-12-31") + => Fri, 31 Dec 1999 00:00:00 HST -10:00 + + This new method has stricter semantics than the current `parse` method + and will raise an `ArgumentError` instead of returning nil, e.g: + + >> Time.zone = "Hawaii" + => "Hawaii" + >> Time.zone.iso8601("foobar") + ArgumentError: invalid date + >> Time.zone.parse("foobar") + => nil + + *Andrew White* + +* Deprecate implicit coercion of `ActiveSupport::Duration` + + Currently `ActiveSupport::Duration` implicitly converts to a seconds + value when used in a calculation except for the explicit examples of + addition and subtraction where the duration is the receiver, e.g: + + >> 2 * 1.day + => 172800 + + This results in lots of confusion especially when using durations + with dates because adding/subtracting a value from a date treats + integers as a day and not a second, e.g: + + >> Date.today + => Wed, 01 Mar 2017 + >> Date.today + 2 * 1.day + => Mon, 10 Apr 2490 + + To fix this we're implementing `coerce` so that we can provide a + deprecation warning with the intent of removing the implicit coercion + in Rails 5.2, e.g: + + >> 2 * 1.day + DEPRECATION WARNING: Implicit coercion of ActiveSupport::Duration + to a Numeric is deprecated and will raise a TypeError in Rails 5.2. + => 172800 + + In Rails 5.2 it will raise `TypeError`, e.g: + + >> 2 * 1.day + TypeError: ActiveSupport::Duration can't be coerced into Integer + + This is the same behavior as with other types in Ruby, e.g: + + >> 2 * "foo" + TypeError: String can't be coerced into Integer + >> "foo" * 2 + => "foofoo" + + As part of this deprecation add `*` and `/` methods to `AS::Duration` + so that calculations that keep the duration as the receiver work + correctly whether the final receiver is a `Date` or `Time`, e.g: + + >> Date.today + => Wed, 01 Mar 2017 + >> Date.today + 1.day * 2 + => Fri, 03 Mar 2017 + + Fixes #27457. + + *Andrew White* + +* Update `DateTime#change` to support `:usec` and `:nsec` options. + + Adding support for these options now allows us to update the `DateTime#end_of` + methods to match the equivalent `Time#end_of` methods, e.g: + + datetime = DateTime.now.end_of_day + datetime.nsec == 999999999 # => true + + Fixes #21424. + + *Dan Moore*, *Andrew White* + +* Add `ActiveSupport::Duration#before` and `#after` as aliases for `#until` and `#since` + + These read more like English and require less mental gymnastics to read and write. + + Before: + + 2.weeks.since(customer_start_date) + 5.days.until(today) + + After: + + 2.weeks.after(customer_start_date) + 5.days.before(today) + + *Nick Johnstone* + +* Soft-deprecated the top-level `HashWithIndifferentAccess` constant. + `ActiveSupport::HashWithIndifferentAccess` should be used instead. + + *Robin Dupret* (#28157) + +* In Core Extensions, make `MarshalWithAutoloading#load` pass through the second, optional + argument for `Marshal#load( source [, proc] )`. This way we don't have to do + `Marshal.method(:load).super_method.call(source, proc)` just to be able to pass a proc. + + *Jeff Latz* + +* `ActiveSupport::Gzip.decompress` now checks checksum and length in footer. + + *Dylan Thacker-Smith* + + +## Rails 5.1.0.beta1 (February 23, 2017) ## + +* Cache `ActiveSupport::TimeWithZone#to_datetime` before freezing. + + *Adam Rice* + +* Deprecate `ActiveSupport.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 duplication 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` +* Remove deprecated separator argument from `parameterize`. *Andrew White* -* Remove deprecated method `Numeric#to_formatted_s` +* Remove deprecated method `Numeric#to_formatted_s`. *Andrew White* -* Remove deprecated method `alias_method_chain` +* Remove deprecated method `alias_method_chain`. *Andrew White* -* Remove deprecated constant `MissingSourceFile` +* Remove deprecated constant `MissingSourceFile`. *Andrew White* * Remove deprecated methods `Module.qualified_const_defined?`, - `Module.qualified_const_get` and `Module.qualified_const_set` + `Module.qualified_const_get` and `Module.qualified_const_set`. *Andrew White* -* Remove deprecated `:prefix` option from `number_to_human_size` +* Remove deprecated `:prefix` option from `number_to_human_size`. *Andrew White* -* Remove deprecated method `ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default` +* Remove deprecated method `ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default`. *Andrew White* -* Remove deprecated file `active_support/core_ext/time/marshal.rb` +* Remove deprecated file `active_support/core_ext/time/marshal.rb`. *Andrew White* -* Remove deprecated file `active_support/core_ext/struct.rb` +* Remove deprecated file `active_support/core_ext/struct.rb`. *Andrew White* -* Remove deprecated file `active_support/core_ext/module/method_transplanting.rb` +* Remove deprecated file `active_support/core_ext/module/method_transplanting.rb`. *Andrew White* -* Remove deprecated method `Module.local_constants` +* Remove deprecated method `Module.local_constants`. *Andrew White* -* Remove deprecated file `active_support/core_ext/kernel/debugger.rb` +* Remove deprecated file `active_support/core_ext/kernel/debugger.rb`. *Andrew White* -* Remove deprecated method `ActiveSupport::Cache::Store#namespaced_key` +* Remove deprecated method `ActiveSupport::Cache::Store#namespaced_key`. *Andrew White* -* Remove deprecated method `ActiveSupport::Cache::Strategy::LocalCache::LocalStore#set_cache_value` +* Remove deprecated method `ActiveSupport::Cache::Strategy::LocalCache::LocalStore#set_cache_value`. *Andrew White* -* Remove deprecated method `ActiveSupport::Cache::MemCacheStore#escape_key` +* Remove deprecated method `ActiveSupport::Cache::MemCacheStore#escape_key`. *Andrew White* -* Remove deprecated method `ActiveSupport::Cache::FileStore#key_file_path` +* Remove deprecated method `ActiveSupport::Cache::FileStore#key_file_path`. *Andrew White* -* Ensure duration parsing is consistent across DST changes +* 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 @@ -224,28 +499,28 @@ *John Gesimondo* * `travel/travel_to` travel time helpers, now raise on nested calls, - as this can lead to confusing time stubbing. + as this can lead to confusing time stubbing. - Instead of: + 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 + 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: + preferred way to achieve above is: - travel 2.days do - # 2 days from today - end + travel 2.days do + # 2 days from today + end - travel 5.days do - # 5 days from today - end + travel 5.days do + # 5 days from today + end - *Vipul A M* + *Vipul A M* * Support parsing JSON time in ISO8601 local time strings in `ActiveSupport::JSON.decode` when `parse_json_times` is enabled. 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 8f0a954882..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 diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 34456a594f..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 diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 15f82317bd..5eee04a34e 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,20 +143,20 @@ 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 if expires_in > 0 && !options[:raw] # Set the memcache expire a few minutes in the future to support race condition ttls on read - expires_in += 5.minutes + expires_in += 300 end rescue_error_with false do @data.send(method, key, value, expires_in, options) @@ -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. diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb index 3323e4edc9..f2996a9c45 100644 --- a/activesupport/lib/active_support/cache/memory_store.rb +++ b/activesupport/lib/active_support/cache/memory_store.rb @@ -106,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 @@ -126,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] @@ -143,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) @@ -152,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 7652cae201..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,17 +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 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 @@ -136,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/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/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_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/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/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/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb index 3cf7ea0a97..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,18 +6,6 @@ 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) 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/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 0665aa88bc..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 diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb index 946f8ddeab..4f6621693e 100644 --- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb +++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb @@ -99,31 +99,30 @@ 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 end 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..ea81df2bd8 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 @@ -96,3 +122,23 @@ class Method false end end + +class Complex + # Complexes are not duplicable: + # + # Complex(1).duplicable? # => false + # Complex(1).dup # => TypeError: can't copy Complex + def duplicable? + false + end +end + +class Rational + # Rationals are not duplicable: + # + # Rational(1).duplicable? # => false + # Rational(1).dup # => TypeError: can't copy Rational + def duplicable? + false + end +end diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 4eabce79e2..7a2fc5c1b5 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -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 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/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/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/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 0146401abe..e125b657f2 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -242,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/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..d26bbac511 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -1,5 +1,7 @@ require "active_support/core_ext/array/conversions" 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 +9,118 @@ module ActiveSupport # # 1.month.ago # equivalent to Time.now.advance(months: -1) class Duration - EPOCH = ::Time.utc(2000) + 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: + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Implicit coercion of ActiveSupport::Duration to a Numeric + is deprecated and will raise a TypeError in Rails 5.2. + MSG + + [other, value] + 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 +130,24 @@ module ActiveSupport self + (-other) end + # Multiplies this Duration by a Numeric and returns a new Duration. + def *(other) + if Numeric === other + Duration.new(value * other, parts.map { |type, number| [type, number * other] }) + else + value * other + end + end + + # Divides this Duration by a Numeric and returns a new Duration. + def /(other) + if Numeric === other + Duration.new(value / other, parts.map { |type, number| [type, number / other] }) + else + value / other + end + end + def -@ #:nodoc: Duration.new(-value, parts.map { |type, number| [type, -number] }) end @@ -72,14 +186,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 +213,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 +227,7 @@ module ActiveSupport sum(-1, time) end alias :until :ago + alias :before :ago def inspect #:nodoc: parts. @@ -135,27 +245,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,9 +271,7 @@ module ActiveSupport end end - private - - def method_missing(method, *args, &block) #:nodoc: + def method_missing(method, *args, &block) value.send(method, *args, &block) 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 f54f88eb0a..8e0dc71dca 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..a641b96c57 100644 --- a/activesupport/lib/active_support/gem_version.rb +++ b/activesupport/lib/active_support/gem_version.rb @@ -8,7 +8,7 @@ module ActiveSupport MAJOR = 5 MINOR = 1 TINY = 0 - PRE = "alpha" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end 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 5919b89338..1927cddf34 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -269,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 @@ -280,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 @@ -302,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 @@ -312,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 3e78986e8e..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) 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..69109d2005 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" @@ -61,7 +61,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 262f25b874..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 @@ -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 6000ea44be..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. 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/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 135690cc42..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 @@ -88,6 +88,8 @@ module ActiveSupport 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/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..e31983cf26 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 @@ -427,7 +428,8 @@ module ActiveSupport end def freeze - period; utc; time # preload instance variables before freezing + # preload instance variables before freezing + period; utc; time; to_datetime 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/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 551235e9e8..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 @@ -675,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 @@ -983,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 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/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_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..2b1a715b7a 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,8 +84,46 @@ 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 + end + + def test_multiply + assert_equal 7.days, 1.day * 7 + assert_instance_of ActiveSupport::Duration, 1.day * 7 + + assert_deprecated do + assert_equal 86400, 1.day * 1.second + end + end + + def test_divide + assert_equal 1.day, 7.days / 7 + assert_instance_of ActiveSupport::Duration, 7.days / 7 + + assert_deprecated do + assert_equal 1, 1.day / 1.day + end + 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" + assert_deprecated do + assert_equal 1 + 1.second, 1.second + 1, "Duration + Numeric should == Numeric + Duration" + end end def test_time_plus_duration_returns_same_time_datatype @@ -104,6 +142,13 @@ class DurationTest < ActiveSupport::TestCase assert_equal 'expected a time or date, got ""', e.message, "ensure ArgumentError is not being raised by dependencies.rb" end + def test_implicit_coercion_is_deprecated + assert_deprecated { 1 + 1.second } + assert_deprecated { 1 - 1.second } + assert_deprecated { 1 * 1.second } + assert_deprecated { 1 / 1.second } + end + def test_fractional_weeks assert_equal((86400 * 7) * 1.5, 1.5.weeks) assert_equal((86400 * 7) * 1.7, 1.7.weeks) @@ -179,6 +224,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 +254,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 +271,7 @@ class DurationTest < ActiveSupport::TestCase when 1.day "ok" end - assert_equal cased, "ok" + assert_equal "ok", cased end def test_respond_to @@ -228,13 +286,43 @@ class DurationTest < ActiveSupport::TestCase def test_comparable assert_equal(-1, (0.seconds <=> 1.second)) assert_equal(-1, (1.second <=> 1.minute)) - assert_equal(-1, (1 <=> 1.minute)) + + assert_deprecated do + assert_equal(-1, (1 <=> 1.minute)) + end + assert_equal(0, (0.seconds <=> 0.seconds)) assert_equal(0, (0.seconds <=> 0.minutes)) assert_equal(0, (1.second <=> 1.second)) assert_equal(1, (1.second <=> 0.second)) assert_equal(1, (1.minute <=> 1.second)) - assert_equal(1, (61 <=> 1.minute)) + + assert_deprecated do + assert_equal(1, (61 <=> 1.minute)) + end + 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 @@ -345,6 +433,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 8b816b3aad..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 @@ -1068,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 @@ -1113,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 @@ -1595,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/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_test.rb b/activesupport/test/core_ext/module_test.rb index 8f2df22f27..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,17 +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_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 5361b7b708..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 @@ -394,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 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..e6f3c8aef2 100644 --- a/activesupport/test/core_ext/object/duplicable_test.rb +++ b/activesupport/test/core_ext/object/duplicable_test.rb @@ -4,16 +4,23 @@ 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.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 98bcdda45b..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,37 +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) + 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) + 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 @@ -222,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: @@ -283,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 @@ -339,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 @@ -710,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..3cc29ca040 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 @@ -455,6 +465,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 +517,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_nothing_raised do @twz.period @twz.time + @twz.to_datetime end end @@ -1057,7 +1072,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..5f72fbf662 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -285,6 +285,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 d881bd346d..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,35 +271,25 @@ 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_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 @@ -507,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 fa518104c2..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 @@ -452,7 +452,7 @@ EXPECTED assert_equal '{"number":null}', NaNNumber.new.to_json end - protected + 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/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..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") @@ -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 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 |