diff options
Diffstat (limited to 'activesupport')
38 files changed, 665 insertions, 215 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 2656d3f113..21c79949ca 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,7 +1,24 @@ -## Rails 5.0.0.beta1 (December 18, 2015) ## +* Match `HashWithIndifferentAccess#default`'s behaviour with `Hash#default`. + + *David Cornu* + +* Adds `:exception_object` key to `ActiveSupport::Notifications::Instrumenter` + payload when an exception is raised. + + Adds new key/value pair to payload when an exception is raised: + e.g. `:exception_object => #<RuntimeError: FAIL>`. -* No changes. + *Ryan T. Hosford* +* Support extended grapheme clusters and UAX 29. + + *Adam Roben* + +* Add petabyte and exabyte numeric conversion. + + *Akshay Vishnoi* + +## Rails 5.0.0.beta1 (December 18, 2015) ## * Add thread_m/cattr_accessor/reader/writer suite of methods for declaring class and module variables that live per-thread. This makes it easy to declare per-thread globals that are encapsulated. Note: This is a sharp edge. A wild proliferation @@ -13,14 +30,14 @@ module Current thread_mattr_accessor :account thread_mattr_accessor :user - + def self.reset() self.account = self.user = nil end end - class ApplicationController < ActiveController::Base + class ApplicationController < ActionController::Base before_action :set_current after_action { Current.reset } - + private def set_current Current.account = Account.find(params[:account_id]) @@ -37,7 +54,7 @@ class Message < ApplicationRecord has_many :events after_create :track_created - + private def track_created events.create! origin: self, action: :create @@ -61,9 +78,9 @@ *Yuichiro Kaneko* -* `ActiveSupport::Cache::Store#namespaced_key`, - `ActiveSupport::Cache::MemCachedStore#escape_key`, and - `ActiveSupport::Cache::FileStore#key_file_path` +* `ActiveSupport::Cache::Store#namespaced_key`, + `ActiveSupport::Cache::MemCachedStore#escape_key`, and + `ActiveSupport::Cache::FileStore#key_file_path` are deprecated and replaced with `normalize_key` that now calls `super`. `ActiveSupport::Cache::LocaleCache#set_cache_value` is deprecated and replaced with `write_cache_value`. @@ -122,7 +139,7 @@ *Konstantinos Rousis* -* Handle invalid UTF-8 strings when HTML escaping +* Handle invalid UTF-8 strings when HTML escaping. Use `ActiveSupport::Multibyte::Unicode.tidy_bytes` to handle invalid UTF-8 strings in `ERB::Util.unwrapped_html_escape` and `ERB::Util.html_escape_once`. @@ -163,7 +180,7 @@ * Short-circuit `blank?` on date and time values since they are never blank. - Fixes #21657 + Fixes #21657. *Andrew White* @@ -201,7 +218,7 @@ * ActiveSupport::HashWithIndifferentAccess `select` and `reject` will now return enumerator if called without block. - Fixes #20095 + Fixes #20095. *Bernard Potocki* @@ -215,11 +232,11 @@ *Simon Eskildsen* -* Fix setting `default_proc` on `HashWithIndifferentAccess#dup` +* Fix setting `default_proc` on `HashWithIndifferentAccess#dup`. *Simon Eskildsen* -* Fix a range of values for parameters of the Time#change +* Fix a range of values for parameters of the Time#change. *Nikolay Kondratyev* @@ -231,7 +248,7 @@ *Kevin Deisz* * Add a bang version to `ActiveSupport::OrderedOptions` get methods which will raise - an `KeyError` if the value is `.blank?` + an `KeyError` if the value is `.blank?`. Before: diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE index 7bffebb076..40235833ba 100644 --- a/activesupport/MIT-LICENSE +++ b/activesupport/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2005-2015 David Heinemeier Hansson +Copyright (c) 2005-2016 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -17,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 2019afeb00..94fe893149 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2005-2015 David Heinemeier Hansson +# Copyright (c) 2005-2016 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/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 5011014e96..610105f41c 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -255,10 +255,11 @@ module ActiveSupport # end # end # - # # val_1 => "new value 1" - # # val_2 => "original value" - # # sleep 10 # First thread extend the life of cache by another 10 seconds - # # cache.fetch('foo') => "new value 1" + # cache.fetch('foo') # => "original value" + # sleep 10 # First thread extended the life of cache by another 10 seconds + # cache.fetch('foo') # => "new value 1" + # val_1 # => "new value 1" + # val_2 # => "original value" # # Other options will be handled by the specific cache store implementation. # Internally, #fetch calls #read_entry, and calls #write_entry on a cache @@ -278,18 +279,18 @@ module ActiveSupport options = merged_options(options) key = normalize_key(name, options) + entry = nil instrument(:read, name, options) do |payload| cached_entry = read_entry(key, options) unless options[:force] - payload[:super_operation] = :fetch if payload entry = handle_expired_entry(cached_entry, key, options) + payload[:super_operation] = :fetch if payload + payload[:hit] = !!entry if payload + end - if entry - payload[:hit] = true if payload - get_entry_value(entry, name, options) - else - payload[:hit] = false if payload - save_block_result_to_cache(name, options) { |_name| yield _name } - end + if entry + get_entry_value(entry, name, options) + else + save_block_result_to_cache(name, options) { |_name| yield _name } end else read(name, options) diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index dff2443bc8..99c55b1aa4 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -17,6 +17,7 @@ module ActiveSupport FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write) FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room EXCLUDED_DIRS = ['.', '..'].freeze + GITKEEP_FILES = ['.gitkeep', '.keep'].freeze def initialize(cache_path, options = nil) super(options) @@ -24,10 +25,10 @@ module ActiveSupport end # Deletes all items from the cache. In this case it deletes all the entries in the specified - # file store directory except for .gitkeep. Be careful which directory is specified in your + # file store directory except for .keep or .gitkeep. Be careful which directory is specified in your # config file when using +FileStore+ because everything in that directory will be deleted. def clear(options = nil) - root_dirs = Dir.entries(cache_path).reject {|f| (EXCLUDED_DIRS + [".gitkeep"]).include?(f)} + root_dirs = exclude_from(cache_path, EXCLUDED_DIRS + GITKEEP_FILES) FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)}) rescue Errno::ENOENT end @@ -154,7 +155,7 @@ module ActiveSupport # Delete empty directories in the cache. def delete_empty_directories(dir) return if File.realpath(dir) == File.realpath(cache_path) - if Dir.entries(dir).reject {|f| EXCLUDED_DIRS.include?(f)}.empty? + if exclude_from(dir, EXCLUDED_DIRS).empty? Dir.delete(dir) rescue nil delete_empty_directories(File.dirname(dir)) end @@ -193,6 +194,11 @@ module ActiveSupport end end end + + # Exclude entries from source directory + def exclude_from(source, excludes) + Dir.entries(source).reject { |f| excludes.include?(f) } + end end end end diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 8594d9bf2e..6741e732f0 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -138,6 +138,8 @@ end module ActiveSupport class XMLConverter # :nodoc: + # Raised if the XML contains attributes with type="yaml" or + # type="symbol". Read Hash#from_xml for more details. class DisallowedType < StandardError def initialize(type) super "Disallowed type attribute: #{type.inspect}" diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index 124f90dc0f..76825862d7 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -49,7 +49,7 @@ class Module # include HairColors # end # - # Person.hair_colors # => [:brown, :black, :blonde, :red] + # Person.new.hair_colors # => [:brown, :black, :blonde, :red] def mattr_reader(*syms) options = syms.extract_options! syms.each do |sym| @@ -105,7 +105,7 @@ class Module # # Also, you can pass a block to set up the attribute with a default value. # - # class HairColors + # module HairColors # mattr_writer :hair_colors do # [:brown, :black, :blonde, :red] # end @@ -150,8 +150,8 @@ class Module # include HairColors # end # - # Person.hair_colors = [:brown, :black, :blonde, :red] - # Person.hair_colors # => [:brown, :black, :blonde, :red] + # HairColors.hair_colors = [:brown, :black, :blonde, :red] + # HairColors.hair_colors # => [:brown, :black, :blonde, :red] # Person.new.hair_colors # => [:brown, :black, :blonde, :red] # # If a subclass changes the value then that would also change the value for @@ -161,8 +161,8 @@ class Module # class Male < Person # end # - # Male.hair_colors << :blue - # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue] + # Male.new.hair_colors << :blue + # Person.new.hair_colors # => [:brown, :black, :blonde, :red, :blue] # # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>. # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>. diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb index 9a3651f29a..9d832897ed 100644 --- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb +++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/big_decimal/conversions' require 'active_support/number_helper' +require 'active_support/core_ext/module/deprecation' module ActiveSupport::NumericWithFormat @@ -75,6 +76,8 @@ module ActiveSupport::NumericWithFormat # 1234567.to_s(:human_size) # => 1.18 MB # 1234567890.to_s(:human_size) # => 1.15 GB # 1234567890123.to_s(:human_size) # => 1.12 TB + # 1234567890123456.to_s(:human_size) # => 1.1 PB + # 1234567890123456789.to_s(:human_size) # => 1.07 EB # 1234567.to_s(:human_size, precision: 2) # => 1.2 MB # 483989.to_s(:human_size, precision: 2) # => 470 KB # 1234567.to_s(:human_size, precision: 2, separator: ',') # => 1,2 MB @@ -117,7 +120,11 @@ module ActiveSupport::NumericWithFormat when :human_size return ActiveSupport::NumberHelper.number_to_human_size(self, options) else - super + if is_a?(Float) || format.is_a?(Symbol) + super() + else + super + end end end diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 510fa48189..6251f34daf 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -5,7 +5,6 @@ class ERB module Util HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' } JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003e', '<' => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' } - HTML_ESCAPE_REGEXP = /[&"'><]/ HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/ JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u @@ -37,7 +36,7 @@ class ERB if s.html_safe? s else - ActiveSupport::Multibyte::Unicode.tidy_bytes(s).gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE) + CGI.escapeHTML(ActiveSupport::Multibyte::Unicode.tidy_bytes(s)) end end module_function :unwrapped_html_escape @@ -142,6 +141,7 @@ module ActiveSupport #:nodoc: alias_method :original_concat, :concat private :original_concat + # Raised when <tt>ActiveSupport::SafeBuffer#safe_concat</tt> is called on unsafe buffers. class SafeConcatError < StandardError def initialize super 'Could not concatenate to the buffer because it is not html safe.' @@ -243,8 +243,7 @@ module ActiveSupport #:nodoc: private def html_escape_interpolated_argument(arg) - (!html_safe? || arg.html_safe?) ? arg : - arg.to_s.gsub(ERB::Util::HTML_ESCAPE_REGEXP, ERB::Util::HTML_ESCAPE) + (!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s) end end end diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb index 877dc84ec8..7a60f94996 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -40,7 +40,23 @@ class Time Thread.current[:time_zone] = find_zone!(time_zone) end - # Allows override of <tt>Time.zone</tt> locally inside supplied block; resets <tt>Time.zone</tt> to existing value when done. + # Allows override of <tt>Time.zone</tt> locally inside supplied block; + # resets <tt>Time.zone</tt> to existing value when done. + # + # class ApplicationController < ActionController::Base + # around_action :set_time_zone + # + # private + # + # def set_time_zone + # Time.use_zone(current_user.timezone) { yield } + # end + # end + # + # NOTE: This won't affect any <tt>ActiveSupport::TimeWithZone</tt> + # objects that have already been created, e.g. any model timestamp + # attributes that have been read before the block will remain in + # the application's default timezone. def use_zone(time_zone) new_zone = find_zone!(time_zone) begin diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index 46e9996d59..24545d766c 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -32,7 +32,7 @@ module ActiveSupport # and the second is a library name # # ActiveSupport::Deprecation.new('2.0', 'MyLibrary') - def initialize(deprecation_horizon = '5.0', gem_name = 'Rails') + def initialize(deprecation_horizon = '5.1', gem_name = 'Rails') self.gem_name = gem_name self.deprecation_horizon = deprecation_horizon # By default, warnings are not silenced and debugging is off. diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index 28d2d78643..0de891f1a2 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -1,6 +1,8 @@ require "active_support/notifications" module ActiveSupport + # Raised when <tt>ActiveSupport::Deprecation::Behavior#behavior</tt> is set with <tt>:raise</tt>. + # You would set <tt>:raise</tt>, as a behaviour to raise errors and proactively report exceptions from deprecations. class DeprecationException < StandardError end diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb index 32fe8025fe..f5ea6669ce 100644 --- a/activesupport/lib/active_support/deprecation/method_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb @@ -21,15 +21,15 @@ module ActiveSupport # # => [:aaa, :bbb, :ccc] # # Fred.aaa - # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.0. (called from irb_binding at (irb):10) + # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.1. (called from irb_binding at (irb):10) # # => nil # # Fred.bbb - # # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.0 (use zzz instead). (called from irb_binding at (irb):11) + # # 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 - # # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.0 (use Bar#ccc instead). (called from irb_binding at (irb):12) + # # 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 # # Passing in a custom deprecator: diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 4ff35a45a1..b878f31e75 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -68,12 +68,10 @@ module ActiveSupport end end - def default(key = nil) - if key.is_a?(Symbol) && include?(key = key.to_s) - self[key] - else - super - end + def default(*args) + key = args.first + args[0] = key.to_s if key.is_a?(Symbol) + super(*args) end def self.new_from_hash_copying_default(hash) @@ -159,6 +157,20 @@ module ActiveSupport alias_method :has_key?, :key? alias_method :member?, :key? + + # Same as <tt>Hash#[]</tt> where the key passed as argument can be + # either a string or a symbol: + # + # counters = ActiveSupport::HashWithIndifferentAccess.new + # counters[:foo] = 1 + # + # counters['foo'] # => 1 + # counters[:foo] # => 1 + # counters[:zoo] # => nil + def [](key) + super(convert_key(key)) + end + # Same as <tt>Hash#fetch</tt> where the key passed as argument can be # either a string or a symbol: # diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 595b0339cc..f741c0bfb8 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -173,7 +173,7 @@ module ActiveSupport # # Singular names are not handled correctly: # - # classify('calculus') # => "Calculu" + # classify('calculus') # => "Calculus" def classify(table_name) # strip out any leading schema name camelize(singularize(table_name.to_s.sub(/.*\./, ''.freeze))) diff --git a/activesupport/lib/active_support/locale/en.yml b/activesupport/lib/active_support/locale/en.yml index a4563ace8f..c64b7598ee 100644 --- a/activesupport/lib/active_support/locale/en.yml +++ b/activesupport/lib/active_support/locale/en.yml @@ -106,6 +106,8 @@ en: mb: "MB" gb: "GB" tb: "TB" + pb: "PB" + eb: "EB" # Used in NumberHelper.number_to_human() decimal_units: format: "%n %u" diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb index 82117a64d2..7626b28108 100644 --- a/activesupport/lib/active_support/logger.rb +++ b/activesupport/lib/active_support/logger.rb @@ -5,18 +5,27 @@ module ActiveSupport class Logger < ::Logger include LoggerSilence - attr_accessor :broadcast_messages + # Returns true if the logger destination matches one of the sources + # + # logger = Logger.new(STDOUT) + # ActiveSupport::Logger.logger_outputs_to?(logger, STDOUT) + # # => true + def self.logger_outputs_to?(logger, *sources) + logdev = logger.instance_variable_get("@logdev") + logger_source = logdev.dev if logdev.respond_to?(:dev) + sources.any? { |source| source == logger_source } + end # Broadcasts logs to multiple loggers. def self.broadcast(logger) # :nodoc: Module.new do define_method(:add) do |*args, &block| - logger.add(*args, &block) if broadcast_messages + logger.add(*args, &block) super(*args, &block) end define_method(:<<) do |x| - logger << x if broadcast_messages + logger << x super(x) end @@ -45,7 +54,20 @@ module ActiveSupport def initialize(*args) super @formatter = SimpleFormatter.new - @broadcast_messages = true + after_initialize if respond_to? :after_initialize + end + + def add(severity, message = nil, progname = nil, &block) + return true if @logdev.nil? || (severity || UNKNOWN) < level + super + end + + Logger::Severity.constants.each do |severity| + class_eval(<<-EOT, __FILE__, __LINE__ + 1) + def #{severity.downcase}? # def debug? + Logger::#{severity} >= level # DEBUG >= level + end # end + EOT end # Simple formatter which only displays the message. diff --git a/activesupport/lib/active_support/logger_silence.rb b/activesupport/lib/active_support/logger_silence.rb index 7d92256f24..125d81d973 100644 --- a/activesupport/lib/active_support/logger_silence.rb +++ b/activesupport/lib/active_support/logger_silence.rb @@ -1,25 +1,45 @@ require 'active_support/concern' require 'active_support/core_ext/module/attribute_accessors' +require 'concurrent' module LoggerSilence extend ActiveSupport::Concern included do cattr_accessor :silencer + attr_reader :local_levels self.silencer = true end + def after_initialize + @local_levels = Concurrent::Map.new(:initial_capacity => 2) + end + + def local_log_id + Thread.current.__id__ + end + + def level + local_levels[local_log_id] || super + end + # Silences the logger for the duration of the block. def silence(temporary_level = Logger::ERROR) if silencer begin - old_logger_level, self.level = level, temporary_level + old_local_level = local_levels[local_log_id] + local_levels[local_log_id] = temporary_level + yield self ensure - self.level = old_logger_level + if old_local_level + local_levels[local_log_id] = old_local_level + else + local_levels.delete(local_log_id) + end end else yield self end end -end +end
\ No newline at end of file diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index 586002b03b..72b20fff06 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -87,19 +87,44 @@ module ActiveSupport pos += 1 previous = codepoints[pos-1] current = codepoints[pos] - if ( - # CR X LF - ( previous == database.boundary[:cr] and current == database.boundary[:lf] ) or - # L X (L|V|LV|LVT) - ( database.boundary[:l] === previous and in_char_class?(current, [:l,:v,:lv,:lvt]) ) or - # (LV|V) X (V|T) - ( in_char_class?(previous, [:lv,:v]) and in_char_class?(current, [:v,:t]) ) or - # (LVT|T) X (T) - ( in_char_class?(previous, [:lvt,:t]) and database.boundary[:t] === current ) or - # X Extend - (database.boundary[:extend] === current) - ) - else + + should_break = + # GB3. CR X LF + if previous == database.boundary[:cr] and current == database.boundary[:lf] + false + # GB4. (Control|CR|LF) ÷ + elsif previous and in_char_class?(previous, [:control,:cr,:lf]) + true + # GB5. ÷ (Control|CR|LF) + elsif in_char_class?(current, [:control,:cr,:lf]) + true + # GB6. L X (L|V|LV|LVT) + elsif database.boundary[:l] === previous and in_char_class?(current, [:l,:v,:lv,:lvt]) + false + # GB7. (LV|V) X (V|T) + elsif in_char_class?(previous, [:lv,:v]) and in_char_class?(current, [:v,:t]) + false + # GB8. (LVT|T) X (T) + elsif in_char_class?(previous, [:lvt,:t]) and database.boundary[:t] === current + false + # GB8a. Regional_Indicator X Regional_Indicator + elsif database.boundary[:regional_indicator] === previous and database.boundary[:regional_indicator] === current + false + # GB9. X Extend + elsif database.boundary[:extend] === current + false + # GB9a. X SpacingMark + elsif database.boundary[:spacingmark] === current + false + # GB9b. Prepend X + elsif database.boundary[:prepend] === previous + false + # GB10. Any ÷ Any + else + true + end + + if should_break unpacked << codepoints[marker..pos-1] marker = pos end diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index 67f2ee1a7f..91f94cb2d7 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -21,6 +21,7 @@ module ActiveSupport yield payload rescue Exception => e payload[:exception] = [e.class.name, e.message] + payload[:exception_object] = e raise e ensure finish_with_state listeners_state, name, payload diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index 248521e677..64d9e71f37 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -47,6 +47,14 @@ module ActiveSupport # Formats a +number+ into a currency string (e.g., $13.65). You # can customize the format in the +options+ hash. # + # The currency unit and number formatting of the current locale will be used + # unless otherwise specified in the provided options. No currency conversion + # is performed. If the user is given a way to change their locale, they will + # also be able to change the relative value of the currency displayed with + # this helper. If your application will ever support multiple locales, you + # may want to specify a constant <tt>:locale</tt> option or consider + # using a library capable of currency conversion. + # # ==== Options # # * <tt>:locale</tt> - Sets the locale to be used for formatting @@ -235,6 +243,8 @@ module ActiveSupport # number_to_human_size(1234567) # => 1.18 MB # number_to_human_size(1234567890) # => 1.15 GB # number_to_human_size(1234567890123) # => 1.12 TB + # number_to_human_size(1234567890123456) # => 1.1 PB + # number_to_human_size(1234567890123456789) # => 1.07 EB # number_to_human_size(1234567, precision: 2) # => 1.2 MB # number_to_human_size(483989, precision: 2) # => 470 KB # number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB diff --git a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb index a4a8690bcd..a83b368b7f 100644 --- a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb @@ -1,7 +1,7 @@ module ActiveSupport module NumberHelper class NumberToHumanSizeConverter < NumberConverter #:nodoc: - STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb] + STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb, :pb, :eb] self.namespace = :human self.validate_float = true diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb index 4680d5acb7..b1658f0f27 100644 --- a/activesupport/lib/active_support/ordered_hash.rb +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -5,7 +5,7 @@ YAML.add_builtin_type("omap") do |type, val| end module ActiveSupport - # <tt>ActiveSupport::OrderedHash</tt> implements a hash that preserves + # DEPRECATED: <tt>ActiveSupport::OrderedHash</tt> implements a hash that preserves # insertion order. # # oh = ActiveSupport::OrderedHash.new diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index ae6f00b861..d9a668c0ea 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -9,7 +9,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/testing/composite_filter' require 'active_support/core_ext/kernel/reporting' module ActiveSupport @@ -39,15 +38,6 @@ module ActiveSupport def test_order ActiveSupport.test_order ||= :random end - - def run(reporter, options = {}) - if options[:patterns] && options[:patterns].any? { |p| p =~ /:\d+/ } - options[:filter] = \ - Testing::CompositeFilter.new(self, options[:filter], options[:patterns]) - end - - super - end end alias_method :method_name, :name diff --git a/activesupport/lib/active_support/testing/composite_filter.rb b/activesupport/lib/active_support/testing/composite_filter.rb deleted file mode 100644 index bde723e30b..0000000000 --- a/activesupport/lib/active_support/testing/composite_filter.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'method_source' - -module ActiveSupport - module Testing - class CompositeFilter # :nodoc: - def initialize(runnable, filter, patterns) - @runnable = runnable - @filters = [ derive_regexp(filter), *derive_line_filters(patterns) ].compact - end - - def ===(method) - @filters.any? { |filter| filter === method } - end - - private - def derive_regexp(filter) - filter =~ %r%/(.*)/% ? Regexp.new($1) : filter - end - - def derive_line_filters(patterns) - patterns.map do |file_and_line| - file, line = file_and_line.split(':') - Filter.new(@runnable, file, line) if file - end - end - - class Filter # :nodoc: - def initialize(runnable, file, line) - @runnable, @file = runnable, File.expand_path(file) - @line = line.to_i if line - end - - def ===(method) - return unless @runnable.method_defined?(method) - - if @line - test_file, test_range = definition_for(@runnable.instance_method(method)) - test_file == @file && test_range.include?(@line) - else - @runnable.instance_method(method).source_location.first == @file - end - end - - private - def definition_for(method) - file, start_line = method.source_location - end_line = method.source.count("\n") + start_line - 1 - - return file, start_line..end_line - end - end - end - end -end diff --git a/activesupport/test/broadcast_logger_test.rb b/activesupport/test/broadcast_logger_test.rb index e7d56c80c3..6d4e3b74f7 100644 --- a/activesupport/test/broadcast_logger_test.rb +++ b/activesupport/test/broadcast_logger_test.rb @@ -2,69 +2,56 @@ require 'abstract_unit' module ActiveSupport class BroadcastLoggerTest < TestCase - attr_reader :logger, :receiving_logger + attr_reader :logger, :log1, :log2 def setup - @logger = FakeLogger.new - @receiving_logger = FakeLogger.new - @logger.extend Logger.broadcast @receiving_logger + @log1 = FakeLogger.new + @log2 = FakeLogger.new + @log1.extend Logger.broadcast @log2 + @logger = @log1 end def test_debug logger.debug "foo" - assert_equal 'foo', logger.adds.first[2] - assert_equal 'foo', receiving_logger.adds.first[2] - end - - def test_debug_without_message_broadcasts - logger.broadcast_messages = false - logger.debug "foo" - assert_equal 'foo', logger.adds.first[2] - assert_equal [], receiving_logger.adds + assert_equal 'foo', log1.adds.first[2] + assert_equal 'foo', log2.adds.first[2] end def test_close logger.close - assert logger.closed, 'should be closed' - assert receiving_logger.closed, 'should be closed' + assert log1.closed, 'should be closed' + assert log2.closed, 'should be closed' end def test_chevrons logger << "foo" - assert_equal %w{ foo }, logger.chevrons - assert_equal %w{ foo }, receiving_logger.chevrons - end - - def test_chevrons_without_message_broadcasts - logger.broadcast_messages = false - logger << "foo" - assert_equal %w{ foo }, logger.chevrons - assert_equal [], receiving_logger.chevrons + assert_equal %w{ foo }, log1.chevrons + assert_equal %w{ foo }, log2.chevrons end def test_level assert_nil logger.level logger.level = 10 - assert_equal 10, logger.level - assert_equal 10, receiving_logger.level + assert_equal 10, log1.level + assert_equal 10, log2.level end def test_progname assert_nil logger.progname logger.progname = 10 - assert_equal 10, logger.progname - assert_equal 10, receiving_logger.progname + assert_equal 10, log1.progname + assert_equal 10, log2.progname end def test_formatter assert_nil logger.formatter logger.formatter = 10 - assert_equal 10, logger.formatter - assert_equal 10, receiving_logger.formatter + assert_equal 10, log1.formatter + assert_equal 10, log2.formatter end class FakeLogger attr_reader :adds, :closed, :chevrons - attr_accessor :level, :progname, :formatter, :broadcast_messages + attr_accessor :level, :progname, :formatter def initialize @adds = [] @@ -73,7 +60,6 @@ module ActiveSupport @level = nil @progname = nil @formatter = nil - @broadcast_messages = true end def debug msg, &block diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 7bef73136c..4a299429f3 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -493,28 +493,32 @@ module CacheStoreBehavior def test_cache_hit_instrumentation key = "test_key" - subscribe_executed = false - ActiveSupport::Notifications.subscribe "cache_read.active_support" do |name, start, finish, id, payload| - subscribe_executed = true - assert_equal :fetch, payload[:super_operation] - assert payload[:hit] + @events = [] + ActiveSupport::Notifications.subscribe "cache_read.active_support" do |*args| + @events << ActiveSupport::Notifications::Event.new(*args) end assert @cache.write(key, "1", :raw => true) assert @cache.fetch(key) {} - assert subscribe_executed + assert_equal 1, @events.length + assert_equal 'cache_read.active_support', @events[0].name + assert_equal :fetch, @events[0].payload[:super_operation] + assert @events[0].payload[:hit] ensure ActiveSupport::Notifications.unsubscribe "cache_read.active_support" end def test_cache_miss_instrumentation - subscribe_executed = false - ActiveSupport::Notifications.subscribe "cache_read.active_support" do |name, start, finish, id, payload| - subscribe_executed = true - assert_equal :fetch, payload[:super_operation] - assert_not payload[:hit] + @events = [] + ActiveSupport::Notifications.subscribe(/^cache_(.*)\.active_support$/) do |*args| + @events << ActiveSupport::Notifications::Event.new(*args) end assert_not @cache.fetch("bad_key") {} - assert subscribe_executed + assert_equal 3, @events.length + assert_equal 'cache_read.active_support', @events[0].name + assert_equal 'cache_generate.active_support', @events[1].name + assert_equal 'cache_write.active_support', @events[2].name + assert_equal :fetch, @events[0].payload[:super_operation] + assert_not @events[0].payload[:hit] ensure ActiveSupport::Notifications.unsubscribe "cache_read.active_support" end @@ -776,10 +780,12 @@ class FileStoreTest < ActiveSupport::TestCase include AutoloadingCacheBehavior def test_clear - filepath = File.join(cache_dir, ".gitkeep") - FileUtils.touch(filepath) + gitkeep = File.join(cache_dir, ".gitkeep") + keep = File.join(cache_dir, ".keep") + FileUtils.touch([gitkeep, keep]) @cache.clear - assert File.exist?(filepath) + assert File.exist?(gitkeep) + assert File.exist?(keep) end def test_clear_without_cache_dir diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 2119352df0..1b66f784e4 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -1587,9 +1587,9 @@ class HashToXmlTest < ActiveSupport::TestCase assert_equal 3, hash_wia[:new_key] end - def test_should_use_default_proc_if_no_key_is_supplied + def test_should_return_nil_if_no_key_is_supplied hash_wia = HashWithIndifferentAccess.new { 1 + 2 } - assert_equal 3, hash_wia.default + assert_equal nil, hash_wia.default end def test_should_use_default_value_for_unknown_key diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb index 0ff8f0f89b..5654aeb4f8 100644 --- a/activesupport/test/core_ext/numeric_ext_test.rb +++ b/activesupport/test/core_ext/numeric_ext_test.rb @@ -143,6 +143,14 @@ class NumericExtFormattingTest < ActiveSupport::TestCase gigabytes(number) * 1024 end + def petabytes(number) + terabytes(number) * 1024 + end + + def exabytes(number) + petabytes(number) * 1024 + end + def test_to_s__phone assert_equal("555-1234", 5551234.to_s(:phone)) assert_equal("800-555-1212", 8005551212.to_s(:phone)) @@ -266,7 +274,9 @@ class NumericExtFormattingTest < ActiveSupport::TestCase assert_equal '1.18 MB', 1234567.to_s(:human_size) assert_equal '1.15 GB', 1234567890.to_s(:human_size) assert_equal '1.12 TB', 1234567890123.to_s(:human_size) - assert_equal '1030 TB', terabytes(1026).to_s(:human_size) + assert_equal '1.1 PB', 1234567890123456.to_s(:human_size) + assert_equal '1.07 EB', 1234567890123456789.to_s(:human_size) + assert_equal '1030 EB', exabytes(1026).to_s(:human_size) assert_equal '444 KB', kilobytes(444).to_s(:human_size) assert_equal '1020 MB', megabytes(1023).to_s(:human_size) assert_equal '3 TB', terabytes(3).to_s(:human_size) @@ -289,6 +299,8 @@ class NumericExtFormattingTest < ActiveSupport::TestCase assert_equal '1.23 MB', 1234567.to_s(:human_size, :prefix => :si) assert_equal '1.23 GB', 1234567890.to_s(:human_size, :prefix => :si) assert_equal '1.23 TB', 1234567890123.to_s(:human_size, :prefix => :si) + assert_equal '1.23 PB', 1234567890123456.to_s(:human_size, :prefix => :si) + assert_equal '1.23 EB', 1234567890123456789.to_s(:human_size, :prefix => :si) end end @@ -388,6 +400,32 @@ class NumericExtFormattingTest < ActiveSupport::TestCase assert_equal '1 Million', BigDecimal("1000010").to_s(:human) end + def test_to_formatted_s_is_deprecated + assert_deprecated do + 5551234.to_formatted_s(:phone) + end + end + + def test_to_s_with_invalid_formatter + assert_equal '123', 123.to_s(:invalid) + assert_equal '2.5', 2.5.to_s(:invalid) + assert_equal '100000000000000000000', (100**10).to_s(:invalid) + assert_equal '1000010.0', BigDecimal("1000010").to_s(:invalid) + end + + def test_default_to_s + assert_equal '123', 123.to_s + assert_equal '1111011', 123.to_s(2) + + assert_equal '2.5', 2.5.to_s + + assert_equal '100000000000000000000', (100**10).to_s + assert_equal '1010110101111000111010111100010110101100011000100000000000000000000', (100**10).to_s(2) + + assert_equal '1000010.0', BigDecimal("1000010").to_s + assert_equal '10000 10.0', BigDecimal("1000010").to_s('5F') + end + def test_in_milliseconds assert_equal 10_000, 10.seconds.in_milliseconds end diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb index f096328cee..f28cebda3d 100644 --- a/activesupport/test/core_ext/range_ext_test.rb +++ b/activesupport/test/core_ext/range_ext_test.rb @@ -1,5 +1,6 @@ require 'abstract_unit' require 'active_support/time' +require 'active_support/core_ext/numeric' require 'active_support/core_ext/range' class RangeTest < ActiveSupport::TestCase @@ -13,6 +14,11 @@ class RangeTest < ActiveSupport::TestCase assert_equal "BETWEEN '2005-12-10 15:30:00' AND '2005-12-10 17:30:00'", date_range.to_s(:db) end + def test_to_s_with_numeric + number_range = (1..100) + assert_equal "BETWEEN '1' AND '100'", number_range.to_s(:db) + end + def test_date_range assert_instance_of Range, DateTime.new..DateTime.new assert_instance_of Range, DateTime::Infinity.new..DateTime::Infinity.new diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index cd02ad3f3f..58a0a3964d 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -199,7 +199,7 @@ class DeprecationTest < ActiveSupport::TestCase end def test_assert_deprecated_warn_work_with_default_behavior - ActiveSupport::Deprecation.instance_variable_set('@behavior' , nil) + ActiveSupport::Deprecation.instance_variable_set('@behavior', nil) assert_deprecated('abc') do ActiveSupport::Deprecation.warn 'abc' end @@ -340,6 +340,10 @@ class DeprecationTest < ActiveSupport::TestCase assert_match(/You are calling deprecated method/, object.last_message) end + def test_default_deprecation_horizon_should_always_bigger_than_current_rails_version + assert ActiveSupport::Deprecation.new.deprecation_horizon > ActiveSupport::VERSION::STRING + end + def test_default_gem_name deprecator = ActiveSupport::Deprecation.new diff --git a/activesupport/test/logger_test.rb b/activesupport/test/logger_test.rb index d2801849ca..317e09b7f2 100644 --- a/activesupport/test/logger_test.rb +++ b/activesupport/test/logger_test.rb @@ -3,6 +3,7 @@ require 'multibyte_test_helpers' require 'stringio' require 'fileutils' require 'tempfile' +require 'concurrent/atomics' class LoggerTest < ActiveSupport::TestCase include MultibyteTestHelpers @@ -16,6 +17,14 @@ class LoggerTest < ActiveSupport::TestCase @logger = Logger.new(@output) end + def test_log_outputs_to + assert Logger.logger_outputs_to?(@logger, @output), "Expected logger_outputs_to? @output to return true but was false" + assert Logger.logger_outputs_to?(@logger, @output, STDOUT), "Expected logger_outputs_to? @output or STDOUT to return true but was false" + + assert_not Logger.logger_outputs_to?(@logger, STDOUT), "Expected logger_outputs_to? to STDOUT to return false, but was true" + assert_not Logger.logger_outputs_to?(@logger, STDOUT, STDERR), "Expected logger_outputs_to? to STDOUT or STDERR to return false, but was true" + end + def test_write_binary_data_to_existing_file t = Tempfile.new ['development', 'log'] t.binmode @@ -64,7 +73,7 @@ class LoggerTest < ActiveSupport::TestCase def test_should_not_log_debug_messages_when_log_level_is_info @logger.level = Logger::INFO @logger.add(Logger::DEBUG, @message) - assert ! @output.string.include?(@message) + assert_not @output.string.include?(@message) end def test_should_add_message_passed_as_block_when_using_add @@ -113,6 +122,7 @@ class LoggerTest < ActiveSupport::TestCase end def test_buffer_multibyte + @logger.level = Logger::INFO @logger.info(UNICODE_STRING) @logger.info(BYTE_STRING) assert @output.string.include?(UNICODE_STRING) @@ -120,14 +130,93 @@ class LoggerTest < ActiveSupport::TestCase byte_string.force_encoding("ASCII-8BIT") assert byte_string.include?(BYTE_STRING) end - + def test_silencing_everything_but_errors @logger.silence do @logger.debug "NOT THERE" @logger.error "THIS IS HERE" end - - assert !@output.string.include?("NOT THERE") + + assert_not @output.string.include?("NOT THERE") assert @output.string.include?("THIS IS HERE") end + + def test_logger_level_per_object_thread_safety + logger1 = Logger.new(StringIO.new) + logger2 = Logger.new(StringIO.new) + + level = Logger::DEBUG + assert_equal level, logger1.level, "Expected level #{level_name(level)}, got #{level_name(logger1.level)}" + assert_equal level, logger2.level, "Expected level #{level_name(level)}, got #{level_name(logger2.level)}" + + logger1.level = Logger::ERROR + assert_equal level, logger2.level, "Expected level #{level_name(level)}, got #{level_name(logger2.level)}" + end + + def test_logger_level_main_thread_safety + @logger.level = Logger::INFO + assert_level(Logger::INFO) + + latch = Concurrent::CountDownLatch.new + latch2 = Concurrent::CountDownLatch.new + + t = Thread.new do + latch.wait + assert_level(Logger::INFO) + latch2.count_down + end + + @logger.silence(Logger::ERROR) do + assert_level(Logger::ERROR) + latch.count_down + latch2.wait + end + + t.join + end + + def test_logger_level_local_thread_safety + @logger.level = Logger::INFO + assert_level(Logger::INFO) + + thread_1_latch = Concurrent::CountDownLatch.new + thread_2_latch = Concurrent::CountDownLatch.new + + threads = (1..2).collect do |thread_number| + Thread.new do + # force thread 2 to wait until thread 1 is already in @logger.silence + thread_2_latch.wait if thread_number == 2 + + @logger.silence(Logger::ERROR) do + assert_level(Logger::ERROR) + @logger.silence(Logger::DEBUG) do + # allow thread 2 to finish but hold thread 1 + if thread_number == 1 + thread_2_latch.count_down + thread_1_latch.wait + end + assert_level(Logger::DEBUG) + end + end + + # allow thread 1 to finish + assert_level(Logger::INFO) + thread_1_latch.count_down if thread_number == 2 + end + end + + threads.each(&:join) + assert_level(Logger::INFO) + end + + private + def level_name(level) + ::Logger::Severity.constants.find do |severity| + Logger.const_get(severity) == level + end.to_s + end + + def assert_level(level) + assert_equal level, @logger.level, "Expected level #{level_name(level)}, got #{level_name(@logger.level)}" + end end diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index 8d4d9d736c..c1e0b19248 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -612,28 +612,54 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase ['abc', 3], ['こにちわ', 4], [[0x0924, 0x094D, 0x0930].pack('U*'), 2], + # GB3 [%w(cr lf), 1], + # GB4 + [%w(cr n), 2], + [%w(lf n), 2], + [%w(control n), 2], + [%w(cr extend), 2], + [%w(lf extend), 2], + [%w(control extend), 2], + # GB 5 + [%w(n cr), 2], + [%w(n lf), 2], + [%w(n control), 2], + [%w(extend cr), 2], + [%w(extend lf), 2], + [%w(extend control), 2], + # GB 6 [%w(l l), 1], [%w(l v), 1], [%w(l lv), 1], [%w(l lvt), 1], + # GB7 [%w(lv v), 1], [%w(lv t), 1], [%w(v v), 1], [%w(v t), 1], + # GB8 [%w(lvt t), 1], [%w(t t), 1], + # GB8a + [%w(r r), 1], + # GB9 [%w(n extend), 1], + # GB9a + [%w(n spacingmark), 1], + # GB10 [%w(n n), 2], + # Other [%w(n cr lf n), 3], - [%w(n l v t), 2] + [%w(n l v t), 2], + [%w(cr extend n), 3], ].each do |input, expected_length| if input.kind_of?(Array) str = string_from_classes(input) else str = input end - assert_equal expected_length, chars(str).grapheme_length + assert_equal expected_length, chars(str).grapheme_length, input.inspect end end @@ -698,7 +724,7 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase # Characters from the character classes as described in UAX #29 character_from_class = { :l => 0x1100, :v => 0x1160, :t => 0x11A8, :lv => 0xAC00, :lvt => 0xAC01, :cr => 0x000D, :lf => 0x000A, - :extend => 0x094D, :n => 0x64 + :extend => 0x094D, :n => 0x64, :spacingmark => 0x0903, :r => 0x1F1E6, :control => 0x0001 } classes.collect do |k| character_from_class[k.intern] diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb index d493a48fe4..5df8f32e46 100644 --- a/activesupport/test/multibyte_conformance_test.rb +++ b/activesupport/test/multibyte_conformance_test.rb @@ -5,25 +5,25 @@ require 'fileutils' require 'open-uri' require 'tmpdir' -class Downloader - def self.download(from, to) - unless File.exist?(to) - unless File.exist?(File.dirname(to)) - system "mkdir -p #{File.dirname(to)}" - end - open(from) do |source| - File.open(to, 'w') do |target| - source.each_line do |l| - target.write l +class MultibyteConformanceTest < ActiveSupport::TestCase + class Downloader + def self.download(from, to) + unless File.exist?(to) + unless File.exist?(File.dirname(to)) + system "mkdir -p #{File.dirname(to)}" + end + open(from) do |source| + File.open(to, 'w') do |target| + source.each_line do |l| + target.write l + end end end end + true end - true end -end -class MultibyteConformanceTest < ActiveSupport::TestCase include MultibyteTestHelpers UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd" diff --git a/activesupport/test/multibyte_grapheme_break_conformance_test.rb b/activesupport/test/multibyte_grapheme_break_conformance_test.rb new file mode 100644 index 0000000000..229f24990e --- /dev/null +++ b/activesupport/test/multibyte_grapheme_break_conformance_test.rb @@ -0,0 +1,76 @@ +# encoding: utf-8 + +require 'abstract_unit' + +require 'fileutils' +require 'open-uri' +require 'tmpdir' + +class MultibyteGraphemeBreakConformanceTest < ActiveSupport::TestCase + class Downloader + def self.download(from, to) + unless File.exist?(to) + $stderr.puts "Downloading #{from} to #{to}" + unless File.exist?(File.dirname(to)) + system "mkdir -p #{File.dirname(to)}" + end + open(from) do |source| + File.open(to, 'w') do |target| + source.each_line do |l| + target.write l + end + end + end + end + end + end + + TEST_DATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd/auxiliary" + TEST_DATA_FILE = '/GraphemeBreakTest.txt' + CACHE_DIR = File.join(Dir.tmpdir, 'cache') + + def setup + FileUtils.mkdir_p(CACHE_DIR) + Downloader.download(TEST_DATA_URL + TEST_DATA_FILE, CACHE_DIR + TEST_DATA_FILE) + end + + def test_breaks + each_line_of_break_tests do |*cols| + *clusters, comment = *cols + packed = ActiveSupport::Multibyte::Unicode.pack_graphemes(clusters) + assert_equal clusters, ActiveSupport::Multibyte::Unicode.unpack_graphemes(packed), comment + end + end + + protected + def each_line_of_break_tests(&block) + lines = 0 + max_test_lines = 0 # Don't limit below 21, because that's the header of the testfile + File.open(File.join(CACHE_DIR, TEST_DATA_FILE), 'r') do | f | + until f.eof? || (max_test_lines > 21 and lines > max_test_lines) + lines += 1 + line = f.gets.chomp! + next if (line.empty? || line =~ /^\#/) + + cols, comment = line.split("#") + # Cluster breaks are represented by ÷ + clusters = cols.split("÷").map{|e| e.strip}.reject{|e| e.empty? } + clusters = clusters.map do |cluster| + # Codepoints within each cluster are separated by × + codepoints = cluster.split("×").map{|e| e.strip}.reject{|e| e.empty? } + # codepoints are in hex in the test suite, pack wants them as integers + codepoints.map{|codepoint| codepoint.to_i(16)} + end + + # The tests contain a solitary U+D800 <Non Private Use High + # Surrogate, First> character, which Ruby does not allow to stand + # alone in a UTF-8 string. So we'll just skip it. + next if clusters.flatten.include?(0xd800) + + clusters << comment.strip + + yield(*clusters) + end + end + end +end diff --git a/activesupport/test/multibyte_normalization_conformance_test.rb b/activesupport/test/multibyte_normalization_conformance_test.rb new file mode 100644 index 0000000000..8bc91ef708 --- /dev/null +++ b/activesupport/test/multibyte_normalization_conformance_test.rb @@ -0,0 +1,129 @@ +# encoding: utf-8 + +require 'abstract_unit' +require 'multibyte_test_helpers' + +require 'fileutils' +require 'open-uri' +require 'tmpdir' + +class MultibyteNormalizationConformanceTest < ActiveSupport::TestCase + class Downloader + def self.download(from, to) + unless File.exist?(to) + $stderr.puts "Downloading #{from} to #{to}" + unless File.exist?(File.dirname(to)) + system "mkdir -p #{File.dirname(to)}" + end + open(from) do |source| + File.open(to, 'w') do |target| + source.each_line do |l| + target.write l + end + end + end + end + end + end + + include MultibyteTestHelpers + + UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd" + UNIDATA_FILE = '/NormalizationTest.txt' + CACHE_DIR = File.join(Dir.tmpdir, 'cache') + + def setup + FileUtils.mkdir_p(CACHE_DIR) + Downloader.download(UNIDATA_URL + UNIDATA_FILE, CACHE_DIR + UNIDATA_FILE) + @proxy = ActiveSupport::Multibyte::Chars + end + + def test_normalizations_C + each_line_of_norm_tests do |*cols| + col1, col2, col3, col4, col5, comment = *cols + + # CONFORMANCE: + # 1. The following invariants must be true for all conformant implementations + # + # NFC + # c2 == NFC(c1) == NFC(c2) == NFC(c3) + assert_equal_codepoints col2, @proxy.new(col1).normalize(:c), "Form C - Col 2 has to be NFC(1) - #{comment}" + assert_equal_codepoints col2, @proxy.new(col2).normalize(:c), "Form C - Col 2 has to be NFC(2) - #{comment}" + assert_equal_codepoints col2, @proxy.new(col3).normalize(:c), "Form C - Col 2 has to be NFC(3) - #{comment}" + # + # c4 == NFC(c4) == NFC(c5) + assert_equal_codepoints col4, @proxy.new(col4).normalize(:c), "Form C - Col 4 has to be C(4) - #{comment}" + assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}" + end + end + + def test_normalizations_D + each_line_of_norm_tests do |*cols| + col1, col2, col3, col4, col5, comment = *cols + # + # NFD + # c3 == NFD(c1) == NFD(c2) == NFD(c3) + assert_equal_codepoints col3, @proxy.new(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}" + assert_equal_codepoints col3, @proxy.new(col2).normalize(:d), "Form D - Col 3 has to be NFD(2) - #{comment}" + assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}" + # c5 == NFD(c4) == NFD(c5) + assert_equal_codepoints col5, @proxy.new(col4).normalize(:d), "Form D - Col 5 has to be NFD(4) - #{comment}" + assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}" + end + end + + def test_normalizations_KC + each_line_of_norm_tests do | *cols | + col1, col2, col3, col4, col5, comment = *cols + # + # NFKC + # c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5) + assert_equal_codepoints col4, @proxy.new(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{comment}" + assert_equal_codepoints col4, @proxy.new(col2).normalize(:kc), "Form D - Col 4 has to be NFKC(2) - #{comment}" + assert_equal_codepoints col4, @proxy.new(col3).normalize(:kc), "Form D - Col 4 has to be NFKC(3) - #{comment}" + assert_equal_codepoints col4, @proxy.new(col4).normalize(:kc), "Form D - Col 4 has to be NFKC(4) - #{comment}" + assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{comment}" + end + end + + def test_normalizations_KD + each_line_of_norm_tests do | *cols | + col1, col2, col3, col4, col5, comment = *cols + # + # NFKD + # c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5) + assert_equal_codepoints col5, @proxy.new(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{comment}" + assert_equal_codepoints col5, @proxy.new(col2).normalize(:kd), "Form KD - Col 5 has to be NFKD(2) - #{comment}" + assert_equal_codepoints col5, @proxy.new(col3).normalize(:kd), "Form KD - Col 5 has to be NFKD(3) - #{comment}" + assert_equal_codepoints col5, @proxy.new(col4).normalize(:kd), "Form KD - Col 5 has to be NFKD(4) - #{comment}" + assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{comment}" + end + end + + protected + def each_line_of_norm_tests(&block) + lines = 0 + max_test_lines = 0 # Don't limit below 38, because that's the header of the testfile + File.open(File.join(CACHE_DIR, UNIDATA_FILE), 'r') do | f | + until f.eof? || (max_test_lines > 38 and lines > max_test_lines) + lines += 1 + line = f.gets.chomp! + next if (line.empty? || line =~ /^\#/) + + cols, comment = line.split("#") + cols = cols.split(";").map{|e| e.strip}.reject{|e| e.empty? } + next unless cols.length == 5 + + # codepoints are in hex in the test suite, pack wants them as integers + cols.map!{|c| c.split.map{|codepoint| codepoint.to_i(16)}.pack("U*") } + cols << comment + + yield(*cols) + end + end + end + + def inspect_codepoints(str) + str.to_s.unpack("U*").map{|cp| cp.to_s(16) }.join(' ') + end +end diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index d9cc392ac9..1cb17e6197 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -232,7 +232,7 @@ module Notifications assert_equal 1, @events.size assert_equal Hash[:payload => "notifications", - :exception => ["RuntimeError", "FAIL"]], @events.last.payload + :exception => ["RuntimeError", "FAIL"], :exception_object => e], @events.last.payload end def test_event_is_pushed_even_without_block diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb index 7f62d7c0b3..b3464462c8 100644 --- a/activesupport/test/number_helper_test.rb +++ b/activesupport/test/number_helper_test.rb @@ -34,6 +34,14 @@ module ActiveSupport gigabytes(number) * 1024 end + def petabytes(number) + terabytes(number) * 1024 + end + + def exabytes(number) + petabytes(number) * 1024 + end + def test_number_to_phone [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| assert_equal("555-1234", number_helper.number_to_phone(5551234)) @@ -219,7 +227,9 @@ module ActiveSupport assert_equal '1.18 MB', number_helper.number_to_human_size(1234567) assert_equal '1.15 GB', number_helper.number_to_human_size(1234567890) assert_equal '1.12 TB', number_helper.number_to_human_size(1234567890123) - assert_equal '1030 TB', number_helper.number_to_human_size(terabytes(1026)) + assert_equal '1.1 PB', number_helper.number_to_human_size(1234567890123456) + assert_equal '1.07 EB', number_helper.number_to_human_size(1234567890123456789) + assert_equal '1030 EB', number_helper.number_to_human_size(exabytes(1026)) assert_equal '444 KB', number_helper.number_to_human_size(kilobytes(444)) assert_equal '1020 MB', number_helper.number_to_human_size(megabytes(1023)) assert_equal '3 TB', number_helper.number_to_human_size(terabytes(3)) @@ -245,6 +255,8 @@ module ActiveSupport assert_equal '1.23 MB', number_helper.number_to_human_size(1234567, :prefix => :si) assert_equal '1.23 GB', number_helper.number_to_human_size(1234567890, :prefix => :si) assert_equal '1.23 TB', number_helper.number_to_human_size(1234567890123, :prefix => :si) + assert_equal '1.23 PB', number_helper.number_to_human_size(1234567890123456, :prefix => :si) + assert_equal '1.23 EB', number_helper.number_to_human_size(1234567890123456789, :prefix => :si) end end end |