diff options
Diffstat (limited to 'activesupport')
84 files changed, 1508 insertions, 290 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 8e8e9b9440..3f77b191f9 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,235 @@ +* `assert_changes` will always assert that the expression changes, + regardless of `from:` and `to:` argument combinations. + + *Daniel Ma* + +* Allow the hash function used to generate non-sensitive digests, such as the + ETag header, to be specified with `config.active_support.hash_digest_class`. + + The object provided must respond to `#hexdigest`, e.g. `Digest::SHA1`. + + *Dmitri Dolguikh* + + +## Rails 5.2.0.beta2 (November 28, 2017) ## + +* No changes. + + +## Rails 5.2.0.beta1 (November 27, 2017) ## + +* Changed default behaviour of `ActiveSupport::SecurityUtils.secure_compare`, + to make it not leak length information even for variable length string. + + Renamed old `ActiveSupport::SecurityUtils.secure_compare` to `fixed_length_secure_compare`, + and started raising `ArgumentError` in case of length mismatch of passed strings. + + *Vipul A M* + +* Make `ActiveSupport::TimeZone.all` return only time zones that are in + `ActiveSupport::TimeZone::MAPPING`. + + Fixes #7245. + + *Chris LaRose* + +* MemCacheStore: Support expiring counters. + + Pass `expires_in: [seconds]` to `#increment` and `#decrement` options + to set the Memcached TTL (time-to-live) if the counter doesn't exist. + If the counter exists, Memcached doesn't extend its expiry when it's + incremented or decremented. + + ``` + Rails.cache.increment("my_counter", 1, expires_in: 2.minutes) + ``` + + *Takumasa Ochi* + +* Handle `TZInfo::AmbiguousTime` errors + + Make `ActiveSupport::TimeWithZone` match Ruby's handling of ambiguous + times by choosing the later period, e.g. + + Ruby: + ``` + ENV["TZ"] = "Europe/Moscow" + Time.local(2014, 10, 26, 1, 0, 0) # => 2014-10-26 01:00:00 +0300 + ``` + + Before: + ``` + >> "2014-10-26 01:00:00".in_time_zone("Moscow") + TZInfo::AmbiguousTime: 26/10/2014 01:00 is an ambiguous local time. + ``` + + After: + ``` + >> "2014-10-26 01:00:00".in_time_zone("Moscow") + => Sun, 26 Oct 2014 01:00:00 MSK +03:00 + ``` + + Fixes #17395. + + *Andrew White* + +* Redis cache store. + + ``` + # Defaults to `redis://localhost:6379/0`. Only use for dev/test. + config.cache_store = :redis_cache_store + + # Supports all common cache store options (:namespace, :compress, + # :compress_threshold, :expires_in, :race_condition_ttl) and all + # Redis options. + cache_password = Rails.application.secrets.redis_cache_password + config.cache_store = :redis_cache_store, driver: :hiredis, + namespace: 'myapp-cache', compress: true, timeout: 1, + url: "redis://:#{cache_password}@myapp-cache-1:6379/0" + + # Supports Redis::Distributed with multiple hosts + config.cache_store = :redis_cache_store, driver: :hiredis + namespace: 'myapp-cache', compress: true, + url: %w[ + redis://myapp-cache-1:6379/0 + redis://myapp-cache-1:6380/0 + redis://myapp-cache-2:6379/0 + redis://myapp-cache-2:6380/0 + redis://myapp-cache-3:6379/0 + redis://myapp-cache-3:6380/0 + ] + + # Or pass a builder block + config.cache_store = :redis_cache_store, + namespace: 'myapp-cache', compress: true, + redis: -> { Redis.new … } + ``` + + Deployment note: Take care to use a *dedicated Redis cache* rather + than pointing this at your existing Redis server. It won't cope well + with mixed usage patterns and it won't expire cache entries by default. + + Redis cache server setup guide: https://redis.io/topics/lru-cache + + *Jeremy Daer* + +* Cache: Enable compression by default for values > 1kB. + + Compression has long been available, but opt-in and at a 16kB threshold. + It wasn't enabled by default due to CPU cost. Today it's cheap and typical + cache data is eminently compressible, such as HTML or JSON fragments. + Compression dramatically reduces Memcached/Redis mem usage, which means + the same cache servers can store more data, which means higher hit rates. + + To disable compression, pass `compress: false` to the initializer. + + *Jeremy Daer* + +* Allow `Range#include?` on TWZ ranges + + In #11474 we prevented TWZ ranges being iterated over which matched + Ruby's handling of Time ranges and as a consequence `include?` + stopped working with both Time ranges and TWZ ranges. However in + ruby/ruby@b061634 support was added for `include?` to use `cover?` + for 'linear' objects. Since we have no way of making Ruby consider + TWZ instances as 'linear' we have to override `Range#include?`. + + Fixes #30799. + + *Andrew White* + +* Fix acronym support in `humanize` + + Acronym inflections are stored with lowercase keys in the hash but + the match wasn't being lowercased before being looked up in the hash. + This shouldn't have any performance impact because before it would + fail to find the acronym and perform the `downcase` operation anyway. + + Fixes #31052. + + *Andrew White* + +* Add same method signature for `Time#prev_year` and `Time#next_year` + in accordance with `Date#prev_year`, `Date#next_year`. + + Allows pass argument for `Time#prev_year` and `Time#next_year`. + + Before: + ``` + Time.new(2017, 9, 16, 17, 0).prev_year # => 2016-09-16 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).prev_year(1) + # => ArgumentError: wrong number of arguments (given 1, expected 0) + + Time.new(2017, 9, 16, 17, 0).next_year # => 2018-09-16 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).next_year(1) + # => ArgumentError: wrong number of arguments (given 1, expected 0) + ``` + + After: + ``` + Time.new(2017, 9, 16, 17, 0).prev_year # => 2016-09-16 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).prev_year(1) # => 2016-09-16 17:00:00 +0300 + + Time.new(2017, 9, 16, 17, 0).next_year # => 2018-09-16 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).next_year(1) # => 2018-09-16 17:00:00 +0300 + ``` + + *bogdanvlviv* + +* Add same method signature for `Time#prev_month` and `Time#next_month` + in accordance with `Date#prev_month`, `Date#next_month`. + + Allows pass argument for `Time#prev_month` and `Time#next_month`. + + Before: + ``` + Time.new(2017, 9, 16, 17, 0).prev_month # => 2017-08-16 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).prev_month(1) + # => ArgumentError: wrong number of arguments (given 1, expected 0) + + Time.new(2017, 9, 16, 17, 0).next_month # => 2017-10-16 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).next_month(1) + # => ArgumentError: wrong number of arguments (given 1, expected 0) + ``` + + After: + ``` + Time.new(2017, 9, 16, 17, 0).prev_month # => 2017-08-16 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).prev_month(1) # => 2017-08-16 17:00:00 +0300 + + Time.new(2017, 9, 16, 17, 0).next_month # => 2017-10-16 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).next_month(1) # => 2017-10-16 17:00:00 +0300 + ``` + + *bogdanvlviv* + +* Add same method signature for `Time#prev_day` and `Time#next_day` + in accordance with `Date#prev_day`, `Date#next_day`. + + Allows pass argument for `Time#prev_day` and `Time#next_day`. + + Before: + ``` + Time.new(2017, 9, 16, 17, 0).prev_day # => 2017-09-15 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).prev_day(1) + # => ArgumentError: wrong number of arguments (given 1, expected 0) + + Time.new(2017, 9, 16, 17, 0).next_day # => 2017-09-17 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).next_day(1) + # => ArgumentError: wrong number of arguments (given 1, expected 0) + ``` + + After: + ``` + Time.new(2017, 9, 16, 17, 0).prev_day # => 2017-09-15 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).prev_day(1) # => 2017-09-15 17:00:00 +0300 + + Time.new(2017, 9, 16, 17, 0).next_day # => 2017-09-17 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).next_day(1) # => 2017-09-17 17:00:00 +0300 + ``` + + *bogdanvlviv* + * `IO#to_json` now returns the `to_s` representation, rather than attempting to convert to an array. This fixes a bug where `IO#to_json` would raise an `IOError` when called on an unreadable object. diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE index 6b3cead1a7..8f769c0767 100644 --- a/activesupport/MIT-LICENSE +++ b/activesupport/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2005-2017 David Heinemeier Hansson +Copyright (c) 2005-2018 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/README.rdoc b/activesupport/README.rdoc index 8b47933bd2..c770324be8 100644 --- a/activesupport/README.rdoc +++ b/activesupport/README.rdoc @@ -30,7 +30,7 @@ API documentation is at: * http://api.rubyonrails.org -Bug reports can be filed for the Ruby on Rails project here: +Bug reports for the Ruby on Rails project can be filed here: * https://github.com/rails/rails/issues diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 68be94f99d..a4fb697669 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true #-- -# Copyright (c) 2005-2017 David Heinemeier Hansson +# Copyright (c) 2005-2018 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -53,6 +53,7 @@ module ActiveSupport autoload :Callbacks autoload :Configurable autoload :Deprecation + autoload :Digest autoload :Gzip autoload :Inflector autoload :JSON diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 976104e0c4..8301b8c7cb 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -12,10 +12,11 @@ require "active_support/core_ext/string/inflections" module ActiveSupport # See ActiveSupport::Cache::Store for documentation. module Cache - autoload :FileStore, "active_support/cache/file_store" - autoload :MemoryStore, "active_support/cache/memory_store" - autoload :MemCacheStore, "active_support/cache/mem_cache_store" - autoload :NullStore, "active_support/cache/null_store" + autoload :FileStore, "active_support/cache/file_store" + autoload :MemoryStore, "active_support/cache/memory_store" + autoload :MemCacheStore, "active_support/cache/mem_cache_store" + autoload :NullStore, "active_support/cache/null_store" + autoload :RedisCacheStore, "active_support/cache/redis_cache_store" # These options mean something to all cache implementations. Individual cache # implementations may support additional options. @@ -148,12 +149,11 @@ module ActiveSupport # cache.namespace = -> { @last_mod_time } # Set the namespace to a variable # @last_mod_time = Time.now # Invalidate the entire cache by changing namespace # - # Caches can also store values in a compressed format to save space and - # reduce time spent sending data. Since there is overhead, values must be - # large enough to warrant compression. To turn on compression either pass - # <tt>compress: true</tt> in the initializer or as an option to +fetch+ - # or +write+. To specify the threshold at which to compress values, set the - # <tt>:compress_threshold</tt> option. The default threshold is 16K. + # Cached data larger than 1kB are compressed by default. To turn off + # compression, pass <tt>compress: false</tt> to the initializer or to + # individual +fetch+ or +write+ method calls. The 1kB compression + # threshold is configurable with the <tt>:compress_threshold</tt> option, + # specified in bytes. class Store cattr_accessor :logger, instance_writer: true @@ -212,8 +212,7 @@ module ActiveSupport # ask whether you should force a cache write. Otherwise, it's clearer to # just call <tt>Cache#write</tt>. # - # Setting <tt>:compress</tt> will store a large cache entry set by the call - # in a compressed format. + # Setting <tt>compress: false</tt> disables compression of the cache entry. # # Setting <tt>:expires_in</tt> will set an expiration time on the cache. # All caches support auto-expiring content after a specified number of @@ -562,14 +561,34 @@ module ActiveSupport end end - # Prefixes a key with the namespace. Namespace and key will be delimited - # with a colon. - def normalize_key(key, options) - key = expanded_key(key) - namespace = options[:namespace] if options - prefix = namespace.is_a?(Proc) ? namespace.call : namespace - key = "#{prefix}:#{key}" if prefix - key + # Expands and namespaces the cache key. May be overridden by + # cache stores to do additional normalization. + def normalize_key(key, options = nil) + namespace_key expanded_key(key), options + end + + # Prefix the key with a namespace string: + # + # namespace_key 'foo', namespace: 'cache' + # # => 'cache:foo' + # + # With a namespace block: + # + # namespace_key 'foo', namespace: -> { 'cache' } + # # => 'cache:foo' + def namespace_key(key, options = nil) + options = merged_options(options) + namespace = options[:namespace] + + if namespace.respond_to?(:call) + namespace = namespace.call + end + + if namespace + "#{namespace}:#{key}" + else + key + end end # Expands key to be a consistent string value. Invokes +cache_key+ if @@ -658,7 +677,7 @@ module ActiveSupport class Entry # :nodoc: attr_reader :version - DEFAULT_COMPRESS_LIMIT = 16.kilobytes + DEFAULT_COMPRESS_LIMIT = 1.kilobyte # Creates a new cache entry for the specified value. Options supported are # +:compress+, +:compress_threshold+, and +:expires_in+. @@ -733,8 +752,8 @@ module ActiveSupport private def should_compress?(value, options) - if value && options[:compress] - compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT + if value && options.fetch(:compress, true) + compress_threshold = options.fetch(:compress_threshold, DEFAULT_COMPRESS_LIMIT) serialized_value_size = (value.is_a?(String) ? value : Marshal.dump(value)).bytesize return true if serialized_value_size >= compress_threshold diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 0812cc34c7..a0f44aac0f 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -121,7 +121,7 @@ module ActiveSupport fname = URI.encode_www_form_component(key) if fname.size > FILEPATH_MAX_SIZE - fname = Digest::MD5.hexdigest(key) + fname = ActiveSupport::Digest.hexdigest(key) end hash = Zlib.adler32(fname) diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 9242a334f2..df8bc8e43e 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -7,7 +7,6 @@ rescue LoadError => e raise e end -require "digest/md5" require "active_support/core_ext/marshal" require "active_support/core_ext/array/extract_options" @@ -122,7 +121,7 @@ module ActiveSupport options = merged_options(options) instrument(:increment, name, amount: amount) do rescue_error_with nil do - @data.incr(normalize_key(name, options), amount) + @data.incr(normalize_key(name, options), amount, options[:expires_in]) end end end @@ -135,7 +134,7 @@ module ActiveSupport options = merged_options(options) instrument(:decrement, name, amount: amount) do rescue_error_with nil do - @data.decr(normalize_key(name, options), amount) + @data.decr(normalize_key(name, options), amount, options[:expires_in]) end end end @@ -183,7 +182,7 @@ module ActiveSupport key = super.dup key = key.force_encoding(Encoding::ASCII_8BIT) key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" } - key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250 + key = "#{key[0, 213]}:md5:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250 key end diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb new file mode 100644 index 0000000000..6cc45f5284 --- /dev/null +++ b/activesupport/lib/active_support/cache/redis_cache_store.rb @@ -0,0 +1,406 @@ +# frozen_string_literal: true + +begin + gem "redis", ">= 4.0.1" + require "redis" + require "redis/distributed" +rescue LoadError + warn "The Redis cache store requires the redis gem, version 4.0.1 or later. Please add it to your Gemfile: `gem \"redis\", \"~> 4.0\"`" + raise +end + +# Prefer the hiredis driver but don't require it. +begin + require "redis/connection/hiredis" +rescue LoadError +end + +require "digest/sha2" +require "active_support/core_ext/marshal" + +module ActiveSupport + module Cache + # Redis cache store. + # + # Deployment note: Take care to use a *dedicated Redis cache* rather + # than pointing this at your existing Redis server. It won't cope well + # with mixed usage patterns and it won't expire cache entries by default. + # + # Redis cache server setup guide: https://redis.io/topics/lru-cache + # + # * Supports vanilla Redis, hiredis, and Redis::Distributed. + # * Supports Memcached-like sharding across Redises with Redis::Distributed. + # * Fault tolerant. If the Redis server is unavailable, no exceptions are + # raised. Cache fetches are all misses and writes are dropped. + # * Local cache. Hot in-memory primary cache within block/middleware scope. + # * +read_multi+ and +write_multi+ support for Redis mget/mset. Use Redis::Distributed + # 4.0.1+ for distributed mget support. + # * +delete_matched+ support for Redis KEYS globs. + class RedisCacheStore < Store + # Keys are truncated with their own SHA2 digest if they exceed 1kB + MAX_KEY_BYTESIZE = 1024 + + DEFAULT_REDIS_OPTIONS = { + connect_timeout: 20, + read_timeout: 1, + write_timeout: 1, + reconnect_attempts: 0, + } + + DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:) do + if logger + logger.error { "RedisCacheStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" } + end + end + + DELETE_GLOB_LUA = "for i, name in ipairs(redis.call('KEYS', ARGV[1])) do redis.call('DEL', name); end" + private_constant :DELETE_GLOB_LUA + + # Support raw values in the local cache strategy. + module LocalCacheWithRaw # :nodoc: + private + def read_entry(key, options) + entry = super + if options[:raw] && local_cache && entry + entry = deserialize_entry(entry.value) + end + entry + end + + 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 + super(key, raw_entry, options) + else + super + end + end + + def write_multi_entries(entries, options) + if options[:raw] && local_cache + raw_entries = entries.map do |key, entry| + raw_entry = Entry.new(entry.value.to_s) + raw_entry.expires_at = entry.expires_at + end.to_h + + super(raw_entries, options) + else + super + end + end + end + + prepend Strategy::LocalCache + prepend LocalCacheWithRaw + + class << self + # Factory method to create a new Redis instance. + # + # Handles four options: :redis block, :redis instance, single :url + # string, and multiple :url strings. + # + # Option Class Result + # :redis Proc -> options[:redis].call + # :redis Object -> options[:redis] + # :url String -> Redis.new(url: …) + # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …]) + # + def build_redis(redis: nil, url: nil, **redis_options) #:nodoc: + urls = Array(url) + + if redis.respond_to?(:call) + redis.call + elsif redis + redis + elsif urls.size > 1 + build_redis_distributed_client urls: urls, **redis_options + else + build_redis_client url: urls.first, **redis_options + end + end + + private + def build_redis_distributed_client(urls:, **redis_options) + ::Redis::Distributed.new([], DEFAULT_REDIS_OPTIONS.merge(redis_options)).tap do |dist| + urls.each { |u| dist.add_node url: u } + end + end + + def build_redis_client(url:, **redis_options) + ::Redis.new DEFAULT_REDIS_OPTIONS.merge(redis_options.merge(url: url)) + end + end + + attr_reader :redis_options + attr_reader :max_key_bytesize + + # Creates a new Redis cache store. + # + # Handles three options: block provided to instantiate, single URL + # provided, and multiple URLs provided. + # + # :redis Proc -> options[:redis].call + # :url String -> Redis.new(url: …) + # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …]) + # + # No namespace is set by default. Provide one if the Redis cache + # server is shared with other apps: <tt>namespace: 'myapp-cache'<tt>. + # + # Compression is enabled by default with a 1kB threshold, so cached + # values larger than 1kB are automatically compressed. Disable by + # passing <tt>cache: false</tt> or change the threshold by passing + # <tt>compress_threshold: 4.kilobytes</tt>. + # + # No expiry is set on cache entries by default. Redis is expected to + # be configured with an eviction policy that automatically deletes + # least-recently or -frequently used keys when it reaches max memory. + # See https://redis.io/topics/lru-cache for cache server setup. + # + # Race condition TTL is not set by default. This can be used to avoid + # "thundering herd" cache writes when hot cache entries are expired. + # See <tt>ActiveSupport::Cache::Store#fetch</tt> for more. + def initialize(namespace: nil, compress: true, compress_threshold: 1.kilobyte, expires_in: nil, race_condition_ttl: nil, error_handler: DEFAULT_ERROR_HANDLER, **redis_options) + @redis_options = redis_options + + @max_key_bytesize = MAX_KEY_BYTESIZE + @error_handler = error_handler + + super namespace: namespace, + compress: compress, compress_threshold: compress_threshold, + expires_in: expires_in, race_condition_ttl: race_condition_ttl + end + + def redis + @redis ||= self.class.build_redis(**redis_options) + end + + def inspect + instance = @redis || @redis_options + "<##{self.class} options=#{options.inspect} redis=#{instance.inspect}>" + end + + # Cache Store API implementation. + # + # Read multiple values at once. Returns a hash of requested keys -> + # fetched values. + def read_multi(*names) + if mget_capable? + read_multi_mget(*names) + else + super + end + end + + # Cache Store API implementation. + # + # Supports Redis KEYS glob patterns: + # + # h?llo matches hello, hallo and hxllo + # h*llo matches hllo and heeeello + # h[ae]llo matches hello and hallo, but not hillo + # h[^e]llo matches hallo, hbllo, ... but not hello + # h[a-b]llo matches hallo and hbllo + # + # Use \ to escape special characters if you want to match them verbatim. + # + # See https://redis.io/commands/KEYS for more. + # + # Failsafe: Raises errors. + def delete_matched(matcher, options = nil) + instrument :delete_matched, matcher do + case matcher + when String + redis.eval DELETE_GLOB_LUA, [], [namespace_key(matcher, options)] + else + raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}" + end + end + end + + # Cache Store API implementation. + # + # Increment a cached value. This method uses the Redis incr atomic + # operator and can only be used on values written with the :raw option. + # Calling it on a value not stored with :raw will initialize that value + # to zero. + # + # Failsafe: Raises errors. + def increment(name, amount = 1, options = nil) + instrument :increment, name, amount: amount do + redis.incrby normalize_key(name, options), amount + end + end + + # Cache Store API implementation. + # + # Decrement a cached value. This method uses the Redis decr atomic + # operator and can only be used on values written with the :raw option. + # Calling it on a value not stored with :raw will initialize that value + # to zero. + # + # Failsafe: Raises errors. + def decrement(name, amount = 1, options = nil) + instrument :decrement, name, amount: amount do + redis.decrby normalize_key(name, options), amount + end + end + + # Cache Store API implementation. + # + # Removes expired entries. Handled natively by Redis least-recently-/ + # least-frequently-used expiry, so manual cleanup is not supported. + def cleanup(options = nil) + super + end + + # Clear the entire cache on all Redis servers. Safe to use on + # shared servers if the cache is namespaced. + # + # Failsafe: Raises errors. + def clear(options = nil) + failsafe :clear do + if namespace = merged_options(options)[namespace] + delete_matched "*", namespace: namespace + else + redis.flushdb + end + end + end + + def mget_capable? #:nodoc: + set_redis_capabilities unless defined? @mget_capable + @mget_capable + end + + def mset_capable? #:nodoc: + set_redis_capabilities unless defined? @mset_capable + @mset_capable + end + + private + def set_redis_capabilities + case redis + when Redis::Distributed + @mget_capable = true + @mset_capable = false + else + @mget_capable = true + @mset_capable = true + end + end + + # Store provider interface: + # Read an entry from the cache. + def read_entry(key, options = nil) + failsafe :read_entry do + deserialize_entry redis.get(key) + end + end + + def read_multi_mget(*names) + options = names.extract_options! + options = merged_options(options) + + keys = names.map { |name| normalize_key(name, options) } + values = redis.mget(*keys) + + names.zip(values).each_with_object({}) do |(name, value), results| + if value + entry = deserialize_entry(value) + unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options)) + results[name] = entry.value + end + end + end + end + + # Write an entry to the cache. + # + # Requires Redis 2.6.12+ for extended SET options. + def write_entry(key, entry, unless_exist: false, raw: false, expires_in: nil, race_condition_ttl: nil, **options) + value = raw ? entry.value.to_s : serialize_entry(entry) + + # If race condition TTL is in use, ensure that cache entries + # stick around a bit longer after they would have expired + # so we can purposefully serve stale entries. + if race_condition_ttl && expires_in && expires_in > 0 && !raw + expires_in += 5.minutes + end + + failsafe :write_entry do + if unless_exist || expires_in + modifiers = {} + modifiers[:nx] = unless_exist + modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in + + redis.set key, value, modifiers + else + redis.set key, value + end + end + end + + # Delete an entry from the cache. + def delete_entry(key, options) + failsafe :delete_entry, returning: false do + redis.del key + end + end + + # Nonstandard store provider API to write multiple values at once. + def write_multi_entries(entries, expires_in: nil, **options) + if entries.any? + if mset_capable? && expires_in.nil? + failsafe :write_multi_entries do + redis.mapped_mset(entries) + end + else + super + end + end + end + + # Truncate keys that exceed 1kB. + def normalize_key(key, options) + truncate_key super + end + + def truncate_key(key) + if key.bytesize > max_key_bytesize + suffix = ":sha2:#{Digest::SHA2.hexdigest(key)}" + truncate_at = max_key_bytesize - suffix.bytesize + "#{key.byteslice(0, truncate_at)}#{suffix}" + else + key + end + end + + def deserialize_entry(raw_value) + if raw_value + entry = Marshal.load(raw_value) rescue raw_value + entry.is_a?(Entry) ? entry : Entry.new(entry) + end + end + + def serialize_entry(entry) + Marshal.dump(entry) + end + + def failsafe(method, returning: nil) + yield + rescue ::Redis::BaseConnectionError => e + handle_exception exception: e, method: method, returning: returning + returning + end + + def handle_exception(exception:, method:, returning:) + if @error_handler + @error_handler.(method: method, exception: exception, returning: returning) + end + rescue => failsafe + warn "RedisCacheStore ignored exception in handle_exception: #{failsafe.class}: #{failsafe.message}\n #{failsafe.backtrace.join("\n ")}" + end + end + end +end diff --git a/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb b/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb new file mode 100644 index 0000000000..a8455c0048 --- /dev/null +++ b/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "monitor" + +module ActiveSupport + module Concurrency + # A monitor that will permit dependency loading while blocked waiting for + # the lock. + class LoadInterlockAwareMonitor < Monitor + # Enters an exclusive section, but allows dependency loading while blocked + def mon_enter + mon_try_enter || + ActiveSupport::Dependencies.interlock.permit_concurrent_loads { super } + end + end + end +end diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index d67f99df0e..b7ff7a3907 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -35,8 +35,8 @@ class Array # people.without "Aaron", "Todd" # # => ["David", "Rafael"] # - # Note: This is an optimization of `Enumerable#without` that uses `Array#-` - # instead of `Array#reject` for performance reasons. + # Note: This is an optimization of <tt>Enumerable#without</tt> that uses <tt>Array#-</tt> + # instead of <tt>Array#reject</tt> for performance reasons. def without(*elements) self - elements end diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb index 265c6949e1..f6cb1a384c 100644 --- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb @@ -20,9 +20,9 @@ module DateAndTime advance(days: -1) end - # Returns a new date/time representing the previous day. - def prev_day - advance(days: -1) + # Returns a new date/time the specified number of days ago. + def prev_day(days = 1) + advance(days: -days) end # Returns a new date/time representing tomorrow. @@ -30,9 +30,9 @@ module DateAndTime advance(days: 1) end - # Returns a new date/time representing the next day. - def next_day - advance(days: 1) + # Returns a new date/time the specified number of days in the future. + def next_day(days = 1) + advance(days: days) end # Returns true if the date/time is today. @@ -188,9 +188,9 @@ module DateAndTime end end - # Short-hand for months_since(1). - def next_month - months_since(1) + # Returns a new date/time the specified number of months in the future. + def next_month(months = 1) + advance(months: months) end # Short-hand for months_since(3) @@ -198,9 +198,9 @@ module DateAndTime months_since(3) end - # Short-hand for years_since(1). - def next_year - years_since(1) + # Returns a new date/time the specified number of years in the future. + def next_year(years = 1) + advance(years: years) end # Returns a new date/time representing the given day in the previous week. @@ -223,11 +223,15 @@ module DateAndTime end alias_method :last_weekday, :prev_weekday + # Returns a new date/time the specified number of months ago. + def prev_month(months = 1) + advance(months: -months) + end + # Short-hand for months_ago(1). - def prev_month + def last_month months_ago(1) end - alias_method :last_month, :prev_month # Short-hand for months_ago(3). def prev_quarter @@ -235,11 +239,15 @@ module DateAndTime end alias_method :last_quarter, :prev_quarter + # Returns a new date/time the specified number of years ago. + def prev_year(years = 1) + advance(years: -years) + end + # Short-hand for years_ago(1). - def prev_year + def last_year years_ago(1) end - alias_method :last_year, :prev_year # Returns the number of days to the start of the week on the given day. # Week is assumed to start on +start_day+, default is @@ -322,20 +330,28 @@ module DateAndTime beginning_of_year..end_of_year end - # Returns specific next occurring day of week + # Returns a new date/time representing the next occurrence of the specified day of week. + # + # today = Date.today # => Thu, 14 Dec 2017 + # today.next_occurring(:monday) # => Mon, 18 Dec 2017 + # today.next_occurring(:thursday) # => Thu, 21 Dec 2017 def next_occurring(day_of_week) current_day_number = wday != 0 ? wday - 1 : 6 from_now = DAYS_INTO_WEEK.fetch(day_of_week) - current_day_number from_now += 7 unless from_now > 0 - since(from_now.days) + advance(days: from_now) end - # Returns specific previous occurring day of week + # Returns a new date/time representing the previous occurrence of the specified day of week. + # + # today = Date.today # => Thu, 14 Dec 2017 + # today.prev_occurring(:monday) # => Mon, 11 Dec 2017 + # today.prev_occurring(:thursday) # => Thu, 07 Dec 2017 def prev_occurring(day_of_week) current_day_number = wday != 0 ? wday - 1 : 6 ago = current_day_number - DAYS_INTO_WEEK.fetch(day_of_week) ago += 7 unless ago > 0 - ago(ago.days) + advance(days: -ago) end private diff --git a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb index 424f64d6fa..2d6b49722d 100644 --- a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb +++ b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb @@ -8,8 +8,8 @@ class DateTime silence_redefinition_of_method :to_time - # Either return an instance of `Time` with the same UTC offset - # as +self+ or an instance of `Time` representing the same time + # Either return an instance of +Time+ with the same UTC offset + # as +self+ or an instance of +Time+ representing the same time # in the the local system timezone depending on the setting of # on the setting of +ActiveSupport.to_time_preserves_timezone+. def to_time diff --git a/activesupport/lib/active_support/core_ext/module/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb index 370a948eea..7bbbf321ab 100644 --- a/activesupport/lib/active_support/core_ext/module/concerning.rb +++ b/activesupport/lib/active_support/core_ext/module/concerning.rb @@ -22,7 +22,7 @@ class Module # # == Using comments: # - # class Todo + # class Todo < ApplicationRecord # # Other todo implementation # # ... # @@ -30,7 +30,6 @@ class Module # has_many :events # # before_create :track_creation - # after_destroy :track_deletion # # private # def track_creation @@ -42,7 +41,7 @@ class Module # # Noisy syntax. # - # class Todo + # class Todo < ApplicationRecord # # Other todo implementation # # ... # @@ -52,7 +51,6 @@ class Module # included do # has_many :events # before_create :track_creation - # after_destroy :track_deletion # end # # private @@ -70,7 +68,7 @@ class Module # increased overhead can be a reasonable tradeoff even if it reduces our # at-a-glance perception of how things work. # - # class Todo + # class Todo < ApplicationRecord # # Other todo implementation # # ... # @@ -82,7 +80,7 @@ class Module # By quieting the mix-in noise, we arrive at a natural, low-ceremony way to # separate bite-sized concerns. # - # class Todo + # class Todo < ApplicationRecord # # Other todo implementation # # ... # @@ -90,7 +88,6 @@ class Module # included do # has_many :events # before_create :track_creation - # after_destroy :track_deletion # end # # private @@ -101,7 +98,7 @@ class Module # end # # Todo.ancestors - # # => [Todo, Todo::EventTracking, Object] + # # => [Todo, Todo::EventTracking, ApplicationRecord, Object] # # This small step has some wonderful ripple effects. We can # * grok the behavior of our class in one glance, diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index a77f903db5..4310df3024 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -115,11 +115,8 @@ class Module # invoice.customer_address # => 'Vimmersvej 13' # # If the target is +nil+ and does not respond to the delegated method a - # +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. + # +Module::DelegationError+ is raised. If you wish to instead return +nil+, + # use the <tt>:allow_nil</tt> option. # # class User < ActiveRecord::Base # has_one :profile diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index 1744a44df6..9bb99087bc 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -108,8 +108,8 @@ require "bigdecimal" class BigDecimal # BigDecimals are duplicable: # - # BigDecimal.new("1.2").duplicable? # => true - # BigDecimal.new("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)> + # BigDecimal("1.2").duplicable? # => true + # BigDecimal("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)> def duplicable? true end diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb index b6c464db33..2838fd76be 100644 --- a/activesupport/lib/active_support/core_ext/object/with_options.rb +++ b/activesupport/lib/active_support/core_ext/object/with_options.rb @@ -62,7 +62,7 @@ class Object # # validates :content, length: { minimum: 50 }, if: -> { content.present? } # - # Hence the inherited default for `if` key is ignored. + # Hence the inherited default for +if+ key is ignored. # # NOTE: You cannot call class methods implicitly inside of with_options. # You can access these methods using the class name instead: diff --git a/activesupport/lib/active_support/core_ext/range.rb b/activesupport/lib/active_support/core_ext/range.rb index 51ae0ddd21..4074e91d17 100644 --- a/activesupport/lib/active_support/core_ext/range.rb +++ b/activesupport/lib/active_support/core_ext/range.rb @@ -2,5 +2,6 @@ require "active_support/core_ext/range/conversions" require "active_support/core_ext/range/include_range" +require "active_support/core_ext/range/include_time_with_zone" require "active_support/core_ext/range/overlaps" require "active_support/core_ext/range/each" diff --git a/activesupport/lib/active_support/core_ext/range/each.rb b/activesupport/lib/active_support/core_ext/range/each.rb index cdff6393d7..2f22cd0e92 100644 --- a/activesupport/lib/active_support/core_ext/range/each.rb +++ b/activesupport/lib/active_support/core_ext/range/each.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "active_support/time_with_zone" + module ActiveSupport module EachTimeWithZone #:nodoc: def each(&block) @@ -15,7 +17,7 @@ module ActiveSupport private def ensure_iteration_allowed - raise TypeError, "can't iterate from #{first.class}" if first.is_a?(Time) + raise TypeError, "can't iterate from #{first.class}" if first.is_a?(TimeWithZone) end end end diff --git a/activesupport/lib/active_support/core_ext/range/include_time_with_zone.rb b/activesupport/lib/active_support/core_ext/range/include_time_with_zone.rb new file mode 100644 index 0000000000..5f80acf68e --- /dev/null +++ b/activesupport/lib/active_support/core_ext/range/include_time_with_zone.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "active_support/time_with_zone" + +module ActiveSupport + module IncludeTimeWithZone #:nodoc: + # Extends the default Range#include? to support ActiveSupport::TimeWithZone. + # + # (1.hour.ago..1.hour.from_now).include?(Time.current) # => true + # + def include?(value) + if first.is_a?(TimeWithZone) + cover?(value) + elsif last.is_a?(TimeWithZone) + cover?(value) + else + super + end + end + end +end + +Range.prepend(ActiveSupport::IncludeTimeWithZone) diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index da53739efc..8af301734a 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -174,7 +174,7 @@ class String # <%= link_to(@person.name, person_path) %> # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a> # - # To preserve the case of the characters in a string, use the `preserve_case` argument. + # To preserve the case of the characters in a string, use the +preserve_case+ argument. # # class Person # def to_param diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb index 38224ea5da..07c0d16398 100644 --- a/activesupport/lib/active_support/core_ext/string/multibyte.rb +++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb @@ -16,6 +16,8 @@ class String # >> "lj".mb_chars.upcase.to_s # => "LJ" # + # NOTE: An above example is useful for pre Ruby 2.4. Ruby 2.4 supports Unicode case mappings. + # # == Method chaining # # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows 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 b712200959..f3bdc2977e 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -250,7 +250,7 @@ class String # Marks a string as trusted safe. It will be inserted into HTML with no # additional escaping performed. It is your responsibility to ensure that the # string contains no malicious content. This method is equivalent to the - # `raw` helper in views. It is recommended that you use `sanitize` instead of + # +raw+ helper in views. It is recommended that you use +sanitize+ instead of # this method. It should never be called on user input. def html_safe ActiveSupport::SafeBuffer.new(self) diff --git a/activesupport/lib/active_support/deprecation/constant_accessor.rb b/activesupport/lib/active_support/deprecation/constant_accessor.rb index 3d7eedf637..1ed0015812 100644 --- a/activesupport/lib/active_support/deprecation/constant_accessor.rb +++ b/activesupport/lib/active_support/deprecation/constant_accessor.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "active_support/inflector/methods" - module ActiveSupport class Deprecation # DeprecatedConstantAccessor transforms a constant into a deprecated one by @@ -17,7 +15,7 @@ module ActiveSupport # # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto) # - # (In a later update, the original implementation of `PLANETS` has been removed.) + # # (In a later update, the original implementation of `PLANETS` has been removed.) # # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune) # include ActiveSupport::Deprecation::DeprecatedConstantAccessor @@ -29,6 +27,8 @@ module ActiveSupport # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] module DeprecatedConstantAccessor def self.included(base) + require "active_support/inflector/methods" + extension = Module.new do def const_missing(missing_const_name) if class_variable_defined?(:@@_deprecated_constants) diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb index d359992bf5..5be893d281 100644 --- a/activesupport/lib/active_support/deprecation/method_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb @@ -8,9 +8,7 @@ module ActiveSupport module MethodWrapper # Declare that a method has been deprecated. # - # module Fred - # extend self - # + # class Fred # def aaa; end # def bbb; end # def ccc; end @@ -22,15 +20,15 @@ module ActiveSupport # ActiveSupport::Deprecation.deprecate_methods(Fred, :aaa, bbb: :zzz, ccc: 'use Bar#ccc instead') # # => Fred # - # Fred.aaa + # Fred.new.aaa # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.1. (called from irb_binding at (irb):10) # # => nil # - # Fred.bbb + # Fred.new.bbb # # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.1 (use zzz instead). (called from irb_binding at (irb):11) # # => nil # - # Fred.ccc + # Fred.new.ccc # # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.1 (use Bar#ccc instead). (called from irb_binding at (irb):12) # # => nil # @@ -39,7 +37,7 @@ module ActiveSupport # ActiveSupport::Deprecation.deprecate_methods(Fred, ddd: :zzz, deprecator: custom_deprecator) # # => [:ddd] # - # Fred.ddd + # Fred.new.ddd # DEPRECATION WARNING: ddd is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):15) # # => nil # @@ -48,7 +46,7 @@ module ActiveSupport # custom_deprecator.deprecate_methods(Fred, eee: :zzz) # # => [:eee] # - # Fred.eee + # Fred.new.eee # DEPRECATION WARNING: eee is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):18) # # => nil def deprecate_methods(target_module, *method_names) @@ -62,6 +60,13 @@ module ActiveSupport deprecator.deprecation_warning(method_name, options[method_name]) super(*args, &block) end + + case + when target_module.protected_method_defined?(method_name) + protected method_name + when target_module.private_method_defined?(method_name) + private method_name + end end end diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index f6c6648917..896c0d2d8e 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "active_support/inflector/methods" require "active_support/core_ext/regexp" module ActiveSupport @@ -114,7 +113,7 @@ module ActiveSupport # # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto) # - # (In a later update, the original implementation of `PLANETS` has been removed.) + # # (In a later update, the original implementation of `PLANETS` has been removed.) # # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune) # PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006') @@ -125,6 +124,8 @@ module ActiveSupport # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] class DeprecatedConstantProxy < DeprecationProxy def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance, message: "#{old_const} is deprecated! Use #{new_const} instead.") + require "active_support/inflector/methods" + @old_const = old_const @new_const = new_const @deprecator = deprecator diff --git a/activesupport/lib/active_support/digest.rb b/activesupport/lib/active_support/digest.rb new file mode 100644 index 0000000000..fba10fbdcf --- /dev/null +++ b/activesupport/lib/active_support/digest.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module ActiveSupport + class Digest #:nodoc: + class <<self + def hash_digest_class + @hash_digest_class ||= ::Digest::MD5 + end + + def hash_digest_class=(klass) + raise ArgumentError, "#{klass} is expected to implement hexdigest class method" unless klass.respond_to?(:hexdigest) + @hash_digest_class = klass + end + + def hexdigest(arg) + hash_digest_class.hexdigest(arg)[0...32] + end + end + end +end diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 1af3411a8a..fe1058762b 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -194,7 +194,6 @@ module ActiveSupport end parts[:seconds] = remainder - parts.reject! { |k, v| v.zero? } new(value, parts) end @@ -211,6 +210,7 @@ module ActiveSupport def initialize(value, parts) #:nodoc: @value, @parts = value, parts.to_h @parts.default = 0 + @parts.reject! { |k, v| v.zero? } end def coerce(other) #:nodoc: @@ -370,6 +370,8 @@ module ActiveSupport alias :before :ago def inspect #:nodoc: + return "0 seconds" if parts.empty? + parts. reduce(::Hash.new(0)) { |h, (l, r)| h[l] += r; h }. sort_by { |unit, _ | PARTS.index(unit) }. diff --git a/activesupport/lib/active_support/encrypted_configuration.rb b/activesupport/lib/active_support/encrypted_configuration.rb index b403048627..dab953d5d5 100644 --- a/activesupport/lib/active_support/encrypted_configuration.rb +++ b/activesupport/lib/active_support/encrypted_configuration.rb @@ -11,8 +11,9 @@ module ActiveSupport delegate :[], :fetch, to: :config delegate_missing_to :options - def initialize(config_path:, key_path:, env_key:) - super content_path: config_path, key_path: key_path, env_key: env_key + def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:) + super content_path: config_path, key_path: key_path, + env_key: env_key, raise_if_missing_key: raise_if_missing_key end # Allow a config to be started without a file present @@ -22,6 +23,12 @@ module ActiveSupport "" end + def write(contents) + deserialize(contents) + + super + end + def config @config ||= deserialize(read).deep_symbolize_keys end @@ -36,7 +43,7 @@ module ActiveSupport end def deserialize(config) - config.present? ? YAML.load(config) : {} + config.present? ? YAML.load(config, content_path) : {} end end end diff --git a/activesupport/lib/active_support/encrypted_file.rb b/activesupport/lib/active_support/encrypted_file.rb index 1c4c1cb457..671b6b6a69 100644 --- a/activesupport/lib/active_support/encrypted_file.rb +++ b/activesupport/lib/active_support/encrypted_file.rb @@ -2,8 +2,6 @@ require "pathname" require "active_support/message_encryptor" -require "active_support/core_ext/string/strip" -require "active_support/core_ext/module/delegation" module ActiveSupport class EncryptedFile @@ -28,11 +26,11 @@ module ActiveSupport end - attr_reader :content_path, :key_path, :env_key + attr_reader :content_path, :key_path, :env_key, :raise_if_missing_key - def initialize(content_path:, key_path:, env_key:) + def initialize(content_path:, key_path:, env_key:, raise_if_missing_key:) @content_path, @key_path = Pathname.new(content_path), Pathname.new(key_path) - @env_key = env_key + @env_key, @raise_if_missing_key = env_key, raise_if_missing_key end def key @@ -40,7 +38,7 @@ module ActiveSupport end def read - if content_path.exist? + if !key.nil? && content_path.exist? decrypt content_path.binread else raise MissingContentError, content_path @@ -95,7 +93,7 @@ module ActiveSupport end def handle_missing_key - raise MissingKeyError, key_path: key_path, env_key: env_key + raise MissingKeyError, key_path: key_path, env_key: env_key if raise_if_missing_key end end end diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb index 2a7ef2f820..1e09adbb52 100644 --- a/activesupport/lib/active_support/gem_version.rb +++ b/activesupport/lib/active_support/gem_version.rb @@ -10,7 +10,7 @@ module ActiveSupport MAJOR = 5 MINOR = 2 TINY = 0 - PRE = "alpha" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index a639eddf10..0450a4be4c 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -4,6 +4,7 @@ require "concurrent/map" require "active_support/core_ext/array/prepend_and_append" require "active_support/core_ext/regexp" require "active_support/i18n" +require "active_support/deprecation" module ActiveSupport module Inflector @@ -67,16 +68,21 @@ module ActiveSupport end attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex + deprecate :acronym_regex + + attr_reader :acronyms_camelize_regex, :acronyms_underscore_regex # :nodoc: def initialize - @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], Uncountables.new, [], {}, /(?=a)b/ + @plurals, @singulars, @uncountables, @humans, @acronyms = [], [], Uncountables.new, [], {} + define_acronym_regex_patterns end # Private, for the test suite. def initialize_dup(orig) # :nodoc: - %w(plurals singulars uncountables humans acronyms acronym_regex).each do |scope| + %w(plurals singulars uncountables humans acronyms).each do |scope| instance_variable_set("@#{scope}", orig.send(scope).dup) end + define_acronym_regex_patterns end # Specifies a new acronym. An acronym must be specified as it will appear @@ -130,7 +136,7 @@ module ActiveSupport # camelize 'mcdonald' # => 'McDonald' def acronym(word) @acronyms[word.downcase] = word - @acronym_regex = /#{@acronyms.values.join("|")}/ + define_acronym_regex_patterns end # Specifies a new pluralization rule and its replacement. The rule can @@ -225,6 +231,14 @@ module ActiveSupport instance_variable_set "@#{scope}", [] end end + + private + + def define_acronym_regex_patterns + @acronym_regex = @acronyms.empty? ? /(?=a)b/ : /#{@acronyms.values.join("|")}/ + @acronyms_camelize_regex = /^(?:#{@acronym_regex}(?=\b|[A-Z_])|\w)/ + @acronyms_underscore_regex = /(?:(?<=([A-Za-z\d]))|\b)(#{@acronym_regex})(?=\b|[^a-z])/ + end end # Yields a singleton instance of Inflector::Inflections so you can specify diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 72de42b1c3..60eeaa77cb 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -71,7 +71,7 @@ module ActiveSupport if uppercase_first_letter string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize } else - string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { |match| match.downcase } + string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase } end string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" } string.gsub!("/".freeze, "::".freeze) @@ -92,7 +92,7 @@ module ActiveSupport def underscore(camel_cased_word) return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word) word = camel_cased_word.to_s.gsub("::".freeze, "/".freeze) - word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" } + word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_'.freeze }#{$2.downcase}" } word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze) word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze) word.tr!("-".freeze, "_".freeze) @@ -138,7 +138,7 @@ module ActiveSupport result.tr!("_".freeze, " ".freeze) result.gsub!(/([a-z\d]*)/i) do |match| - "#{inflections.acronyms[match] || match.downcase}" + "#{inflections.acronyms[match.downcase] || match.downcase}" end if capitalize diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index 9801f1d118..6f2ca4999c 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -71,17 +71,23 @@ module ActiveSupport # a 'pretty' URL. # # parameterize("Donald E. Knuth") # => "donald-e-knuth" - # parameterize("^trés|Jolie-- ") # => "tres-jolie" + # parameterize("^très|Jolie-- ") # => "tres-jolie" # - # To use a custom separator, override the `separator` argument. + # To use a custom separator, override the +separator+ argument. # # parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth" - # parameterize("^trés|Jolie-- ", separator: '_') # => "tres_jolie" + # parameterize("^très|Jolie__ ", separator: '_') # => "tres_jolie" # - # To preserve the case of the characters in a string, use the `preserve_case` argument. + # To preserve the case of the characters in a string, use the +preserve_case+ argument. # # parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth" - # parameterize("^trés|Jolie-- ", preserve_case: true) # => "tres-Jolie" + # parameterize("^très|Jolie-- ", preserve_case: true) # => "tres-Jolie" + # + # It preserves dashes and underscores unless they are used as separators: + # + # parameterize("^très|Jolie__ ") # => "tres-jolie__" + # parameterize("^très|Jolie-- ", separator: "_") # => "tres_jolie--" + # parameterize("^très_Jolie-- ", separator: ".") # => "tres_jolie--" # def parameterize(string, separator: "-", preserve_case: false) # Replace accented chars with their ASCII equivalents. diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 69c95e0622..27fd061947 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -58,8 +58,8 @@ module ActiveSupport # === Rotating keys # # MessageEncryptor also supports rotating out old configurations by falling - # back to a stack of encryptors. Call `rotate` to build and add an encryptor - # so `decrypt_and_verify` will also try the fallback. + # back to a stack of encryptors. Call +rotate+ to build and add an encryptor + # so +decrypt_and_verify+ will also try the fallback. # # By default any rotated encryptors use the values of the primary # encryptor unless specified otherwise. diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 622b66ee55..83c39c0a86 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -31,7 +31,7 @@ module ActiveSupport # # +MessageVerifier+ creates HMAC signatures using SHA1 hash algorithm by default. # If you want to use a different hash algorithm, you can change it by providing - # `:digest` key as an option while initializing the verifier: + # +:digest+ key as an option while initializing the verifier: # # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256') # @@ -78,8 +78,8 @@ module ActiveSupport # === Rotating keys # # MessageVerifier also supports rotating out old configurations by falling - # back to a stack of verifiers. Call `rotate` to build and add a verifier to - # so either `verified` or `verify` will also try verifying with the fallback. + # back to a stack of verifiers. Call +rotate+ to build and add a verifier to + # so either +verified+ or +verify+ will also try verifying with the fallback. # # By default any rotated verifiers use the values of the primary # verifier unless specified otherwise. diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb index b7ad76bb62..eb528a0583 100644 --- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb @@ -37,14 +37,6 @@ module ActiveSupport private - def calculate_rounded_number(multiplier) - (number / BigDecimal.new(multiplier.to_f.to_s)).round * multiplier - end - - def digit_count(number) - number.zero? ? 1 : (Math.log10(absolute_number(number)) + 1).floor - end - def strip_insignificant_zeros options[:strip_insignificant_zeros] end diff --git a/activesupport/lib/active_support/number_helper/rounding_helper.rb b/activesupport/lib/active_support/number_helper/rounding_helper.rb index a5b28296a2..2ad8d49c4e 100644 --- a/activesupport/lib/active_support/number_helper/rounding_helper.rb +++ b/activesupport/lib/active_support/number_helper/rounding_helper.rb @@ -36,7 +36,7 @@ module ActiveSupport return 0 if number.zero? digits = digit_count(number) multiplier = 10**(digits - precision) - (number / BigDecimal.new(multiplier.to_f.to_s)).round * multiplier + (number / BigDecimal(multiplier.to_f.to_s)).round * multiplier end def convert_to_decimal(number) diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb index 8560eae110..91872e29c8 100644 --- a/activesupport/lib/active_support/railtie.rb +++ b/activesupport/lib/active_support/railtie.rb @@ -66,5 +66,12 @@ module ActiveSupport ActiveSupport.send(k, v) if ActiveSupport.respond_to? k end end + + initializer "active_support.set_hash_digest_class" do |app| + if app.config.active_support.respond_to?(:hash_digest_class) && app.config.active_support.hash_digest_class + ActiveSupport::Digest.hash_digest_class = + app.config.active_support.hash_digest_class + end + end end end diff --git a/activesupport/lib/active_support/reloader.rb b/activesupport/lib/active_support/reloader.rb index 6cd11b1a26..b26d9c3665 100644 --- a/activesupport/lib/active_support/reloader.rb +++ b/activesupport/lib/active_support/reloader.rb @@ -28,14 +28,17 @@ module ActiveSupport define_callbacks :class_unload + # Registers a callback that will run once at application startup and every time the code is reloaded. def self.to_prepare(*args, &block) set_callback(:prepare, *args, &block) end + # Registers a callback that will run immediately before the classes are unloaded. def self.before_class_unload(*args, &block) set_callback(:class_unload, *args, &block) end + # Registers a callback that will run immediately after the classes are unloaded. def self.after_class_unload(*args, &block) set_callback(:class_unload, :after, *args, &block) end diff --git a/activesupport/lib/active_support/security_utils.rb b/activesupport/lib/active_support/security_utils.rb index b6b31ef140..20b6b9cd3f 100644 --- a/activesupport/lib/active_support/security_utils.rb +++ b/activesupport/lib/active_support/security_utils.rb @@ -4,14 +4,12 @@ require "digest/sha2" module ActiveSupport module SecurityUtils - # Constant time string comparison. + # Constant time string comparison, for fixed length strings. # # The values compared should be of fixed length, such as strings - # that have already been processed by HMAC. This should not be used - # on variable length plaintext strings because it could leak length info - # via timing attacks. - def secure_compare(a, b) - return false unless a.bytesize == b.bytesize + # that have already been processed by HMAC. Raises in case of length mismatch. + def fixed_length_secure_compare(a, b) + raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize l = a.unpack "C#{a.bytesize}" @@ -19,11 +17,15 @@ module ActiveSupport b.each_byte { |byte| res |= byte ^ l.shift } res == 0 end - module_function :secure_compare + module_function :fixed_length_secure_compare - def variable_size_secure_compare(a, b) # :nodoc: - secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b)) + # Constant time string comparison, for variable length strings. + # + # The values are first processed by SHA256, so that we don't leak length info + # via timing attacks. + def secure_compare(a, b) + fixed_length_secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b)) && a == b end - module_function :variable_size_secure_compare + module_function :secure_compare end end diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index ee5286bd55..d1f7e6ea09 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -11,7 +11,6 @@ require "active_support/testing/isolation" require "active_support/testing/constant_lookup" require "active_support/testing/time_helpers" require "active_support/testing/file_fixtures" -require "active_support/core_ext/kernel/reporting" module ActiveSupport class TestCase < ::Minitest::Test diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index e2bc51ff7a..6f69c48674 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -156,11 +156,12 @@ module ActiveSupport after = exp.call - if to == UNTRACKED - error = "#{expression.inspect} didn't change" - error = "#{message}.\n#{error}" if message - assert_not_equal before, after, error - else + error = "#{expression.inspect} didn't change" + error = "#{error}. It was already #{to}" if before == to + error = "#{message}.\n#{error}" if message + assert before != after, error + + unless to == UNTRACKED error = "#{expression.inspect} didn't change to #{to}" error = "#{message}.\n#{error}" if message assert to === after, error @@ -190,7 +191,7 @@ module ActiveSupport error = "#{expression.inspect} did change to #{after}" error = "#{message}.\n#{error}" if message - assert_equal before, after, error + assert before == after, error retval end diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index 8c620e7f8c..998a51a34c 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "active_support/core_ext/module/redefine_method" require "active_support/core_ext/string/strip" # for strip_heredoc require "active_support/core_ext/time/calculations" require "concurrent/map" @@ -43,7 +44,7 @@ module ActiveSupport def unstub_object(stub) singleton_class = stub.object.singleton_class - singleton_class.send :undef_method, stub.method_name + singleton_class.send :silence_redefinition_of_method, stub.method_name singleton_class.send :alias_method, stub.method_name, stub.original_method singleton_class.send :undef_method, stub.original_method end diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 4d1fbd4453..4d81ac939e 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -30,7 +30,7 @@ module ActiveSupport class TimeZone # Keys are Rails TimeZone names, values are TZInfo identifiers. MAPPING = { - "International Date Line West" => "Pacific/Midway", + "International Date Line West" => "Etc/GMT+12", "Midway Island" => "Pacific/Midway", "American Samoa" => "Pacific/Pago_Pago", "Hawaii" => "Pacific/Honolulu", @@ -256,6 +256,13 @@ module ActiveSupport @country_zones[code] ||= load_country_zones(code) end + def clear #:nodoc: + @lazy_zones_map = Concurrent::Map.new + @country_zones = Concurrent::Map.new + @zones = nil + @zones_map = nil + end + private def load_country_zones(code) country = TZInfo::Country.get(code) @@ -269,9 +276,8 @@ module ActiveSupport end def zones_map - @zones_map ||= begin - MAPPING.each_key { |place| self[place] } # load all the zones - @lazy_zones_map + @zones_map ||= MAPPING.each_with_object({}) do |(name, _), zones| + zones[name] = self[name] end end end @@ -506,7 +512,7 @@ module ActiveSupport # Available so that TimeZone instances respond like TZInfo::Timezone # instances. def period_for_local(time, dst = true) - tzinfo.period_for_local(time, dst) + tzinfo.period_for_local(time, dst) { |periods| periods.last } end def periods_for_local(time) #:nodoc: diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index 3ac11e0fe0..f214898145 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -38,4 +38,8 @@ class ActiveSupport::TestCase private def jruby_skip(message = "") skip message if defined?(JRUBY_VERSION) end + + def frozen_error_class + Object.const_defined?(:FrozenError) ? FrozenError : RuntimeError + end end diff --git a/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb b/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb index 2fa2d7af88..16b7abc679 100644 --- a/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb +++ b/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb @@ -8,7 +8,9 @@ module CacheIncrementDecrementBehavior assert_equal 2, @cache.read("foo").to_i assert_equal 3, @cache.increment("foo") assert_equal 3, @cache.read("foo").to_i - assert_nil @cache.increment("bar") + + missing = @cache.increment("bar") + assert(missing.nil? || missing == 1) end def test_decrement @@ -18,6 +20,8 @@ module CacheIncrementDecrementBehavior assert_equal 2, @cache.read("foo").to_i assert_equal 1, @cache.decrement("foo") assert_equal 1, @cache.read("foo").to_i - assert_nil @cache.decrement("bar") + + missing = @cache.decrement("bar") + assert(missing.nil? || missing == -1) end end diff --git a/activesupport/test/cache/behaviors/cache_store_behavior.rb b/activesupport/test/cache/behaviors/cache_store_behavior.rb index 582e902f72..bdc689b8b4 100644 --- a/activesupport/test/cache/behaviors/cache_store_behavior.rb +++ b/activesupport/test/cache/behaviors/cache_store_behavior.rb @@ -146,6 +146,16 @@ module CacheStoreBehavior assert_nil @cache.read("foo") end + def test_read_and_write_uncompressed_small_data + @cache.write("foo", "bar", compress: false) + assert_equal "bar", @cache.read("foo") + end + + def test_read_and_write_uncompressed_nil + @cache.write("foo", nil, compress: false) + assert_nil @cache.read("foo") + end + def test_cache_key obj = Object.new def obj.cache_key @@ -164,6 +174,18 @@ module CacheStoreBehavior assert_equal "bar", @cache.read("foo") end + def test_unversioned_cache_key + obj = Object.new + def obj.cache_key + "foo" + end + def obj.cache_key_with_version + "foo-v1" + end + @cache.write(obj, "bar") + assert_equal "bar", @cache.read("foo") + end + def test_array_as_cache_key @cache.write([:fu, "foo"], "bar") assert_equal "bar", @cache.read("fu/foo") diff --git a/activesupport/test/cache/behaviors/local_cache_behavior.rb b/activesupport/test/cache/behaviors/local_cache_behavior.rb index 8dec8090b1..f7302df4c8 100644 --- a/activesupport/test/cache/behaviors/local_cache_behavior.rb +++ b/activesupport/test/cache/behaviors/local_cache_behavior.rb @@ -20,7 +20,11 @@ module LocalCacheBehavior end def test_cleanup_clears_local_cache_but_not_remote_cache - skip unless @cache.class.instance_methods(false).include?(:cleanup) + begin + @cache.cleanup + rescue NotImplementedError + skip + end @cache.with_local_cache do @cache.write("foo", "bar") diff --git a/activesupport/test/cache/cache_entry_test.rb b/activesupport/test/cache/cache_entry_test.rb index 51b214ad8f..80ff7ad564 100644 --- a/activesupport/test/cache/cache_entry_test.rb +++ b/activesupport/test/cache/cache_entry_test.rb @@ -14,16 +14,23 @@ class CacheEntryTest < ActiveSupport::TestCase end end - def test_compress_values + def test_compressed_values value = "value" * 100 entry = ActiveSupport::Cache::Entry.new(value, compress: true, compress_threshold: 1) assert_equal value, entry.value assert(value.bytesize > entry.size, "value is compressed") end - def test_non_compress_values + def test_compressed_by_default value = "value" * 100 - entry = ActiveSupport::Cache::Entry.new(value) + entry = ActiveSupport::Cache::Entry.new(value, compress_threshold: 1) + assert_equal value, entry.value + assert(value.bytesize > entry.size, "value is compressed") + end + + def test_uncompressed_values + value = "value" * 100 + entry = ActiveSupport::Cache::Entry.new(value, compress: false) assert_equal value, entry.value assert_equal value.bytesize, entry.size end diff --git a/activesupport/test/cache/stores/mem_cache_store_test.rb b/activesupport/test/cache/stores/mem_cache_store_test.rb index 1b73fb65eb..99624caf8a 100644 --- a/activesupport/test/cache/stores/mem_cache_store_test.rb +++ b/activesupport/test/cache/stores/mem_cache_store_test.rb @@ -57,6 +57,22 @@ class MemCacheStoreTest < ActiveSupport::TestCase end end + def test_increment_expires_in + cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true) + cache.clear + assert_called_with cache.instance_variable_get(:@data), :incr, [ "foo", 1, 60 ] do + cache.increment("foo", 1, expires_in: 60) + end + end + + def test_decrement_expires_in + cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true) + cache.clear + assert_called_with cache.instance_variable_get(:@data), :decr, [ "foo", 1, 60 ] do + cache.decrement("foo", 1, expires_in: 60) + end + end + def test_local_cache_raw_values_with_marshal cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true) cache.clear diff --git a/activesupport/test/cache/stores/redis_cache_store_test.rb b/activesupport/test/cache/stores/redis_cache_store_test.rb new file mode 100644 index 0000000000..7f684f7a0f --- /dev/null +++ b/activesupport/test/cache/stores/redis_cache_store_test.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "active_support/cache" +require "active_support/cache/redis_cache_store" +require_relative "../behaviors" + +module ActiveSupport::Cache::RedisCacheStoreTests + class LookupTest < ActiveSupport::TestCase + test "may be looked up as :redis_cache_store" do + assert_kind_of ActiveSupport::Cache::RedisCacheStore, + ActiveSupport::Cache.lookup_store(:redis_cache_store) + end + end + + class InitializationTest < ActiveSupport::TestCase + test "omitted URL uses Redis client with default settings" do + assert_called_with Redis, :new, [ + url: nil, + connect_timeout: 20, read_timeout: 1, write_timeout: 1, + reconnect_attempts: 0, + ] do + build + end + end + + test "no URLs uses Redis client with default settings" do + assert_called_with Redis, :new, [ + url: nil, + connect_timeout: 20, read_timeout: 1, write_timeout: 1, + reconnect_attempts: 0, + ] do + build url: [] + end + end + + test "singular URL uses Redis client" do + assert_called_with Redis, :new, [ + url: "redis://localhost:6379/0", + connect_timeout: 20, read_timeout: 1, write_timeout: 1, + reconnect_attempts: 0, + ] do + build url: "redis://localhost:6379/0" + end + end + + test "one URL uses Redis client" do + assert_called_with Redis, :new, [ + url: "redis://localhost:6379/0", + connect_timeout: 20, read_timeout: 1, write_timeout: 1, + reconnect_attempts: 0, + ] do + build url: %w[ redis://localhost:6379/0 ] + end + end + + test "multiple URLs uses Redis::Distributed client" do + assert_called_with Redis, :new, [ + [ url: "redis://localhost:6379/0", + connect_timeout: 20, read_timeout: 1, write_timeout: 1, + reconnect_attempts: 0 ], + [ url: "redis://localhost:6379/1", + connect_timeout: 20, read_timeout: 1, write_timeout: 1, + reconnect_attempts: 0 ], + ], returns: Redis.new do + @cache = build url: %w[ redis://localhost:6379/0 redis://localhost:6379/1 ] + assert_kind_of ::Redis::Distributed, @cache.redis + end + end + + test "block argument uses yielded client" do + block = -> { :custom_redis_client } + assert_called block, :call do + build redis: block + end + end + + private + def build(**kwargs) + ActiveSupport::Cache::RedisCacheStore.new(**kwargs).tap do |cache| + cache.redis + end + end + end + + class StoreTest < ActiveSupport::TestCase + setup do + @namespace = "namespace" + + @cache = ActiveSupport::Cache::RedisCacheStore.new(timeout: 0.1, namespace: @namespace, expires_in: 60) + # @cache.logger = Logger.new($stdout) # For test debugging + + # For LocalCacheBehavior tests + @peek = ActiveSupport::Cache::RedisCacheStore.new(timeout: 0.1, namespace: @namespace) + end + + teardown do + @cache.clear + @cache.redis.disconnect! + end + end + + class RedisCacheStoreCommonBehaviorTest < StoreTest + include CacheStoreBehavior + include CacheStoreVersionBehavior + include LocalCacheBehavior + include CacheIncrementDecrementBehavior + include AutoloadingCacheBehavior + end + + # Separate test class so we can omit the namespace which causes expected, + # appropriate complaints about incompatible string encodings. + class KeyEncodingSafetyTest < StoreTest + include EncodedKeyCacheBehavior + + setup do + @cache = ActiveSupport::Cache::RedisCacheStore.new(timeout: 0.1) + @cache.logger = nil + end + end + + class StoreAPITest < StoreTest + end + + class FailureSafetyTest < StoreTest + test "fetch read failure returns nil" do + end + + test "fetch read failure does not attempt to write" do + end + + test "write failure returns nil" do + end + end + + class DeleteMatchedTest < StoreTest + test "deletes keys matching glob" do + @cache.write("foo", "bar") + @cache.write("fu", "baz") + @cache.delete_matched("foo*") + assert !@cache.exist?("foo") + assert @cache.exist?("fu") + end + + test "fails with regexp matchers" do + assert_raise ArgumentError do + @cache.delete_matched(/OO/i) + end + end + end +end diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index 5f894db46f..30f1632460 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -77,7 +77,7 @@ module CallbacksTest skip_callback :save, :after, :after_save_method, unless: :yes skip_callback :save, :after, :after_save_method, if: :no skip_callback :save, :before, :before_save_method, unless: :no - skip_callback :save, :before, CallbackClass , if: :yes + skip_callback :save, :before, CallbackClass, if: :yes def yes; true; end def no; false; end end diff --git a/activesupport/test/concurrency/load_interlock_aware_monitor_test.rb b/activesupport/test/concurrency/load_interlock_aware_monitor_test.rb new file mode 100644 index 0000000000..2d0f45ec5f --- /dev/null +++ b/activesupport/test/concurrency/load_interlock_aware_monitor_test.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "concurrent/atomic/count_down_latch" +require "active_support/concurrency/load_interlock_aware_monitor" + +module ActiveSupport + module Concurrency + class LoadInterlockAwareMonitorTest < ActiveSupport::TestCase + def setup + @monitor = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new + end + + def test_entering_with_no_blocking + assert @monitor.mon_enter + end + + def test_entering_with_blocking + load_interlock_latch = Concurrent::CountDownLatch.new + monitor_latch = Concurrent::CountDownLatch.new + + able_to_use_monitor = false + able_to_load = false + + thread_with_load_interlock = Thread.new do + ActiveSupport::Dependencies.interlock.running do + load_interlock_latch.count_down + monitor_latch.wait + + @monitor.synchronize do + able_to_use_monitor = true + end + end + end + + thread_with_monitor_lock = Thread.new do + @monitor.synchronize do + monitor_latch.count_down + load_interlock_latch.wait + + ActiveSupport::Dependencies.interlock.loading do + able_to_load = true + end + end + end + + thread_with_load_interlock.join + thread_with_monitor_lock.join + + assert able_to_use_monitor + assert able_to_load + end + end + end +end diff --git a/activesupport/test/core_ext/array/conversions_test.rb b/activesupport/test/core_ext/array/conversions_test.rb index 0f919efcb0..0a7c43d421 100644 --- a/activesupport/test/core_ext/array/conversions_test.rb +++ b/activesupport/test/core_ext/array/conversions_test.rb @@ -90,7 +90,7 @@ class ToXmlTest < ActiveSupport::TestCase def test_to_xml_with_hash_elements xml = [ { name: "David", age: 26, age_in_millis: 820497600000 }, - { name: "Jason", age: 31, age_in_millis: BigDecimal.new("1.0") } + { name: "Jason", age: 31, age_in_millis: BigDecimal("1.0") } ].to_xml(skip_instruct: true, indent: 0) assert_equal '<objects type="array"><object>', xml.first(30) @@ -173,7 +173,7 @@ class ToXmlTest < ActiveSupport::TestCase def test_to_xml_with_instruct xml = [ { name: "David", age: 26, age_in_millis: 820497600000 }, - { name: "Jason", age: 31, age_in_millis: BigDecimal.new("1.0") } + { name: "Jason", age: 31, age_in_millis: BigDecimal("1.0") } ].to_xml(skip_instruct: false, indent: 0) assert_match(/^<\?xml [^>]*/, xml) @@ -183,7 +183,7 @@ class ToXmlTest < ActiveSupport::TestCase def test_to_xml_with_block xml = [ { name: "David", age: 26, age_in_millis: 820497600000 }, - { name: "Jason", age: 31, age_in_millis: BigDecimal.new("1.0") } + { name: "Jason", age: 31, age_in_millis: BigDecimal("1.0") } ].to_xml(skip_instruct: true, indent: 0) do |builder| builder.count 2 end diff --git a/activesupport/test/core_ext/array/grouping_test.rb b/activesupport/test/core_ext/array/grouping_test.rb index da9d4963d8..c182b91826 100644 --- a/activesupport/test/core_ext/array/grouping_test.rb +++ b/activesupport/test/core_ext/array/grouping_test.rb @@ -116,7 +116,7 @@ class SplitTest < ActiveSupport::TestCase def test_split_with_block a = (1..10).to_a assert_equal [[1, 2], [4, 5], [7, 8], [10]], a.split { |i| i % 3 == 0 } - assert_equal [1, 2, 3, 4, 5, 6, 7, 8, 9 , 10], a + assert_equal [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], a end def test_split_with_edge_values diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb index 66e81f1162..62588be33b 100644 --- a/activesupport/test/core_ext/bigdecimal_test.rb +++ b/activesupport/test/core_ext/bigdecimal_test.rb @@ -5,7 +5,7 @@ require "active_support/core_ext/big_decimal" class BigDecimalTest < ActiveSupport::TestCase def test_to_s - bd = BigDecimal.new "0.01" + bd = BigDecimal "0.01" assert_equal "0.01", bd.to_s assert_equal "+0.01", bd.to_s("+F") assert_equal "+0.0 1", bd.to_s("+1F") diff --git a/activesupport/test/core_ext/date_and_time_behavior.rb b/activesupport/test/core_ext/date_and_time_behavior.rb index 256353c309..1176ed647a 100644 --- a/activesupport/test/core_ext/date_and_time_behavior.rb +++ b/activesupport/test/core_ext/date_and_time_behavior.rb @@ -9,6 +9,11 @@ module DateAndTimeBehavior end def test_prev_day + assert_equal date_time_init(2005, 2, 24, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(-2) + assert_equal date_time_init(2005, 2, 23, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(-1) + assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(0) + assert_equal date_time_init(2005, 2, 21, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(1) + assert_equal date_time_init(2005, 2, 20, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day(2) assert_equal date_time_init(2005, 2, 21, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day assert_equal date_time_init(2005, 2, 28, 10, 10, 10), date_time_init(2005, 3, 2, 10, 10, 10).prev_day.prev_day end @@ -19,6 +24,11 @@ module DateAndTimeBehavior end def test_next_day + assert_equal date_time_init(2005, 2, 20, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(-2) + assert_equal date_time_init(2005, 2, 21, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(-1) + assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(0) + assert_equal date_time_init(2005, 2, 23, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(1) + assert_equal date_time_init(2005, 2, 24, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day(2) assert_equal date_time_init(2005, 2, 23, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day assert_equal date_time_init(2005, 3, 2, 10, 10, 10), date_time_init(2005, 2, 28, 10, 10, 10).next_day.next_day end @@ -151,6 +161,16 @@ module DateAndTimeBehavior assert_equal date_time_init(2015, 1, 5, 15, 15, 10), date_time_init(2015, 1, 3, 15, 15, 10).next_weekday end + def test_next_month + assert_equal date_time_init(2004, 12, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(-2) + assert_equal date_time_init(2005, 1, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(-1) + assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(0) + assert_equal date_time_init(2005, 3, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(1) + assert_equal date_time_init(2005, 4, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month(2) + assert_equal date_time_init(2005, 3, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month + assert_equal date_time_init(2005, 4, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_month.next_month + end + def test_next_month_on_31st assert_equal date_time_init(2005, 9, 30, 15, 15, 10), date_time_init(2005, 8, 31, 15, 15, 10).next_month end @@ -160,7 +180,13 @@ module DateAndTimeBehavior end def test_next_year + assert_equal date_time_init(2003, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(-2) + assert_equal date_time_init(2004, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(-1) + assert_equal date_time_init(2005, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(0) + assert_equal date_time_init(2006, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(1) + assert_equal date_time_init(2007, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year(2) assert_equal date_time_init(2006, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year + assert_equal date_time_init(2007, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year.next_year end def test_prev_week @@ -203,6 +229,16 @@ module DateAndTimeBehavior assert_equal date_time_init(2015, 1, 2, 15, 15, 10), date_time_init(2015, 1, 4, 15, 15, 10).prev_weekday end + def test_prev_month + assert_equal date_time_init(2005, 4, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(-2) + assert_equal date_time_init(2005, 3, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(-1) + assert_equal date_time_init(2005, 2, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(0) + assert_equal date_time_init(2005, 1, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(1) + assert_equal date_time_init(2004, 12, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month(2) + assert_equal date_time_init(2005, 1, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month + assert_equal date_time_init(2004, 12, 22, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_month.prev_month + end + def test_prev_month_on_31st assert_equal date_time_init(2004, 2, 29, 10, 10, 10), date_time_init(2004, 3, 31, 10, 10, 10).prev_month end @@ -212,7 +248,21 @@ module DateAndTimeBehavior end def test_prev_year + assert_equal date_time_init(2007, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(-2) + assert_equal date_time_init(2006, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(-1) + assert_equal date_time_init(2005, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(0) + assert_equal date_time_init(2004, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(1) + assert_equal date_time_init(2003, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year(2) assert_equal date_time_init(2004, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year + assert_equal date_time_init(2003, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year.prev_year + end + + def test_last_month_on_31st + assert_equal date_time_init(2004, 2, 29, 0, 0, 0), date_time_init(2004, 3, 31, 0, 0, 0).last_month + end + + def test_last_year + assert_equal date_time_init(2004, 6, 5, 10, 0, 0), date_time_init(2005, 6, 5, 10, 0, 0).last_year end def test_days_to_week_start @@ -247,24 +297,24 @@ module DateAndTimeBehavior def test_beginning_of_week assert_equal date_time_init(2005, 1, 31, 0, 0, 0), date_time_init(2005, 2, 4, 10, 10, 10).beginning_of_week - assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 28, 0, 0, 0).beginning_of_week #monday - assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 29, 0, 0, 0).beginning_of_week #tuesday - assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 30, 0, 0, 0).beginning_of_week #wednesday - assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 01, 0, 0, 0).beginning_of_week #thursday - assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 02, 0, 0, 0).beginning_of_week #friday - assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 03, 0, 0, 0).beginning_of_week #saturday - assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 04, 0, 0, 0).beginning_of_week #sunday + assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 28, 0, 0, 0).beginning_of_week # monday + assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 29, 0, 0, 0).beginning_of_week # tuesday + assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 30, 0, 0, 0).beginning_of_week # wednesday + assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 01, 0, 0, 0).beginning_of_week # thursday + assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 02, 0, 0, 0).beginning_of_week # friday + assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 03, 0, 0, 0).beginning_of_week # saturday + assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 04, 0, 0, 0).beginning_of_week # sunday end def test_end_of_week assert_equal date_time_init(2008, 1, 6, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 12, 31, 10, 10, 10).end_of_week - assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 27, 0, 0, 0).end_of_week #monday - assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 28, 0, 0, 0).end_of_week #tuesday - assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 29, 0, 0, 0).end_of_week #wednesday - assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 30, 0, 0, 0).end_of_week #thursday - assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 31, 0, 0, 0).end_of_week #friday - assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 9, 01, 0, 0, 0).end_of_week #saturday - assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 9, 02, 0, 0, 0).end_of_week #sunday + assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 27, 0, 0, 0).end_of_week # monday + assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 28, 0, 0, 0).end_of_week # tuesday + assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 29, 0, 0, 0).end_of_week # wednesday + assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 30, 0, 0, 0).end_of_week # thursday + assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 31, 0, 0, 0).end_of_week # friday + assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 9, 01, 0, 0, 0).end_of_week # saturday + assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 9, 02, 0, 0, 0).end_of_week # sunday end def test_end_of_month @@ -278,6 +328,26 @@ module DateAndTimeBehavior assert_equal date_time_init(2007, 12, 31, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 12, 31, 10, 10, 10).end_of_year end + def test_next_occurring + assert_equal date_time_init(2017, 12, 18, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:monday) + assert_equal date_time_init(2017, 12, 19, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:tuesday) + assert_equal date_time_init(2017, 12, 20, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:wednesday) + assert_equal date_time_init(2017, 12, 21, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:thursday) + assert_equal date_time_init(2017, 12, 15, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:friday) + assert_equal date_time_init(2017, 12, 16, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:saturday) + assert_equal date_time_init(2017, 12, 17, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:sunday) + end + + def test_prev_occurring + assert_equal date_time_init(2017, 12, 11, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:monday) + assert_equal date_time_init(2017, 12, 12, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:tuesday) + assert_equal date_time_init(2017, 12, 13, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:wednesday) + assert_equal date_time_init(2017, 12, 7, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:thursday) + assert_equal date_time_init(2017, 12, 8, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:friday) + assert_equal date_time_init(2017, 12, 9, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:saturday) + assert_equal date_time_init(2017, 12, 10, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:sunday) + end + def test_monday_with_default_beginning_of_week_set with_bw_default(:saturday) do assert_equal date_time_init(2012, 9, 17, 0, 0, 0), date_time_init(2012, 9, 18, 0, 0, 0).monday diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index b8672eac4b..23d17956df 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -95,11 +95,11 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_beginning_of_week_in_calendar_reform - assert_equal Date.new(1582, 10, 1), Date.new(1582, 10, 15).beginning_of_week #friday + assert_equal Date.new(1582, 10, 1), Date.new(1582, 10, 15).beginning_of_week # friday end def test_end_of_week_in_calendar_reform - assert_equal Date.new(1582, 10, 17), Date.new(1582, 10, 4).end_of_week #thursday + assert_equal Date.new(1582, 10, 17), Date.new(1582, 10, 4).end_of_week # thursday end def test_end_of_year @@ -120,10 +120,6 @@ class DateExtCalculationsTest < ActiveSupport::TestCase assert_equal Date.new(1582, 10, 4), Date.new(1583, 10, 14).prev_year end - def test_last_year - assert_equal Date.new(2004, 6, 5), Date.new(2005, 6, 5).last_year - end - def test_last_year_in_leap_years assert_equal Date.new(1999, 2, 28), Date.new(2000, 2, 29).last_year end @@ -148,7 +144,7 @@ class DateExtCalculationsTest < ActiveSupport::TestCase assert_equal Date.new(2012, 9, 28), Date.new(2005, 2, 28).advance(years: 7, months: 7) assert_equal Date.new(2013, 10, 3), Date.new(2005, 2, 28).advance(years: 7, months: 19, days: 5) assert_equal Date.new(2013, 10, 17), Date.new(2005, 2, 28).advance(years: 7, months: 19, weeks: 2, days: 5) - assert_equal Date.new(2005, 2, 28), Date.new(2004, 2, 29).advance(years: 1) #leap day plus one year + assert_equal Date.new(2005, 2, 28), Date.new(2004, 2, 29).advance(years: 1) # leap day plus one year end def test_advance_does_first_years_and_then_days @@ -185,10 +181,6 @@ class DateExtCalculationsTest < ActiveSupport::TestCase assert_equal Date.new(1582, 10, 18), Date.new(1582, 10, 4).next_week end - def test_last_month_on_31st - assert_equal Date.new(2004, 2, 29), Date.new(2004, 3, 31).last_month - end - def test_last_quarter_on_31st assert_equal Date.new(2004, 2, 29), Date.new(2004, 5, 31).last_quarter end diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index a3c2018a31..f4c9dfcb25 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -30,28 +30,6 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end end - def test_next_occur - datetime = DateTime.new(2016, 9, 24, 0, 0) # saturday - assert_equal datetime.next_occurring(:monday), datetime.since(2.days) - assert_equal datetime.next_occurring(:tuesday), datetime.since(3.days) - assert_equal datetime.next_occurring(:wednesday), datetime.since(4.days) - assert_equal datetime.next_occurring(:thursday), datetime.since(5.days) - assert_equal datetime.next_occurring(:friday), datetime.since(6.days) - assert_equal datetime.next_occurring(:saturday), datetime.since(1.week) - assert_equal datetime.next_occurring(:sunday), datetime.since(1.day) - end - - def test_prev_occur - datetime = DateTime.new(2016, 9, 24, 0, 0) # saturday - assert_equal datetime.prev_occurring(:monday), datetime.ago(5.days) - assert_equal datetime.prev_occurring(:tuesday), datetime.ago(4.days) - assert_equal datetime.prev_occurring(:wednesday), datetime.ago(3.days) - assert_equal datetime.prev_occurring(:thursday), datetime.ago(2.days) - assert_equal datetime.prev_occurring(:friday), datetime.ago(1.day) - assert_equal datetime.prev_occurring(:saturday), datetime.ago(1.week) - assert_equal datetime.prev_occurring(:sunday), datetime.ago(6.days) - end - def test_readable_inspect datetime = DateTime.new(2005, 2, 21, 14, 30, 0) assert_equal "Mon, 21 Feb 2005 14:30:00 +0000", datetime.readable_inspect @@ -162,10 +140,6 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase 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 - assert_equal DateTime.civil(2004, 6, 5, 10), DateTime.civil(2005, 6, 5, 10, 0, 0).last_year - end - def test_ago assert_equal DateTime.civil(2005, 2, 22, 10, 10, 9), DateTime.civil(2005, 2, 22, 10, 10, 10).ago(1) assert_equal DateTime.civil(2005, 2, 22, 9, 10, 10), DateTime.civil(2005, 2, 22, 10, 10, 10).ago(3600) @@ -213,7 +187,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal DateTime.civil(2013, 10, 3, 15, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, days: 5) assert_equal DateTime.civil(2013, 10, 17, 15, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, weeks: 2, days: 5) assert_equal DateTime.civil(2001, 12, 27, 15, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(years: -3, months: -2, days: -1) - assert_equal DateTime.civil(2005, 2, 28, 15, 15, 10), DateTime.civil(2004, 2, 29, 15, 15, 10).advance(years: 1) #leap day plus one year + assert_equal DateTime.civil(2005, 2, 28, 15, 15, 10), DateTime.civil(2004, 2, 29, 15, 15, 10).advance(years: 1) # leap day plus one year assert_equal DateTime.civil(2005, 2, 28, 20, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(hours: 5) assert_equal DateTime.civil(2005, 2, 28, 15, 22, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(minutes: 7) assert_equal DateTime.civil(2005, 2, 28, 15, 15, 19), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(seconds: 9) @@ -248,10 +222,6 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal DateTime.civil(2016, 2, 29), DateTime.civil(2016, 3, 7).last_week end - def test_last_month_on_31st - assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 3, 31).last_month - end - def test_last_quarter_on_31st assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 5, 31).last_quarter end diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index b3e0cd8bd0..4a02f27def 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -71,6 +71,8 @@ class DurationTest < ActiveSupport::TestCase assert_equal "7 days", 7.days.inspect assert_equal "1 week", 1.week.inspect assert_equal "2 weeks", 1.fortnight.inspect + assert_equal "0 seconds", (10 % 5.seconds).inspect + assert_equal "10 minutes", (10.minutes + 0.seconds).inspect end def test_inspect_locale @@ -160,7 +162,7 @@ class DurationTest < ActiveSupport::TestCase end def test_time_plus_duration_returns_same_time_datatype - twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Moscow"] , Time.utc(2016, 4, 28, 00, 45)) + twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Moscow"], Time.utc(2016, 4, 28, 00, 45)) now = Time.now.utc %w( second minute hour day week month year ).each do |unit| assert_equal((now + 1.send(unit)).class, Time, "Time + 1.#{unit} must be Time") diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 746d7ad416..17952e9fc7 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -446,7 +446,7 @@ class HashExtTest < ActiveSupport::TestCase original.freeze assert_nothing_raised { original.except(:a) } - assert_raise(RuntimeError) { original.except!(:a) } + assert_raise(frozen_error_class) { original.except!(:a) } end def test_except_does_not_delete_values_in_original diff --git a/activesupport/test/core_ext/load_error_test.rb b/activesupport/test/core_ext/load_error_test.rb index 4fdd228ff8..41b11d0c33 100644 --- a/activesupport/test/core_ext/load_error_test.rb +++ b/activesupport/test/core_ext/load_error_test.rb @@ -5,7 +5,7 @@ require "active_support/core_ext/load_error" class TestLoadError < ActiveSupport::TestCase def test_with_require - assert_raise(LoadError) { require 'no_this_file_don\'t_exist' } + assert_raise(LoadError) { require "no_this_file_don't_exist" } end def test_with_load assert_raise(LoadError) { load "nor_does_this_one" } diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb index d38124b214..4b9073da54 100644 --- a/activesupport/test/core_ext/numeric_ext_test.rb +++ b/activesupport/test/core_ext/numeric_ext_test.rb @@ -302,7 +302,7 @@ class NumericExtFormattingTest < ActiveSupport::TestCase assert_equal "40 KB", 41100.to_s(:human_size, precision: 2) assert_equal "1.0 KB", kilobytes(1.0123).to_s(:human_size, precision: 2, strip_insignificant_zeros: false) assert_equal "1.012 KB", kilobytes(1.0123).to_s(:human_size, precision: 3, significant: false) - assert_equal "1 KB", kilobytes(1.0123).to_s(:human_size, precision: 0, significant: true) #ignores significant it precision is 0 + assert_equal "1 KB", kilobytes(1.0123).to_s(:human_size, precision: 0, significant: true) # ignores significant it precision is 0 end def test_to_s__human_size_with_custom_delimiter_and_separator @@ -330,17 +330,17 @@ class NumericExtFormattingTest < ActiveSupport::TestCase assert_equal "489.0 Thousand", 489000.to_s(:human, precision: 4, strip_insignificant_zeros: false) assert_equal "1.2346 Million", 1234567.to_s(:human, precision: 4, significant: false) assert_equal "1,2 Million", 1234567.to_s(:human, precision: 1, significant: false, separator: ",") - assert_equal "1 Million", 1234567.to_s(:human, precision: 0, significant: true, separator: ",") #significant forced to false + assert_equal "1 Million", 1234567.to_s(:human, precision: 0, significant: true, separator: ",") # significant forced to false end def test_number_to_human_with_custom_units - #Only integers + # Only integers volume = { unit: "ml", thousand: "lt", million: "m3" } assert_equal "123 lt", 123456.to_s(:human, units: volume) assert_equal "12 ml", 12.to_s(:human, units: volume) assert_equal "1.23 m3", 1234567.to_s(:human, units: volume) - #Including fractionals + # Including fractionals distance = { mili: "mm", centi: "cm", deci: "dm", unit: "m", ten: "dam", hundred: "hm", thousand: "km" } assert_equal "1.23 mm", 0.00123.to_s(:human, units: distance) assert_equal "1.23 cm", 0.0123.to_s(:human, units: distance) @@ -353,14 +353,14 @@ class NumericExtFormattingTest < ActiveSupport::TestCase assert_equal "1.23 km", 1230.to_s(:human, units: distance) assert_equal "12.3 km", 12300.to_s(:human, units: distance) - #The quantifiers don't need to be a continuous sequence + # The quantifiers don't need to be a continuous sequence gangster = { hundred: "hundred bucks", million: "thousand quids" } assert_equal "1 hundred bucks", 100.to_s(:human, units: gangster) assert_equal "25 hundred bucks", 2500.to_s(:human, units: gangster) assert_equal "25 thousand quids", 25000000.to_s(:human, units: gangster) assert_equal "12300 thousand quids", 12345000000.to_s(:human, units: gangster) - #Spaces are stripped from the resulting string + # Spaces are stripped from the resulting string assert_equal "4", 4.to_s(:human, units: { unit: "", ten: "tens " }) assert_equal "4.5 tens", 45.to_s(:human, units: { unit: "", ten: " tens " }) end diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb index 93f39b00bb..b984becce3 100644 --- a/activesupport/test/core_ext/object/duplicable_test.rb +++ b/activesupport/test/core_ext/object/duplicable_test.rb @@ -8,16 +8,16 @@ require "active_support/core_ext/numeric/time" class DuplicableTest < ActiveSupport::TestCase if RUBY_VERSION >= "2.5.0" RAISE_DUP = [method(:puts)] - ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56"), nil, false, true, 1, 2.3, Complex(1), Rational(1)] + ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56"), nil, false, true, 1, 2.3, Complex(1), Rational(1)] elsif RUBY_VERSION >= "2.4.1" RAISE_DUP = [method(:puts), Complex(1), Rational(1)] - ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56"), nil, false, true, 1, 2.3] + ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56"), nil, false, true, 1, 2.3] 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] + ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("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")] + ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56")] end def test_duplicable diff --git a/activesupport/test/core_ext/object/instance_variables_test.rb b/activesupport/test/core_ext/object/instance_variables_test.rb index b9ec827954..a3d8daab5b 100644 --- a/activesupport/test/core_ext/object/instance_variables_test.rb +++ b/activesupport/test/core_ext/object/instance_variables_test.rb @@ -19,7 +19,7 @@ class ObjectInstanceVariableTest < ActiveSupport::TestCase end def test_instance_exec_passes_arguments_to_block - assert_equal %w(hello goodbye), "hello".instance_exec("goodbye") { |v| [self, v] } + assert_equal %w(hello goodbye), "hello".dup.instance_exec("goodbye") { |v| [self, v] } end def test_instance_exec_with_frozen_obj @@ -27,7 +27,7 @@ class ObjectInstanceVariableTest < ActiveSupport::TestCase end def test_instance_exec_nested - assert_equal %w(goodbye olleh bar), "hello".instance_exec("goodbye") { |arg| + assert_equal %w(goodbye olleh bar), "hello".dup.instance_exec("goodbye") { |arg| [arg] + instance_exec("bar") { |v| [reverse, v] } } end end diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb index 0467123e55..903c173e59 100644 --- a/activesupport/test/core_ext/range_ext_test.rb +++ b/activesupport/test/core_ext/range_ext_test.rb @@ -106,24 +106,27 @@ class RangeTest < ActiveSupport::TestCase end def test_each_on_time_with_zone - twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006, 11, 28, 10, 30)) + twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"], Time.utc(2006, 11, 28, 10, 30)) assert_raises TypeError do ((twz - 1.hour)..twz).each {} end end def test_step_on_time_with_zone - twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006, 11, 28, 10, 30)) + twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"], Time.utc(2006, 11, 28, 10, 30)) assert_raises TypeError do ((twz - 1.hour)..twz).step(1) {} end end def test_include_on_time_with_zone - twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006, 11, 28, 10, 30)) - assert_raises TypeError do - ((twz - 1.hour)..twz).include?(twz) - end + twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"], Time.utc(2006, 11, 28, 10, 30)) + assert ((twz - 1.hour)..twz).include?(twz) + end + + def test_case_equals_on_time_with_zone + twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"], Time.utc(2006, 11, 28, 10, 30)) + assert ((twz - 1.hour)..twz) === twz end def test_date_time_with_each diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 9fd6d8ac0f..5c5abe9fd1 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -197,7 +197,7 @@ class StringInflectionsTest < ActiveSupport::TestCase end def test_string_parameterized_underscore_preserve_case - StringToParameterizePreserceCaseWithUnderscore.each do |normal, slugged| + StringToParameterizePreserveCaseWithUnderscore.each do |normal, slugged| assert_equal(slugged, normal.parameterize(separator: "_", preserve_case: true)) end end diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index dc2f4c5ac7..01cf1938be 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -178,10 +178,6 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2005, 2, 4, 19, 30, 59, Rational(999999999, 1000)), Time.local(2005, 2, 4, 19, 30, 10).end_of_minute end - def test_last_year - assert_equal Time.local(2004, 6, 5, 10), Time.local(2005, 6, 5, 10, 0, 0).last_year - end - def test_ago assert_equal Time.local(2005, 2, 22, 10, 10, 9), Time.local(2005, 2, 22, 10, 10, 10).ago(1) assert_equal Time.local(2005, 2, 22, 9, 10, 10), Time.local(2005, 2, 22, 10, 10, 10).ago(3600) @@ -455,7 +451,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2013, 10, 3, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, days: 5) assert_equal Time.local(2013, 10, 17, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, weeks: 2, days: 5) assert_equal Time.local(2001, 12, 27, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(years: -3, months: -2, days: -1) - assert_equal Time.local(2005, 2, 28, 15, 15, 10), Time.local(2004, 2, 29, 15, 15, 10).advance(years: 1) #leap day plus one year + assert_equal Time.local(2005, 2, 28, 15, 15, 10), Time.local(2004, 2, 29, 15, 15, 10).advance(years: 1) # leap day plus one year assert_equal Time.local(2005, 2, 28, 20, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(hours: 5) assert_equal Time.local(2005, 2, 28, 15, 22, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(minutes: 7) assert_equal Time.local(2005, 2, 28, 15, 15, 19), Time.local(2005, 2, 28, 15, 15, 10).advance(seconds: 9) @@ -477,7 +473,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.utc(2013, 10, 3, 15, 15, 10), Time.utc(2005, 2, 22, 15, 15, 10).advance(years: 7, months: 19, days: 11) assert_equal Time.utc(2013, 10, 17, 15, 15, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, weeks: 2, days: 5) assert_equal Time.utc(2001, 12, 27, 15, 15, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(years: -3, months: -2, days: -1) - assert_equal Time.utc(2005, 2, 28, 15, 15, 10), Time.utc(2004, 2, 29, 15, 15, 10).advance(years: 1) #leap day plus one year + assert_equal Time.utc(2005, 2, 28, 15, 15, 10), Time.utc(2004, 2, 29, 15, 15, 10).advance(years: 1) # leap day plus one year assert_equal Time.utc(2005, 2, 28, 20, 15, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(hours: 5) assert_equal Time.utc(2005, 2, 28, 15, 22, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(minutes: 7) assert_equal Time.utc(2005, 2, 28, 15, 15, 19), Time.utc(2005, 2, 28, 15, 15, 10).advance(seconds: 9) @@ -499,7 +495,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.new(2013, 10, 3, 15, 15, 10, "-08:00"), Time.new(2005, 2, 22, 15, 15, 10, "-08:00").advance(years: 7, months: 19, days: 11) assert_equal Time.new(2013, 10, 17, 15, 15, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(years: 7, months: 19, weeks: 2, days: 5) assert_equal Time.new(2001, 12, 27, 15, 15, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(years: -3, months: -2, days: -1) - assert_equal Time.new(2005, 2, 28, 15, 15, 10, "-08:00"), Time.new(2004, 2, 29, 15, 15, 10, "-08:00").advance(years: 1) #leap day plus one year + assert_equal Time.new(2005, 2, 28, 15, 15, 10, "-08:00"), Time.new(2004, 2, 29, 15, 15, 10, "-08:00").advance(years: 1) # leap day plus one year assert_equal Time.new(2005, 2, 28, 20, 15, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(hours: 5) assert_equal Time.new(2005, 2, 28, 15, 22, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(minutes: 7) assert_equal Time.new(2005, 2, 28, 15, 15, 19, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(seconds: 9) @@ -664,10 +660,6 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end end - def test_last_month_on_31st - assert_equal Time.local(2004, 2, 29), Time.local(2004, 3, 31).last_month - end - def test_xmlschema_is_available assert_nothing_raised { Time.now.xmlschema } end diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 0f80a24758..b25747eadb 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -50,6 +50,12 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_raise(ArgumentError) { @twz.in_time_zone(Object.new) } end + def test_in_time_zone_with_ambiguous_time + with_env_tz "Europe/Moscow" do + assert_equal Time.utc(2014, 10, 25, 22, 0, 0), Time.local(2014, 10, 26, 1, 0, 0).in_time_zone("Moscow") + end + end + def test_localtime assert_equal @twz.localtime, @twz.utc.getlocal assert_instance_of Time, @twz.localtime @@ -78,7 +84,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase def test_formatted_offset assert_equal "-05:00", @twz.formatted_offset - assert_equal "-04:00", ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).formatted_offset #dst + assert_equal "-04:00", ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).formatted_offset # dst end def test_dst? @@ -88,7 +94,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase def test_zone assert_equal "EST", @twz.zone - assert_equal "EDT", ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).zone #dst + assert_equal "EDT", ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).zone # dst end def test_nsec @@ -301,13 +307,13 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_plus_with_integer - assert_equal Time.utc(1999, 12, 31, 19, 0 , 5), (@twz + 5).time + assert_equal Time.utc(1999, 12, 31, 19, 0, 5), (@twz + 5).time end def test_plus_with_integer_when_self_wraps_datetime datetime = DateTime.civil(2000, 1, 1, 0) twz = ActiveSupport::TimeWithZone.new(datetime, @time_zone) - assert_equal DateTime.civil(1999, 12, 31, 19, 0 , 5), (twz + 5).time + assert_equal DateTime.civil(1999, 12, 31, 19, 0, 5), (twz + 5).time end def test_plus_when_crossing_time_class_limit @@ -316,21 +322,21 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_plus_with_duration - assert_equal Time.utc(2000, 1, 5, 19, 0 , 0), (@twz + 5.days).time + assert_equal Time.utc(2000, 1, 5, 19, 0, 0), (@twz + 5.days).time end def test_minus_with_integer - assert_equal Time.utc(1999, 12, 31, 18, 59 , 55), (@twz - 5).time + assert_equal Time.utc(1999, 12, 31, 18, 59, 55), (@twz - 5).time end def test_minus_with_integer_when_self_wraps_datetime datetime = DateTime.civil(2000, 1, 1, 0) twz = ActiveSupport::TimeWithZone.new(datetime, @time_zone) - assert_equal DateTime.civil(1999, 12, 31, 18, 59 , 55), (twz - 5).time + assert_equal DateTime.civil(1999, 12, 31, 18, 59, 55), (twz - 5).time end def test_minus_with_duration - assert_equal Time.utc(1999, 12, 26, 19, 0 , 0), (@twz - 5.days).time + assert_equal Time.utc(1999, 12, 26, 19, 0, 0), (@twz - 5.days).time end def test_minus_with_time @@ -501,7 +507,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase def test_method_missing_with_time_return_value assert_instance_of ActiveSupport::TimeWithZone, @twz.months_since(1) - assert_equal Time.utc(2000, 1, 31, 19, 0 , 0), @twz.months_since(1).time + assert_equal Time.utc(2000, 1, 31, 19, 0, 0), @twz.months_since(1).time end def test_marshal_dump_and_load @@ -1301,4 +1307,10 @@ class TimeWithZoneMethodsForString < ActiveSupport::TestCase assert_raise(ArgumentError) { @u.in_time_zone(Object.new) } assert_raise(ArgumentError) { @z.in_time_zone(Object.new) } end + + def test_in_time_zone_with_ambiguous_time + with_tz_default "Moscow" do + assert_equal Time.utc(2014, 10, 25, 22, 0, 0), "2014-10-26 01:00:00".in_time_zone + end + end end diff --git a/activesupport/test/deprecation/method_wrappers_test.rb b/activesupport/test/deprecation/method_wrappers_test.rb index 04e2325754..439e117c1d 100644 --- a/activesupport/test/deprecation/method_wrappers_test.rb +++ b/activesupport/test/deprecation/method_wrappers_test.rb @@ -8,6 +8,16 @@ class MethodWrappersTest < ActiveSupport::TestCase @klass = Class.new do def new_method; "abc" end alias_method :old_method, :new_method + + protected + + def new_protected_method; "abc" end + alias_method :old_protected_method, :new_protected_method + + private + + def new_private_method; "abc" end + alias_method :old_private_method, :new_private_method end end @@ -33,4 +43,16 @@ class MethodWrappersTest < ActiveSupport::TestCase assert_deprecated(warning, deprecator) { assert_equal "abc", @klass.new.old_method } end + + def test_deprecate_methods_protected_method + ActiveSupport::Deprecation.deprecate_methods(@klass, old_protected_method: :new_protected_method) + + assert(@klass.protected_method_defined?(:old_protected_method)) + end + + def test_deprecate_methods_private_method + ActiveSupport::Deprecation.deprecate_methods(@klass, old_private_method: :new_private_method) + + assert(@klass.private_method_defined?(:old_private_method)) + end end diff --git a/activesupport/test/digest_test.rb b/activesupport/test/digest_test.rb new file mode 100644 index 0000000000..83ff2a8d83 --- /dev/null +++ b/activesupport/test/digest_test.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "openssl" + +class DigestTest < ActiveSupport::TestCase + class InvalidDigest; end + def test_with_default_hash_digest_class + assert_equal ::Digest::MD5.hexdigest("hello friend"), ActiveSupport::Digest.hexdigest("hello friend") + end + + def test_with_custom_hash_digest_class + original_hash_digest_class = ActiveSupport::Digest.hash_digest_class + + ActiveSupport::Digest.hash_digest_class = ::Digest::SHA1 + digest = ActiveSupport::Digest.hexdigest("hello friend") + + assert_equal 32, digest.length + assert_equal ::Digest::SHA1.hexdigest("hello friend")[0...32], digest + ensure + ActiveSupport::Digest.hash_digest_class = original_hash_digest_class + end + + def test_should_raise_argument_error_if_custom_digest_is_missing_hexdigest_method + assert_raises(ArgumentError) { ActiveSupport::Digest.hash_digest_class = InvalidDigest } + end +end diff --git a/activesupport/test/encrypted_configuration_test.rb b/activesupport/test/encrypted_configuration_test.rb index 471faa8c12..93ccf457de 100644 --- a/activesupport/test/encrypted_configuration_test.rb +++ b/activesupport/test/encrypted_configuration_test.rb @@ -10,8 +10,10 @@ class EncryptedConfigurationTest < ActiveSupport::TestCase @credentials_key_path = File.join(Dir.tmpdir, "master.key") File.write(@credentials_key_path, ActiveSupport::EncryptedConfiguration.generate_key) - @credentials = ActiveSupport::EncryptedConfiguration.new \ - config_path: @credentials_config_path, key_path: @credentials_key_path, env_key: "RAILS_MASTER_KEY" + @credentials = ActiveSupport::EncryptedConfiguration.new( + config_path: @credentials_config_path, key_path: @credentials_key_path, + env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true + ) end teardown do @@ -51,6 +53,14 @@ class EncryptedConfigurationTest < ActiveSupport::TestCase assert_equal "things", @credentials[:new] end + test "raise error when writing an invalid format value" do + assert_raise(Psych::SyntaxError) do + @credentials.change do |config_file| + config_file.write "login: *login\n username: dummy" + end + end + end + test "raises key error when accessing config via bang method" do assert_raise(KeyError) { @credentials.something! } end diff --git a/activesupport/test/encrypted_file_test.rb b/activesupport/test/encrypted_file_test.rb index 7259726d08..ba3bbef903 100644 --- a/activesupport/test/encrypted_file_test.rb +++ b/activesupport/test/encrypted_file_test.rb @@ -12,8 +12,9 @@ class EncryptedFileTest < ActiveSupport::TestCase @key_path = File.join(Dir.tmpdir, "content.txt.key") File.write(@key_path, ActiveSupport::EncryptedFile.generate_key) - @encrypted_file = ActiveSupport::EncryptedFile.new \ - content_path: @content_path, key_path: @key_path, env_key: "CONTENT_KEY" + @encrypted_file = ActiveSupport::EncryptedFile.new( + content_path: @content_path, key_path: @key_path, env_key: "CONTENT_KEY", raise_if_missing_key: true + ) end teardown do @@ -47,4 +48,12 @@ class EncryptedFileTest < ActiveSupport::TestCase assert_equal "#{@content} and went by the lake", @encrypted_file.read end + + test "raise MissingKeyError when key is missing" do + assert_raise(ActiveSupport::EncryptedFile::MissingKeyError) do + ActiveSupport::EncryptedFile.new( + content_path: @content_path, key_path: "", env_key: "", raise_if_missing_key: true + ).read + end + end end diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index eeec0ab1a5..0e3e576a70 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -224,6 +224,12 @@ class InflectorTest < ActiveSupport::TestCase assert_equal("json_html_api", ActiveSupport::Inflector.underscore("JSONHTMLAPI")) end + def test_acronym_regexp_is_deprecated + assert_deprecated do + ActiveSupport::Inflector.inflections.acronym_regex + end + end + def test_underscore CamelToUnderscore.each do |camel, underscore| assert_equal(underscore, ActiveSupport::Inflector.underscore(camel)) @@ -356,6 +362,19 @@ class InflectorTest < ActiveSupport::TestCase assert_equal("Col rpted bugs", ActiveSupport::Inflector.humanize("COL_rpted_bugs")) end + def test_humanize_with_acronyms + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym "LAX" + inflect.acronym "SFO" + end + assert_equal("LAX roundtrip to SFO", ActiveSupport::Inflector.humanize("LAX ROUNDTRIP TO SFO")) + assert_equal("LAX roundtrip to SFO", ActiveSupport::Inflector.humanize("LAX ROUNDTRIP TO SFO", capitalize: false)) + assert_equal("LAX roundtrip to SFO", ActiveSupport::Inflector.humanize("lax roundtrip to sfo")) + assert_equal("LAX roundtrip to SFO", ActiveSupport::Inflector.humanize("lax roundtrip to sfo", capitalize: false)) + assert_equal("LAX roundtrip to SFO", ActiveSupport::Inflector.humanize("Lax Roundtrip To Sfo")) + assert_equal("LAX roundtrip to SFO", ActiveSupport::Inflector.humanize("Lax Roundtrip To Sfo", capitalize: false)) + end + def test_constantize run_constantize_tests_on do |string| ActiveSupport::Inflector.constantize(string) diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index f1214671ce..689370cccf 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -221,7 +221,7 @@ module InflectorTestCases "Test with malformed utf8 \251" => "test_with_malformed_utf8" } - StringToParameterizePreserceCaseWithUnderscore = { + StringToParameterizePreserveCaseWithUnderscore = { "Donald E. Knuth" => "Donald_E_Knuth", "Random text with *(bad)* characters" => "Random_text_with_bad_characters", "With-some-dashes" => "With-some-dashes", diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 96ad8dfbdb..340a2abf75 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -187,7 +187,7 @@ class TestJSONEncoding < ActiveSupport::TestCase def test_array_should_pass_encoding_options_to_children_in_as_json people = [ { name: "John", address: { city: "London", country: "UK" } }, - { name: "Jean", address: { city: "Paris" , country: "France" } } + { name: "Jean", address: { city: "Paris", country: "France" } } ] json = people.as_json only: [:address, :city] expected = [ @@ -201,7 +201,7 @@ class TestJSONEncoding < ActiveSupport::TestCase def test_array_should_pass_encoding_options_to_children_in_to_json people = [ { name: "John", address: { city: "London", country: "UK" } }, - { name: "Jean", address: { city: "Paris" , country: "France" } } + { name: "Jean", address: { city: "Paris", country: "France" } } ] json = people.to_json only: [:address, :city] @@ -210,10 +210,10 @@ class TestJSONEncoding < ActiveSupport::TestCase People = Class.new(BasicObject) do include Enumerable - def initialize() + def initialize @people = [ { name: "John", address: { city: "London", country: "UK" } }, - { name: "Jean", address: { city: "Paris" , country: "France" } } + { name: "Jean", address: { city: "Paris", country: "France" } } ] end def each(*, &blk) diff --git a/activesupport/test/number_helper_i18n_test.rb b/activesupport/test/number_helper_i18n_test.rb index 6d6958be49..365fa96f4d 100644 --- a/activesupport/test/number_helper_i18n_test.rb +++ b/activesupport/test/number_helper_i18n_test.rb @@ -35,7 +35,7 @@ module ActiveSupport thousand: "t", million: "m", billion: "b", - trillion: "t" , + trillion: "t", quadrillion: "q" } } @@ -77,10 +77,10 @@ module ActiveSupport end def test_number_with_i18n_precision - #Delimiter was set to "" + # Delimiter was set to "" assert_equal("10000", number_to_rounded(10000, locale: "ts")) - #Precision inherited and significant was set + # Precision inherited and significant was set assert_equal("1.00", number_to_rounded(1.0, locale: "ts")) end @@ -90,7 +90,7 @@ module ActiveSupport end def test_number_with_i18n_delimiter - #Delimiter "," and separator "." + # Delimiter "," and separator "." assert_equal("1,000,000.234", number_to_delimited(1000000.234, locale: "ts")) end @@ -114,7 +114,7 @@ module ActiveSupport end def test_number_to_i18n_human_size - #b for bytes and k for kbytes + # b for bytes and k for kbytes assert_equal("2 k", number_to_human_size(2048, locale: "ts")) assert_equal("42 b", number_to_human_size(42, locale: "ts")) end @@ -125,11 +125,11 @@ module ActiveSupport end def test_number_to_human_with_default_translation_scope - #Using t for thousand + # Using t for thousand assert_equal "2 t", number_to_human(2000, locale: "ts") - #Significant was set to true with precision 2, using b for billion + # Significant was set to true with precision 2, using b for billion assert_equal "1.2 b", number_to_human(1234567890, locale: "ts") - #Using pluralization (Ten/Tens and Tenth/Tenths) + # Using pluralization (Ten/Tens and Tenth/Tenths) assert_equal "1 Tenth", number_to_human(0.1, locale: "ts") assert_equal "1.3 Tenth", number_to_human(0.134, locale: "ts") assert_equal "2 Tenths", number_to_human(0.2, locale: "ts") @@ -144,7 +144,7 @@ module ActiveSupport end def test_number_to_human_with_custom_translation_scope - #Significant was set to true with precision 2, with custom translated units + # Significant was set to true with precision 2, with custom translated units assert_equal "4.3 cm", number_to_human(0.0432, locale: "ts", units: :custom_units_for_number_to_human) end end diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb index b4795b6ee1..16ccc5572c 100644 --- a/activesupport/test/number_helper_test.rb +++ b/activesupport/test/number_helper_test.rb @@ -260,7 +260,7 @@ module ActiveSupport assert_equal "40 KB", number_helper.number_to_human_size(41100, precision: 2) assert_equal "1.0 KB", number_helper.number_to_human_size(kilobytes(1.0123), precision: 2, strip_insignificant_zeros: false) assert_equal "1.012 KB", number_helper.number_to_human_size(kilobytes(1.0123), precision: 3, significant: false) - assert_equal "1 KB", number_helper.number_to_human_size(kilobytes(1.0123), precision: 0, significant: true) #ignores significant it precision is 0 + assert_equal "1 KB", number_helper.number_to_human_size(kilobytes(1.0123), precision: 0, significant: true) # ignores significant it precision is 0 end end @@ -292,7 +292,7 @@ module ActiveSupport assert_equal "489.0 Thousand", number_helper.number_to_human(489000, precision: 4, strip_insignificant_zeros: false) assert_equal "1.2346 Million", number_helper.number_to_human(1234567, precision: 4, significant: false) assert_equal "1,2 Million", number_helper.number_to_human(1234567, precision: 1, significant: false, separator: ",") - assert_equal "1 Million", number_helper.number_to_human(1234567, precision: 0, significant: true, separator: ",") #significant forced to false + assert_equal "1 Million", number_helper.number_to_human(1234567, precision: 0, significant: true, separator: ",") # significant forced to false assert_equal "1 Million", number_helper.number_to_human(999999) assert_equal "1 Billion", number_helper.number_to_human(999999999) end @@ -300,13 +300,13 @@ module ActiveSupport def test_number_to_human_with_custom_units [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - #Only integers + # Only integers volume = { unit: "ml", thousand: "lt", million: "m3" } assert_equal "123 lt", number_helper.number_to_human(123456, units: volume) assert_equal "12 ml", number_helper.number_to_human(12, units: volume) assert_equal "1.23 m3", number_helper.number_to_human(1234567, units: volume) - #Including fractionals + # Including fractionals distance = { mili: "mm", centi: "cm", deci: "dm", unit: "m", ten: "dam", hundred: "hm", thousand: "km" } assert_equal "1.23 mm", number_helper.number_to_human(0.00123, units: distance) assert_equal "1.23 cm", number_helper.number_to_human(0.0123, units: distance) @@ -319,7 +319,7 @@ module ActiveSupport assert_equal "1.23 km", number_helper.number_to_human(1230, units: distance) assert_equal "12.3 km", number_helper.number_to_human(12300, units: distance) - #The quantifiers don't need to be a continuous sequence + # The quantifiers don't need to be a continuous sequence gangster = { hundred: "hundred bucks", million: "thousand quids" } assert_equal "1 hundred bucks", number_helper.number_to_human(100, units: gangster) assert_equal "25 hundred bucks", number_helper.number_to_human(2500, units: gangster) @@ -329,11 +329,11 @@ module ActiveSupport assert_equal "25 thousand quids", number_helper.number_to_human(25000000, units: gangster) assert_equal "12300 thousand quids", number_helper.number_to_human(12345000000, units: gangster) - #Spaces are stripped from the resulting string + # Spaces are stripped from the resulting string assert_equal "4", number_helper.number_to_human(4, units: { unit: "", ten: "tens " }) assert_equal "4.5 tens", number_helper.number_to_human(45, units: { unit: "", ten: " tens " }) - #Uses only the provided units and does not try to use larger ones + # Uses only the provided units and does not try to use larger ones assert_equal "1000 kilometers", number_helper.number_to_human(1_000_000, units: { unit: "meter", thousand: "kilometers" }) end end diff --git a/activesupport/test/security_utils_test.rb b/activesupport/test/security_utils_test.rb index efd2bcfa0f..0a607594a2 100644 --- a/activesupport/test/security_utils_test.rb +++ b/activesupport/test/security_utils_test.rb @@ -9,8 +9,14 @@ class SecurityUtilsTest < ActiveSupport::TestCase 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") + def test_fixed_length_secure_compare_should_perform_string_comparison + assert ActiveSupport::SecurityUtils.fixed_length_secure_compare("a", "a") + assert !ActiveSupport::SecurityUtils.fixed_length_secure_compare("a", "b") + end + + def test_fixed_length_secure_compare_raise_on_length_mismatch + assert_raises(ArgumentError, "string length mismatch.") do + ActiveSupport::SecurityUtils.fixed_length_secure_compare("a", "ab") + end end end diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb index 9bc9183668..79b75a001a 100644 --- a/activesupport/test/test_case_test.rb +++ b/activesupport/test/test_case_test.rb @@ -156,6 +156,16 @@ class AssertDifferenceTest < ActiveSupport::TestCase end end + def test_assert_changes_with_to_option_but_no_change_has_special_message + error = assert_raises Minitest::Assertion do + assert_changes "@object.num", to: 0 do + # no changes + end + end + + assert_equal "\"@object.num\" didn't change. It was already 0", error.message + end + def test_assert_changes_with_wrong_to_option assert_raises Minitest::Assertion do assert_changes "@object.num", to: 2 do @@ -179,6 +189,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase end def test_assert_changes_works_with_any_object + # Silences: instance variable @new_object not initialized. retval = silence_warnings do assert_changes :@new_object, from: nil, to: 42 do @new_object = 42 @@ -201,7 +212,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase def test_assert_changes_with_to_and_case_operator token = nil - assert_changes -> { token }, to: /\w{32}/ do + assert_changes -> { token }, to: /\w{32}/ do token = SecureRandom.hex end end @@ -217,6 +228,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase def test_assert_changes_with_message error = assert_raises Minitest::Assertion do assert_changes "@object.num", "@object.num should 1", to: 1 do + @object.decrement end end @@ -236,7 +248,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase end end - assert_equal "@object.num should not change.\n\"@object.num\" did change to 1.\nExpected: 0\n Actual: 1", error.message + assert_equal "@object.num should not change.\n\"@object.num\" did change to 1", error.message end end diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb index 4172c18ead..9c2c635f43 100644 --- a/activesupport/test/time_travel_test.rb +++ b/activesupport/test/time_travel_test.rb @@ -98,7 +98,7 @@ class TimeTravelTest < ActiveSupport::TestCase travel_to outer_expected_time do e = assert_raises(RuntimeError) do travel_to(inner_expected_time) do - #noop + # 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) diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index acb0ecd226..405c8f315b 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -32,6 +32,12 @@ class TimeZoneTest < ActiveSupport::TestCase end end + def test_period_for_local_with_ambigiuous_time + zone = ActiveSupport::TimeZone["Moscow"] + period = zone.period_for_local(Time.utc(2015, 1, 1)) + assert_equal period, zone.period_for_local(Time.utc(2014, 10, 26, 1, 0, 0)) + end + def test_from_integer_to_map assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone[-28800] # PST end @@ -195,6 +201,11 @@ class TimeZoneTest < ActiveSupport::TestCase assert_equal "EDT", twz.zone end + def test_local_with_ambiguous_time + zone = ActiveSupport::TimeZone["Moscow"] + assert_equal Time.utc(2014, 10, 25, 22, 0, 0), zone.local(2014, 10, 26, 1, 0, 0) + end + def test_at zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] secs = 946684800.0 @@ -303,6 +314,11 @@ class TimeZoneTest < ActiveSupport::TestCase end end + def test_iso8601_with_ambiguous_time + zone = ActiveSupport::TimeZone["Moscow"] + assert_equal Time.utc(2014, 10, 25, 22, 0, 0), zone.parse("2014-10-26T01:00:00") + end + def test_parse zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] twz = zone.parse("1999-12-31 19:00:00") @@ -412,6 +428,11 @@ class TimeZoneTest < ActiveSupport::TestCase assert_equal "argument out of range", exception.message end + def test_parse_with_ambiguous_time + zone = ActiveSupport::TimeZone["Moscow"] + assert_equal Time.utc(2014, 10, 25, 22, 0, 0), zone.parse("2014-10-26 01:00:00") + end + def test_rfc3339 zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] twz = zone.rfc3339("1999-12-31T14:00:00-10:00") @@ -604,6 +625,11 @@ class TimeZoneTest < ActiveSupport::TestCase end end + def test_strptime_with_ambiguous_time + zone = ActiveSupport::TimeZone["Moscow"] + assert_equal Time.utc(2014, 10, 25, 22, 0, 0), zone.strptime("2014-10-26 01:00:00", "%Y-%m-%d %H:%M:%S") + end + 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) @@ -692,6 +718,13 @@ class TimeZoneTest < ActiveSupport::TestCase end end + def test_all_uninfluenced_by_time_zone_lookups_delegated_to_tzinfo + ActiveSupport::TimeZone.clear + galapagos = ActiveSupport::TimeZone["Pacific/Galapagos"] + all_zones = ActiveSupport::TimeZone.all + assert_not_includes all_zones, galapagos + end + def test_index assert_nil ActiveSupport::TimeZone["bogus"] assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone["Central Time (US & Canada)"] diff --git a/activesupport/test/xml_mini/libxml_engine_test.rb b/activesupport/test/xml_mini/libxml_engine_test.rb index 5c13f493f1..3eef3946a3 100644 --- a/activesupport/test/xml_mini/libxml_engine_test.rb +++ b/activesupport/test/xml_mini/libxml_engine_test.rb @@ -6,7 +6,7 @@ XMLMiniEngineTest.run_with_gem("libxml") do class LibxmlEngineTest < XMLMiniEngineTest def setup super - LibXML::XML::Error.set_handler(&lambda { |error| }) #silence libxml, exceptions will do + LibXML::XML::Error.set_handler(&lambda { |error| }) # silence libxml, exceptions will do end private diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb index 5e46406a7b..18a3f2ca66 100644 --- a/activesupport/test/xml_mini_test.rb +++ b/activesupport/test/xml_mini_test.rb @@ -114,7 +114,7 @@ module XmlMiniTest end test "#to_tag accepts decimal types" do - @xml.to_tag(:b, ::BigDecimal.new("1.2"), @options) + @xml.to_tag(:b, BigDecimal("1.2"), @options) assert_xml("<b type=\"decimal\">1.2</b>") end |