diff options
Diffstat (limited to 'activesupport/test')
25 files changed, 1223 insertions, 745 deletions
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index c67ffe69b8..f53b98c73e 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -579,6 +579,93 @@ module CacheStoreBehavior end end +module CacheStoreVersionBehavior + ModelWithKeyAndVersion = Struct.new(:cache_key, :cache_version) + + def test_fetch_with_right_version_should_hit + @cache.fetch("foo", version: 1) { "bar" } + assert_equal "bar", @cache.read("foo", version: 1) + end + + def test_fetch_with_wrong_version_should_miss + @cache.fetch("foo", version: 1) { "bar" } + assert_nil @cache.read("foo", version: 2) + end + + def test_read_with_right_version_should_hit + @cache.write("foo", "bar", version: 1) + assert_equal "bar", @cache.read("foo", version: 1) + end + + def test_read_with_wrong_version_should_miss + @cache.write("foo", "bar", version: 1) + assert_nil @cache.read("foo", version: 2) + end + + def test_exist_with_right_version_should_be_true + @cache.write("foo", "bar", version: 1) + assert @cache.exist?("foo", version: 1) + end + + def test_exist_with_wrong_version_should_be_false + @cache.write("foo", "bar", version: 1) + assert !@cache.exist?("foo", version: 2) + end + + def test_reading_and_writing_with_model_supporting_cache_version + m1v1 = ModelWithKeyAndVersion.new("model/1", 1) + m1v2 = ModelWithKeyAndVersion.new("model/1", 2) + + @cache.write(m1v1, "bar") + assert_equal "bar", @cache.read(m1v1) + assert_nil @cache.read(m1v2) + end + + def test_reading_and_writing_with_model_supporting_cache_version_using_nested_key + m1v1 = ModelWithKeyAndVersion.new("model/1", 1) + m1v2 = ModelWithKeyAndVersion.new("model/1", 2) + + @cache.write([ "something", m1v1 ], "bar") + assert_equal "bar", @cache.read([ "something", m1v1 ]) + assert_nil @cache.read([ "something", m1v2 ]) + end + + def test_fetching_with_model_supporting_cache_version + m1v1 = ModelWithKeyAndVersion.new("model/1", 1) + m1v2 = ModelWithKeyAndVersion.new("model/1", 2) + + @cache.fetch(m1v1) { "bar" } + assert_equal "bar", @cache.fetch(m1v1) { "bu" } + assert_equal "bu", @cache.fetch(m1v2) { "bu" } + end + + def test_exist_with_model_supporting_cache_version + m1v1 = ModelWithKeyAndVersion.new("model/1", 1) + m1v2 = ModelWithKeyAndVersion.new("model/1", 2) + + @cache.write(m1v1, "bar") + assert @cache.exist?(m1v1) + assert_not @cache.fetch(m1v2) + end + + def test_fetch_multi_with_model_supporting_cache_version + m1v1 = ModelWithKeyAndVersion.new("model/1", 1) + m2v1 = ModelWithKeyAndVersion.new("model/2", 1) + m2v2 = ModelWithKeyAndVersion.new("model/2", 2) + + first_fetch_values = @cache.fetch_multi(m1v1, m2v1) { |m| m.cache_key } + second_fetch_values = @cache.fetch_multi(m1v1, m2v2) { |m| m.cache_key + " 2nd" } + + assert_equal({ m1v1 => "model/1", m2v1 => "model/2" }, first_fetch_values) + assert_equal({ m1v1 => "model/1", m2v2 => "model/2 2nd" }, second_fetch_values) + end + + def test_version_is_normalized + @cache.write("foo", "bar", version: 1) + assert_equal "bar", @cache.read("foo", version: "1") + end +end + # https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters # The error is caused by character encodings that can't be compared with ASCII-8BIT regular expressions and by special # characters like the umlaut in UTF-8. @@ -708,6 +795,14 @@ module LocalCacheBehavior end end + def test_local_cache_of_write_with_unless_exist + @cache.with_local_cache do + @cache.write("foo", "bar") + @cache.write("foo", "baz", unless_exist: true) + assert_equal @peek.read("foo"), @cache.read("foo") + end + end + def test_local_cache_of_delete @cache.with_local_cache do @cache.write("foo", "bar") @@ -814,6 +909,7 @@ class FileStoreTest < ActiveSupport::TestCase end include CacheStoreBehavior + include CacheStoreVersionBehavior include LocalCacheBehavior include CacheDeleteMatchedBehavior include CacheIncrementDecrementBehavior @@ -923,6 +1019,7 @@ class MemoryStoreTest < ActiveSupport::TestCase end include CacheStoreBehavior + include CacheStoreVersionBehavior include CacheDeleteMatchedBehavior include CacheIncrementDecrementBehavior @@ -1044,6 +1141,7 @@ class MemCacheStoreTest < ActiveSupport::TestCase end include CacheStoreBehavior + include CacheStoreVersionBehavior include LocalCacheBehavior include CacheIncrementDecrementBehavior include EncodedKeyCacheBehavior diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 36f0ee22b8..be7c14e9b4 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -166,6 +166,9 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal DateTime.civil(2005, 2, 22, 16, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16, min: 45) assert_equal DateTime.civil(2005, 2, 22, 15, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(min: 45) + # datetime with non-zero offset + assert_equal DateTime.civil(2005, 2, 22, 15, 15, 10, Rational(-5, 24)), DateTime.civil(2005, 2, 22, 15, 15, 10, 0).change(offset: Rational(-5, 24)) + # datetime with fractions of a second assert_equal DateTime.civil(2005, 2, 1, 15, 15, 10.7), DateTime.civil(2005, 2, 22, 15, 15, 10.7).change(day: 1) assert_equal DateTime.civil(2005, 1, 2, 11, 22, Rational(33000008, 1000000)), DateTime.civil(2005, 1, 2, 11, 22, 33).change(usec: 8) diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index 1648a9b270..3108f24f21 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -337,6 +337,13 @@ class DurationTest < ActiveSupport::TestCase assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message end + def test_scalar_plus_parts + scalar = ActiveSupport::Duration::Scalar.new(10) + + assert_equal({ days: 1, seconds: 10 }, (scalar + 1.day).parts) + assert_equal({ days: -1, seconds: 10 }, (scalar + -1.day).parts) + end + def test_scalar_minus scalar = ActiveSupport::Duration::Scalar.new(10) @@ -349,6 +356,9 @@ class DurationTest < ActiveSupport::TestCase assert_equal 5, scalar - 5.seconds assert_instance_of ActiveSupport::Duration, scalar - 5.seconds + assert_equal({ days: -1, seconds: 10 }, (scalar - 1.day).parts) + assert_equal({ days: 1, seconds: 10 }, (scalar - -1.day).parts) + exception = assert_raises(TypeError) do scalar - "foo" end @@ -356,6 +366,13 @@ class DurationTest < ActiveSupport::TestCase assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message end + def test_scalar_minus_parts + scalar = ActiveSupport::Duration::Scalar.new(10) + + assert_equal({ days: -1, seconds: 10 }, (scalar - 1.day).parts) + assert_equal({ days: 1, seconds: 10 }, (scalar - -1.day).parts) + end + def test_scalar_multiply scalar = ActiveSupport::Duration::Scalar.new(5) @@ -375,6 +392,14 @@ class DurationTest < ActiveSupport::TestCase assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message end + def test_scalar_multiply_parts + scalar = ActiveSupport::Duration::Scalar.new(1) + assert_equal({ days: 2 }, (scalar * 2.days).parts) + assert_equal(172800, (scalar * 2.days).value) + assert_equal({ days: -2 }, (scalar * -2.days).parts) + assert_equal(-172800, (scalar * -2.days).value) + end + def test_scalar_divide scalar = ActiveSupport::Duration::Scalar.new(10) @@ -394,6 +419,15 @@ class DurationTest < ActiveSupport::TestCase assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message end + def test_scalar_divide_parts + scalar = ActiveSupport::Duration::Scalar.new(10) + + assert_equal({ days: 2 }, (scalar / 5.days).parts) + assert_equal(172800, (scalar / 5.days).value) + assert_equal({ days: -2 }, (scalar / -5.days).parts) + assert_equal(-172800, (scalar / -5.days).value) + end + def test_twelve_months_equals_one_year assert_equal 12.months, 1.year end diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 4f1ab993b8..0b345ecf0f 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -171,10 +171,8 @@ class EnumerableTests < ActiveSupport::TestCase assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) }, payments.index_by(&:price)) assert_equal Enumerator, payments.index_by.class - if Enumerator.method_defined? :size - assert_nil payments.index_by.size - assert_equal 42, (1..42).index_by.size - end + assert_nil payments.index_by.size + assert_equal 42, (1..42).index_by.size assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) }, payments.index_by.each(&:price)) end diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 525ea08abd..18da5fcf5f 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -8,33 +8,6 @@ require "active_support/core_ext/object/deep_dup" require "active_support/inflections" class HashExtTest < ActiveSupport::TestCase - HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess - - class IndifferentHash < ActiveSupport::HashWithIndifferentAccess - end - - class SubclassingArray < Array - end - - class SubclassingHash < Hash - end - - class NonIndifferentHash < Hash - def nested_under_indifferent_access - self - end - end - - class HashByConversion - def initialize(hash) - @hash = hash - end - - def to_hash - @hash - end - end - def setup @strings = { "a" => 1, "b" => 2 } @nested_strings = { "a" => { "b" => { "c" => 3 } } } @@ -264,471 +237,6 @@ class HashExtTest < ActiveSupport::TestCase assert_equal({ "a" => { b: { "c" => 3 } } }, @nested_mixed) end - def test_symbolize_keys_for_hash_with_indifferent_access - assert_instance_of Hash, @symbols.with_indifferent_access.symbolize_keys - assert_equal @symbols, @symbols.with_indifferent_access.symbolize_keys - assert_equal @symbols, @strings.with_indifferent_access.symbolize_keys - assert_equal @symbols, @mixed.with_indifferent_access.symbolize_keys - end - - def test_deep_symbolize_keys_for_hash_with_indifferent_access - assert_instance_of Hash, @nested_symbols.with_indifferent_access.deep_symbolize_keys - assert_equal @nested_symbols, @nested_symbols.with_indifferent_access.deep_symbolize_keys - assert_equal @nested_symbols, @nested_strings.with_indifferent_access.deep_symbolize_keys - assert_equal @nested_symbols, @nested_mixed.with_indifferent_access.deep_symbolize_keys - end - - def test_symbolize_keys_bang_for_hash_with_indifferent_access - assert_raise(NoMethodError) { @symbols.with_indifferent_access.dup.symbolize_keys! } - assert_raise(NoMethodError) { @strings.with_indifferent_access.dup.symbolize_keys! } - assert_raise(NoMethodError) { @mixed.with_indifferent_access.dup.symbolize_keys! } - end - - def test_deep_symbolize_keys_bang_for_hash_with_indifferent_access - assert_raise(NoMethodError) { @nested_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! } - assert_raise(NoMethodError) { @nested_strings.with_indifferent_access.deep_dup.deep_symbolize_keys! } - assert_raise(NoMethodError) { @nested_mixed.with_indifferent_access.deep_dup.deep_symbolize_keys! } - end - - def test_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access - assert_equal @illegal_symbols, @illegal_symbols.with_indifferent_access.symbolize_keys - assert_raise(NoMethodError) { @illegal_symbols.with_indifferent_access.dup.symbolize_keys! } - end - - def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access - assert_equal @nested_illegal_symbols, @nested_illegal_symbols.with_indifferent_access.deep_symbolize_keys - assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! } - end - - def test_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access - assert_equal @integers, @integers.with_indifferent_access.symbolize_keys - assert_raise(NoMethodError) { @integers.with_indifferent_access.dup.symbolize_keys! } - end - - def test_deep_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access - assert_equal @nested_integers, @nested_integers.with_indifferent_access.deep_symbolize_keys - assert_raise(NoMethodError) { @nested_integers.with_indifferent_access.deep_dup.deep_symbolize_keys! } - end - - def test_stringify_keys_for_hash_with_indifferent_access - assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.stringify_keys - assert_equal @strings, @symbols.with_indifferent_access.stringify_keys - assert_equal @strings, @strings.with_indifferent_access.stringify_keys - assert_equal @strings, @mixed.with_indifferent_access.stringify_keys - end - - def test_deep_stringify_keys_for_hash_with_indifferent_access - assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.deep_stringify_keys - assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_stringify_keys - assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_stringify_keys - assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_stringify_keys - end - - def test_stringify_keys_bang_for_hash_with_indifferent_access - assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.dup.stringify_keys! - assert_equal @strings, @symbols.with_indifferent_access.dup.stringify_keys! - assert_equal @strings, @strings.with_indifferent_access.dup.stringify_keys! - assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys! - end - - def test_deep_stringify_keys_bang_for_hash_with_indifferent_access - assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.dup.deep_stringify_keys! - assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_dup.deep_stringify_keys! - assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_dup.deep_stringify_keys! - assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_dup.deep_stringify_keys! - end - - def test_nested_under_indifferent_access - foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access - assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"] - - foo = { "foo" => NonIndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access - assert_kind_of NonIndifferentHash, foo["foo"] - - foo = { "foo" => IndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access - assert_kind_of IndifferentHash, foo["foo"] - end - - def test_indifferent_assorted - @strings = @strings.with_indifferent_access - @symbols = @symbols.with_indifferent_access - @mixed = @mixed.with_indifferent_access - - assert_equal "a", @strings.__send__(:convert_key, :a) - - assert_equal 1, @strings.fetch("a") - assert_equal 1, @strings.fetch(:a.to_s) - assert_equal 1, @strings.fetch(:a) - - hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed } - method_map = { '[]': 1, fetch: 1, values_at: [1], - has_key?: true, include?: true, key?: true, - member?: true } - - hashes.each do |name, hash| - method_map.sort_by(&:to_s).each do |meth, expected| - assert_equal(expected, hash.__send__(meth, "a"), - "Calling #{name}.#{meth} 'a'") - assert_equal(expected, hash.__send__(meth, :a), - "Calling #{name}.#{meth} :a") - end - end - - assert_equal [1, 2], @strings.values_at("a", "b") - assert_equal [1, 2], @strings.values_at(:a, :b) - assert_equal [1, 2], @symbols.values_at("a", "b") - assert_equal [1, 2], @symbols.values_at(:a, :b) - assert_equal [1, 2], @mixed.values_at("a", "b") - assert_equal [1, 2], @mixed.values_at(:a, :b) - end - - def test_indifferent_reading - hash = HashWithIndifferentAccess.new - hash["a"] = 1 - hash["b"] = true - hash["c"] = false - hash["d"] = nil - - assert_equal 1, hash[:a] - assert_equal true, hash[:b] - assert_equal false, hash[:c] - assert_nil hash[:d] - assert_nil hash[:e] - end - - def test_indifferent_reading_with_nonnil_default - hash = HashWithIndifferentAccess.new(1) - hash["a"] = 1 - hash["b"] = true - hash["c"] = false - hash["d"] = nil - - assert_equal 1, hash[:a] - assert_equal true, hash[:b] - assert_equal false, hash[:c] - assert_nil hash[:d] - assert_equal 1, hash[:e] - end - - def test_indifferent_writing - hash = HashWithIndifferentAccess.new - hash[:a] = 1 - hash["b"] = 2 - hash[3] = 3 - - assert_equal 1, hash["a"] - assert_equal 2, hash["b"] - assert_equal 1, hash[:a] - assert_equal 2, hash[:b] - assert_equal 3, hash[3] - end - - def test_indifferent_update - hash = HashWithIndifferentAccess.new - hash[:a] = "a" - hash["b"] = "b" - - updated_with_strings = hash.update(@strings) - updated_with_symbols = hash.update(@symbols) - updated_with_mixed = hash.update(@mixed) - - assert_equal 1, updated_with_strings[:a] - assert_equal 1, updated_with_strings["a"] - assert_equal 2, updated_with_strings["b"] - - assert_equal 1, updated_with_symbols[:a] - assert_equal 2, updated_with_symbols["b"] - assert_equal 2, updated_with_symbols[:b] - - assert_equal 1, updated_with_mixed[:a] - assert_equal 2, updated_with_mixed["b"] - - assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 } - end - - def test_update_with_to_hash_conversion - hash = HashWithIndifferentAccess.new - hash.update HashByConversion.new(a: 1) - assert_equal 1, hash["a"] - end - - def test_indifferent_merging - hash = HashWithIndifferentAccess.new - hash[:a] = "failure" - hash["b"] = "failure" - - other = { "a" => 1, :b => 2 } - - merged = hash.merge(other) - - assert_equal HashWithIndifferentAccess, merged.class - assert_equal 1, merged[:a] - assert_equal 2, merged["b"] - - hash.update(other) - - assert_equal 1, hash[:a] - assert_equal 2, hash["b"] - end - - def test_merge_with_to_hash_conversion - hash = HashWithIndifferentAccess.new - merged = hash.merge HashByConversion.new(a: 1) - assert_equal 1, merged["a"] - end - - def test_indifferent_replace - hash = HashWithIndifferentAccess.new - hash[:a] = 42 - - replaced = hash.replace(b: 12) - - assert hash.key?("b") - assert !hash.key?(:a) - assert_equal 12, hash[:b] - assert_same hash, replaced - end - - def test_replace_with_to_hash_conversion - hash = HashWithIndifferentAccess.new - hash[:a] = 42 - - replaced = hash.replace(HashByConversion.new(b: 12)) - - assert hash.key?("b") - assert !hash.key?(:a) - assert_equal 12, hash[:b] - assert_same hash, replaced - end - - def test_indifferent_merging_with_block - hash = HashWithIndifferentAccess.new - hash[:a] = 1 - hash["b"] = 3 - - other = { "a" => 4, :b => 2, "c" => 10 } - - merged = hash.merge(other) { |key, old, new| old > new ? old : new } - - assert_equal HashWithIndifferentAccess, merged.class - assert_equal 4, merged[:a] - assert_equal 3, merged["b"] - assert_equal 10, merged[:c] - - other_indifferent = HashWithIndifferentAccess.new("a" => 9, :b => 2) - - merged = hash.merge(other_indifferent) { |key, old, new| old + new } - - assert_equal HashWithIndifferentAccess, merged.class - assert_equal 10, merged[:a] - assert_equal 5, merged[:b] - end - - def test_indifferent_reverse_merging - hash = HashWithIndifferentAccess.new key: :old_value - hash.reverse_merge! key: :new_value - assert_equal :old_value, hash[:key] - - hash = HashWithIndifferentAccess.new("some" => "value", "other" => "value") - hash.reverse_merge!(some: "noclobber", another: "clobber") - assert_equal "value", hash[:some] - assert_equal "clobber", hash[:another] - end - - def test_indifferent_deleting - get_hash = proc { { a: "foo" }.with_indifferent_access } - hash = get_hash.call - assert_equal "foo", hash.delete(:a) - assert_nil hash.delete(:a) - hash = get_hash.call - assert_equal "foo", hash.delete("a") - assert_nil hash.delete("a") - end - - def test_indifferent_select - hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| v == 1 } - - assert_equal({ "a" => 1 }, hash) - assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash - end - - def test_indifferent_select_returns_enumerator - enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).select - assert_instance_of Enumerator, enum - end - - def test_indifferent_select_returns_a_hash_when_unchanged - hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| true } - - assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash - end - - def test_indifferent_select_bang - indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings) - indifferent_strings.select! { |k, v| v == 1 } - - assert_equal({ "a" => 1 }, indifferent_strings) - assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings - end - - def test_indifferent_reject - hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject { |k, v| v != 1 } - - assert_equal({ "a" => 1 }, hash) - assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash - end - - def test_indifferent_reject_returns_enumerator - enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject - assert_instance_of Enumerator, enum - end - - def test_indifferent_reject_bang - indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings) - indifferent_strings.reject! { |k, v| v != 1 } - - assert_equal({ "a" => 1 }, indifferent_strings) - assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings - end - - def test_indifferent_compact - hash_contain_nil_value = @strings.merge("z" => nil) - hash = ActiveSupport::HashWithIndifferentAccess.new(hash_contain_nil_value) - compacted_hash = hash.compact - - assert_equal(@strings, compacted_hash) - assert_equal(hash_contain_nil_value, hash) - assert_instance_of ActiveSupport::HashWithIndifferentAccess, compacted_hash - - empty_hash = ActiveSupport::HashWithIndifferentAccess.new - compacted_hash = empty_hash.compact - - assert_equal compacted_hash, empty_hash - - non_empty_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: :bar) - compacted_hash = non_empty_hash.compact - - assert_equal compacted_hash, non_empty_hash - end - - def test_indifferent_to_hash - # Should convert to a Hash with String keys. - assert_equal @strings, @mixed.with_indifferent_access.to_hash - - # Should preserve the default value. - mixed_with_default = @mixed.dup - mixed_with_default.default = "1234" - roundtrip = mixed_with_default.with_indifferent_access.to_hash - assert_equal @strings, roundtrip - assert_equal "1234", roundtrip.default - - # Ensure nested hashes are not HashWithIndiffereneAccess - new_to_hash = @nested_mixed.with_indifferent_access.to_hash - assert_not new_to_hash.instance_of?(HashWithIndifferentAccess) - assert_not new_to_hash["a"].instance_of?(HashWithIndifferentAccess) - assert_not new_to_hash["a"]["b"].instance_of?(HashWithIndifferentAccess) - end - - def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access - hash = HashWithIndifferentAccess.new { |h, k| h[k] = [] } - hash[:a] << 1 - - assert_equal [1], hash[:a] - end - - def test_with_indifferent_access_has_no_side_effects_on_existing_hash - hash = { content: [{ :foo => :bar, "bar" => "baz" }] } - hash.with_indifferent_access - - assert_equal [:foo, "bar"], hash[:content].first.keys - end - - def test_indifferent_hash_with_array_of_hashes - hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] } }.with_indifferent_access - assert_equal "1", hash[:urls][:url].first[:address] - - hash = hash.to_hash - assert_not hash.instance_of?(HashWithIndifferentAccess) - assert_not hash["urls"].instance_of?(HashWithIndifferentAccess) - assert_not hash["urls"]["url"].first.instance_of?(HashWithIndifferentAccess) - end - - def test_should_preserve_array_subclass_when_value_is_array - array = SubclassingArray.new - array << { "address" => "1" } - hash = { "urls" => { "url" => array } }.with_indifferent_access - assert_equal SubclassingArray, hash[:urls][:url].class - end - - def test_should_preserve_array_class_when_hash_value_is_frozen_array - array = SubclassingArray.new - array << { "address" => "1" } - hash = { "urls" => { "url" => array.freeze } }.with_indifferent_access - assert_equal SubclassingArray, hash[:urls][:url].class - end - - def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash - h = HashWithIndifferentAccess.new - h[:first] = 1 - h = h.stringify_keys - assert_equal 1, h["first"] - h = HashWithIndifferentAccess.new - h["first"] = 1 - h = h.symbolize_keys - assert_equal 1, h[:first] - end - - def test_deep_stringify_and_deep_symbolize_keys_on_indifferent_preserves_hash - h = HashWithIndifferentAccess.new - h[:first] = 1 - h = h.deep_stringify_keys - assert_equal 1, h["first"] - h = HashWithIndifferentAccess.new - h["first"] = 1 - h = h.deep_symbolize_keys - assert_equal 1, h[:first] - end - - def test_to_options_on_indifferent_preserves_hash - h = HashWithIndifferentAccess.new - h["first"] = 1 - h.to_options! - assert_equal 1, h["first"] - end - - def test_to_options_on_indifferent_preserves_works_as_hash_with_dup - h = HashWithIndifferentAccess.new(a: { b: "b" }) - dup = h.dup - - dup[:a][:c] = "c" - assert_equal "c", h[:a][:c] - end - - def test_indifferent_sub_hashes - h = { "user" => { "id" => 5 } }.with_indifferent_access - ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } } - - h = { user: { id: 5 } }.with_indifferent_access - ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } } - end - - def test_indifferent_duplication - # Should preserve default value - h = HashWithIndifferentAccess.new - h.default = "1234" - assert_equal h.default, h.dup.default - - # Should preserve class for subclasses - h = IndifferentHash.new - assert_equal h.class, h.dup.class - end - - def test_nested_dig_indifferent_access - skip if RUBY_VERSION < "2.3.0" - data = { "this" => { "views" => 1234 } }.with_indifferent_access - assert_equal 1234, data.dig(:this, :views) - end - def test_assert_valid_keys assert_nothing_raised do { failure: "stuff", funny: "business" }.assert_valid_keys([ :failure, :funny ]) @@ -761,12 +269,6 @@ class HashExtTest < ActiveSupport::TestCase assert_equal "Unknown key: :failore. Valid keys are: :failure", exception.message end - def test_assorted_keys_not_stringified - original = { Object.new => 2, 1 => 2, [] => true } - indiff = original.with_indifferent_access - assert(!indiff.keys.any? { |k| k.kind_of? String }, "A key was converted to a string!") - end - def test_deep_merge hash_1 = { a: "a", b: "b", c: { c1: "c1", c2: "c2", c3: { d1: "d1" } } } hash_2 = { a: 1, c: { c1: 2, c3: { d2: "d2" } } } @@ -797,37 +299,6 @@ class HashExtTest < ActiveSupport::TestCase assert_equal expected, hash_1 end - def test_deep_merge_on_indifferent_access - hash_1 = HashWithIndifferentAccess.new(a: "a", b: "b", c: { c1: "c1", c2: "c2", c3: { d1: "d1" } }) - hash_2 = HashWithIndifferentAccess.new(a: 1, c: { c1: 2, c3: { d2: "d2" } }) - hash_3 = { a: 1, c: { c1: 2, c3: { d2: "d2" } } } - expected = { "a" => 1, "b" => "b", "c" => { "c1" => 2, "c2" => "c2", "c3" => { "d1" => "d1", "d2" => "d2" } } } - assert_equal expected, hash_1.deep_merge(hash_2) - assert_equal expected, hash_1.deep_merge(hash_3) - - hash_1.deep_merge!(hash_2) - assert_equal expected, hash_1 - end - - def test_store_on_indifferent_access - hash = HashWithIndifferentAccess.new - hash.store(:test1, 1) - hash.store("test1", 11) - hash[:test2] = 2 - hash["test2"] = 22 - expected = { "test1" => 11, "test2" => 22 } - assert_equal expected, hash - end - - def test_constructor_on_indifferent_access - hash = HashWithIndifferentAccess[:foo, 1] - assert_equal 1, hash[:foo] - assert_equal 1, hash["foo"] - hash[:foo] = 3 - assert_equal 3, hash[:foo] - assert_equal 3, hash["foo"] - end - def test_reverse_merge defaults = { a: "x", b: "y", c: 10 }.freeze options = { a: 1, b: 2 } @@ -848,6 +319,21 @@ class HashExtTest < ActiveSupport::TestCase assert_equal expected, merged end + def test_with_defaults_aliases_reverse_merge + defaults = { a: "x", b: "y", c: 10 }.freeze + options = { a: 1, b: 2 } + expected = { a: 1, b: 2, c: 10 } + + # Should be an alias for reverse_merge + assert_equal expected, options.with_defaults(defaults) + assert_not_equal expected, options + + # Should be an alias for reverse_merge! + merged = options.dup + assert_equal expected, merged.with_defaults!(defaults) + assert_equal expected, merged + end + def test_slice original = { a: "x", b: "y", c: 10 } expected = { a: "x", b: "y" } @@ -890,38 +376,6 @@ class HashExtTest < ActiveSupport::TestCase assert_equal expected, original.slice(*[:a, :b]) end - def test_indifferent_slice - original = { a: "x", b: "y", c: 10 }.with_indifferent_access - expected = { a: "x", b: "y" }.with_indifferent_access - - [["a", "b"], [:a, :b]].each do |keys| - # Should return a new hash with only the given keys. - assert_equal expected, original.slice(*keys), keys.inspect - assert_not_equal expected, original - end - end - - def test_indifferent_slice_inplace - original = { a: "x", b: "y", c: 10 }.with_indifferent_access - expected = { c: 10 }.with_indifferent_access - - [["a", "b"], [:a, :b]].each do |keys| - # Should replace the hash with only the given keys. - copy = original.dup - assert_equal expected, copy.slice!(*keys) - end - end - - def test_indifferent_slice_access_with_symbols - original = { "login" => "bender", "password" => "shiny", "stuff" => "foo" } - original = original.with_indifferent_access - - slice = original.slice(:login, :password) - - assert_equal "bender", slice[:login] - assert_equal "bender", slice["login"] - end - def test_slice_bang_does_not_override_default hash = Hash.new(0) hash.update(a: 1, b: 2) @@ -959,18 +413,6 @@ class HashExtTest < ActiveSupport::TestCase assert_nil extracted[:x] end - def test_indifferent_extract - original = { :a => 1, "b" => 2, :c => 3, "d" => 4 }.with_indifferent_access - expected = { a: 1, b: 2 }.with_indifferent_access - remaining = { c: 3, d: 4 }.with_indifferent_access - - [["a", "b"], [:a, :b]].each do |keys| - copy = original.dup - assert_equal expected, copy.extract!(*keys) - assert_equal remaining, copy - end - end - def test_except original = { a: "x", b: "y", c: 10 } expected = { a: "x", b: "y" } @@ -1042,78 +484,6 @@ class HashExtTest < ActiveSupport::TestCase assert_nil(h.compact!) assert_equal(@symbols, h) end - - def test_new_with_to_hash_conversion - hash = HashWithIndifferentAccess.new(HashByConversion.new(a: 1)) - assert hash.key?("a") - assert_equal 1, hash[:a] - end - - def test_dup_with_default_proc - hash = HashWithIndifferentAccess.new - hash.default_proc = proc { |h, v| raise "walrus" } - assert_nothing_raised { hash.dup } - end - - def test_dup_with_default_proc_sets_proc - hash = HashWithIndifferentAccess.new - hash.default_proc = proc { |h, k| k + 1 } - new_hash = hash.dup - - assert_equal 3, new_hash[2] - - new_hash.default = 2 - assert_equal 2, new_hash[:non_existent] - end - - def test_to_hash_with_raising_default_proc - hash = HashWithIndifferentAccess.new - hash.default_proc = proc { |h, k| raise "walrus" } - - assert_nothing_raised { hash.to_hash } - end - - def test_new_with_to_hash_conversion_copies_default - normal_hash = Hash.new(3) - normal_hash[:a] = 1 - - hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash)) - assert_equal 1, hash[:a] - assert_equal 3, hash[:b] - end - - def test_new_with_to_hash_conversion_copies_default_proc - normal_hash = Hash.new { 1 + 2 } - normal_hash[:a] = 1 - - hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash)) - assert_equal 1, hash[:a] - assert_equal 3, hash[:b] - end - - def test_inheriting_from_top_level_hash_with_indifferent_access_preserves_ancestors_chain - klass = Class.new(::HashWithIndifferentAccess) - assert_equal ActiveSupport::HashWithIndifferentAccess, klass.ancestors[1] - end - - def test_inheriting_from_hash_with_indifferent_access_properly_dumps_ivars - klass = Class.new(::HashWithIndifferentAccess) do - def initialize(*) - @foo = "bar" - super - end - end - - yaml_output = klass.new.to_yaml - - # `hash-with-ivars` was introduced in 2.0.9 (https://git.io/vyUQW) - if Gem::Version.new(Psych::VERSION) >= Gem::Version.new("2.0.9") - assert_includes yaml_output, "hash-with-ivars" - assert_includes yaml_output, "@foo: bar" - else - assert_includes yaml_output, "hash" - end - end end class IWriteMyOwnXML @@ -1159,8 +529,6 @@ class HashExtToParamTests < ActiveSupport::TestCase end class HashToXmlTest < ActiveSupport::TestCase - HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess - def setup @xml_options = { root: :person, skip_instruct: true, indent: 0 } end @@ -1636,61 +1004,6 @@ class HashToXmlTest < ActiveSupport::TestCase assert_equal expected, Hash.from_trusted_xml('<product><name type="yaml">:value</name></product>') end - def test_should_use_default_proc_for_unknown_key - hash_wia = HashWithIndifferentAccess.new { 1 + 2 } - assert_equal 3, hash_wia[:new_key] - end - - def test_should_return_nil_if_no_key_is_supplied - hash_wia = HashWithIndifferentAccess.new { 1 + 2 } - assert_nil hash_wia.default - end - - def test_should_use_default_value_for_unknown_key - hash_wia = HashWithIndifferentAccess.new(3) - assert_equal 3, hash_wia[:new_key] - end - - def test_should_use_default_value_if_no_key_is_supplied - hash_wia = HashWithIndifferentAccess.new(3) - assert_equal 3, hash_wia.default - end - - def test_should_nil_if_no_default_value_is_supplied - hash_wia = HashWithIndifferentAccess.new - assert_nil hash_wia.default - end - - def test_should_return_dup_for_with_indifferent_access - hash_wia = HashWithIndifferentAccess.new - assert_equal hash_wia, hash_wia.with_indifferent_access - assert_not_same hash_wia, hash_wia.with_indifferent_access - end - - def test_allows_setting_frozen_array_values_with_indifferent_access - value = [1, 2, 3].freeze - hash = HashWithIndifferentAccess.new - hash[:key] = value - assert_equal hash[:key], value - end - - def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access - hash = Hash.new(3) - hash_wia = hash.with_indifferent_access - assert_equal 3, hash_wia.default - end - - def test_should_copy_the_default_proc_when_converting_to_hash_with_indifferent_access - hash = Hash.new do - 2 + 1 - end - assert_equal 3, hash[:foo] - - hash_wia = hash.with_indifferent_access - assert_equal 3, hash_wia[:foo] - assert_equal 3, hash_wia[:bar] - end - # The XML builder seems to fail miserably when trying to tag something # with the same name as a Kernel method (throw, test, loop, select ...) def test_kernel_method_names_to_xml diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index a17438bf4d..a4d4444d69 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -70,7 +70,23 @@ Product = Struct.new(:name) do end end +module ExtraMissing + def method_missing(sym, *args) + if sym == :extra_missing + 42 + else + super + end + end + + def respond_to_missing?(sym, priv = false) + sym == :extra_missing || super + end +end + DecoratedTester = Struct.new(:client) do + include ExtraMissing + delegate_missing_to :client end @@ -332,15 +348,15 @@ class ModuleTest < ActiveSupport::TestCase assert has_block.hello? end - def test_delegate_to_missing_with_method + def test_delegate_missing_to_with_method assert_equal "David", DecoratedTester.new(@david).name end - def test_delegate_to_missing_with_reserved_methods + def test_delegate_missing_to_with_reserved_methods assert_equal "David", DecoratedReserved.new(@david).name end - def test_delegate_to_missing_does_not_delegate_to_private_methods + def test_delegate_missing_to_does_not_delegate_to_private_methods e = assert_raises(NoMethodError) do DecoratedReserved.new(@david).private_name end @@ -348,7 +364,7 @@ class ModuleTest < ActiveSupport::TestCase assert_match(/undefined method `private_name' for/, e.message) end - def test_delegate_to_missing_does_not_delegate_to_fake_methods + def test_delegate_missing_to_does_not_delegate_to_fake_methods e = assert_raises(NoMethodError) do DecoratedReserved.new(@david).my_fake_method end @@ -356,6 +372,22 @@ class ModuleTest < ActiveSupport::TestCase assert_match(/undefined method `my_fake_method' for/, e.message) end + def test_delegate_missing_to_affects_respond_to + assert DecoratedTester.new(@david).respond_to?(:name) + assert_not DecoratedTester.new(@david).respond_to?(:private_name) + assert_not DecoratedTester.new(@david).respond_to?(:my_fake_method) + + assert DecoratedTester.new(@david).respond_to?(:name, true) + assert_not DecoratedTester.new(@david).respond_to?(:private_name, true) + assert_not DecoratedTester.new(@david).respond_to?(:my_fake_method, true) + end + + def test_delegate_missing_to_respects_superclass_missing + assert_equal 42, DecoratedTester.new(@david).extra_missing + + assert_respond_to DecoratedTester.new(@david), :extra_missing + end + def test_delegate_with_case event = Event.new(Tester.new) assert_equal 1, event.foo diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb index e6f3c8aef2..68b0129980 100644 --- a/activesupport/test/core_ext/object/duplicable_test.rb +++ b/activesupport/test/core_ext/object/duplicable_test.rb @@ -4,7 +4,10 @@ require "active_support/core_ext/object/duplicable" require "active_support/core_ext/numeric/time" class DuplicableTest < ActiveSupport::TestCase - if RUBY_VERSION >= "2.4.1" + if RUBY_VERSION >= "2.5.0" + RAISE_DUP = [method(:puts)] + ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56"), nil, false, true, 1, 2.3, Complex(1), Rational(1)] + elsif RUBY_VERSION >= "2.4.1" RAISE_DUP = [method(:puts), Complex(1), Rational(1)] ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56"), nil, false, true, 1, 2.3] elsif RUBY_VERSION >= "2.4.0" # Due to 2.4.0 bug. This elsif cannot be removed unless we drop 2.4.0 support... diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 41cc9888c6..a98951e889 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -78,6 +78,12 @@ class StringInflectionsTest < ActiveSupport::TestCase end end + def test_titleize_with_keep_id_suffix + MixtureToTitleCaseWithKeepIdSuffix.each do |before, titleized| + assert_equal(titleized, before.titleize(keep_id_suffix: true)) + end + end + def test_upcase_first assert_equal "What a Lovely Day", "what a Lovely Day".upcase_first end @@ -199,6 +205,12 @@ class StringInflectionsTest < ActiveSupport::TestCase end end + def test_humanize_with_keep_id_suffix + UnderscoreToHumanWithKeepIdSuffix.each do |underscore, human| + assert_equal(human, underscore.humanize(keep_id_suffix: true)) + end + end + def test_humanize_with_html_escape assert_equal "Hello", ERB::Util.html_escape("hello").humanize end diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index bd644c8457..625a5bffb8 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -433,6 +433,13 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "-08:00").change(nsec: 1000000000) } end + def test_change_offset + assert_equal Time.new(2006, 2, 22, 15, 15, 10, "-08:00"), Time.new(2006, 2, 22, 15, 15, 10, "+01:00").change(offset: "-08:00") + assert_equal Time.new(2006, 2, 22, 15, 15, 10, -28800), Time.new(2006, 2, 22, 15, 15, 10, 3600).change(offset: -28800) + assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "+01:00").change(usec: 1000000, offset: "-08:00") } + assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "+01:00").change(nsec: 1000000000, offset: -28800) } + end + def test_advance assert_equal Time.local(2006, 2, 28, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(years: 1) assert_equal Time.local(2005, 6, 28, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(months: 4) diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index c3afe68378..70ae793cda 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -625,6 +625,12 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal "Fri, 31 Dec 1999 06:00:00 EST -05:00", @twz.change(hour: 6).inspect assert_equal "Fri, 31 Dec 1999 19:15:00 EST -05:00", @twz.change(min: 15).inspect assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.change(sec: 30).inspect + assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(offset: "-10:00").inspect + assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(offset: -36000).inspect + assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(zone: "Hawaii").inspect + assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(zone: -10).inspect + assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(zone: -36000).inspect + assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(zone: "Pacific/Honolulu").inspect end def test_change_at_dst_boundary diff --git a/activesupport/test/current_attributes_test.rb b/activesupport/test/current_attributes_test.rb new file mode 100644 index 0000000000..67ef6ef619 --- /dev/null +++ b/activesupport/test/current_attributes_test.rb @@ -0,0 +1,96 @@ +require "abstract_unit" + +class CurrentAttributesTest < ActiveSupport::TestCase + Person = Struct.new(:name, :time_zone) + + class Current < ActiveSupport::CurrentAttributes + attribute :world, :account, :person, :request + delegate :time_zone, to: :person + + resets { Time.zone = "UTC" } + + def account=(account) + super + self.person = "#{account}'s person" + end + + def person=(person) + super + Time.zone = person.try(:time_zone) + end + + def request + "#{super} something" + end + + def intro + "#{person.name}, in #{time_zone}" + end + end + + setup { Current.reset } + + test "read and write attribute" do + Current.world = "world/1" + assert_equal "world/1", Current.world + end + + test "read overwritten attribute method" do + Current.request = "request/1" + assert_equal "request/1 something", Current.request + end + + test "set attribute via overwritten method" do + Current.account = "account/1" + assert_equal "account/1", Current.account + assert_equal "account/1's person", Current.person + end + + test "set auxiliary class via overwritten method" do + Current.person = Person.new("David", "Central Time (US & Canada)") + assert_equal "Central Time (US & Canada)", Time.zone.name + end + + test "resets auxiliary class via callback" do + Current.person = Person.new("David", "Central Time (US & Canada)") + assert_equal "Central Time (US & Canada)", Time.zone.name + + Current.reset + assert_equal "UTC", Time.zone.name + end + + test "set attribute only via scope" do + Current.world = "world/1" + + Current.set(world: "world/2") do + assert_equal "world/2", Current.world + end + + assert_equal "world/1", Current.world + end + + test "set multiple attributes" do + Current.world = "world/1" + Current.account = "account/1" + + Current.set(world: "world/2", account: "account/2") do + assert_equal "world/2", Current.world + assert_equal "account/2", Current.account + end + + assert_equal "world/1", Current.world + assert_equal "account/1", Current.account + end + + test "delegation" do + Current.person = Person.new("David", "Central Time (US & Canada)") + assert_equal "Central Time (US & Canada)", Current.time_zone + assert_equal "Central Time (US & Canada)", Current.instance.time_zone + end + + test "all methods forward to the instance" do + Current.person = Person.new("David", "Central Time (US & Canada)") + assert_equal "David, in Central Time (US & Canada)", Current.intro + assert_equal "David, in Central Time (US & Canada)", Current.instance.intro + end +end diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index e38d4e83e5..1ea36418ff 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -104,7 +104,7 @@ class DependenciesTest < ActiveSupport::TestCase with_loading "dependencies" do old_warnings, ActiveSupport::Dependencies.warnings_on_first_load = ActiveSupport::Dependencies.warnings_on_first_load, true filename = "check_warnings" - expanded = File.expand_path("#{File.dirname(__FILE__)}/dependencies/#{filename}") + expanded = File.expand_path("dependencies/#{filename}", __dir__) $check_warnings_load_count = 0 assert_not ActiveSupport::Dependencies.loaded.include?(expanded) @@ -293,7 +293,7 @@ class DependenciesTest < ActiveSupport::TestCase end def test_doesnt_break_normal_require - path = File.expand_path("../autoloading_fixtures/load_path", __FILE__) + path = File.expand_path("autoloading_fixtures/load_path", __dir__) original_path = $:.dup $:.push(path) with_autoloading_fixtures do @@ -312,7 +312,7 @@ class DependenciesTest < ActiveSupport::TestCase end def test_doesnt_break_normal_require_nested - path = File.expand_path("../autoloading_fixtures/load_path", __FILE__) + path = File.expand_path("autoloading_fixtures/load_path", __dir__) original_path = $:.dup $:.push(path) @@ -332,7 +332,7 @@ class DependenciesTest < ActiveSupport::TestCase end def test_require_returns_true_when_file_not_yet_required - path = File.expand_path("../autoloading_fixtures/load_path", __FILE__) + path = File.expand_path("autoloading_fixtures/load_path", __dir__) original_path = $:.dup $:.push(path) @@ -345,7 +345,7 @@ class DependenciesTest < ActiveSupport::TestCase end def test_require_returns_true_when_file_not_yet_required_even_when_no_new_constants_added - path = File.expand_path("../autoloading_fixtures/load_path", __FILE__) + path = File.expand_path("autoloading_fixtures/load_path", __dir__) original_path = $:.dup $:.push(path) @@ -359,7 +359,7 @@ class DependenciesTest < ActiveSupport::TestCase end def test_require_returns_false_when_file_already_required - path = File.expand_path("../autoloading_fixtures/load_path", __FILE__) + path = File.expand_path("autoloading_fixtures/load_path", __dir__) original_path = $:.dup $:.push(path) @@ -379,7 +379,7 @@ class DependenciesTest < ActiveSupport::TestCase end def test_load_returns_true_when_file_found - path = File.expand_path("../autoloading_fixtures/load_path", __FILE__) + path = File.expand_path("autoloading_fixtures/load_path", __dir__) original_path = $:.dup $:.push(path) @@ -438,7 +438,7 @@ class DependenciesTest < ActiveSupport::TestCase def test_loadable_constants_for_path_should_handle_relative_paths fake_root = "dependencies" - relative_root = File.dirname(__FILE__) + "/dependencies" + relative_root = File.expand_path("dependencies", __dir__) ["", "/"].each do |suffix| with_loading fake_root + suffix do assert_equal ["A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(relative_root + "/a/b") @@ -463,7 +463,7 @@ class DependenciesTest < ActiveSupport::TestCase end def test_loadable_constants_with_load_path_without_trailing_slash - path = File.dirname(__FILE__) + "/autoloading_fixtures/class_folder/inline_class.rb" + path = File.expand_path("autoloading_fixtures/class_folder/inline_class.rb", __dir__) with_loading "autoloading_fixtures/class/" do assert_equal [], ActiveSupport::Dependencies.loadable_constants_for_path(path) end @@ -991,7 +991,7 @@ class DependenciesTest < ActiveSupport::TestCase def test_remove_constant_does_not_trigger_loading_autoloads constant = "ShouldNotBeAutoloaded" Object.class_eval do - autoload constant, File.expand_path("../autoloading_fixtures/should_not_be_required", __FILE__) + autoload constant, File.expand_path("autoloading_fixtures/should_not_be_required", __dir__) end assert_nil ActiveSupport::Dependencies.remove_constant(constant), "Kernel#autoload has been triggered by remove_constant" diff --git a/activesupport/test/dependencies_test_helpers.rb b/activesupport/test/dependencies_test_helpers.rb index 9bc63ed89e..451195a143 100644 --- a/activesupport/test/dependencies_test_helpers.rb +++ b/activesupport/test/dependencies_test_helpers.rb @@ -1,7 +1,7 @@ module DependenciesTestHelpers def with_loading(*from) old_mechanism, ActiveSupport::Dependencies.mechanism = ActiveSupport::Dependencies.mechanism, :load - this_dir = File.dirname(__FILE__) + this_dir = __dir__ parent_dir = File.dirname(this_dir) path_copy = $LOAD_PATH.dup $LOAD_PATH.unshift(parent_dir) unless $LOAD_PATH.include?(parent_dir) diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index 5f72fbf662..257cb50fb2 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -35,6 +35,18 @@ class Deprecatee A = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("Deprecatee::A", "Deprecatee::B::C") end +class DeprecateeWithAccessor + include ActiveSupport::Deprecation::DeprecatedConstantAccessor + + module B + C = 1 + end + deprecate_constant "A", "DeprecateeWithAccessor::B::C" + + class NewException < StandardError; end + deprecate_constant "OldException", "DeprecateeWithAccessor::NewException" +end + class DeprecationTest < ActiveSupport::TestCase include ActiveSupport::Testing::Stream @@ -88,16 +100,18 @@ class DeprecationTest < ActiveSupport::TestCase end def test_several_behaviors - @a, @b = nil, nil + @a, @b, @c = nil, nil, nil ActiveSupport::Deprecation.behavior = [ - Proc.new { |msg, callstack| @a = msg }, - Proc.new { |msg, callstack| @b = msg } + lambda { |msg, callstack, horizon, gem| @a = msg }, + lambda { |msg, callstack| @b = msg }, + lambda { |*args| @c = args }, ] @dtc.partially assert_match(/foo=nil/, @a) assert_match(/foo=nil/, @b) + assert_equal 4, @c.size end def test_raise_behaviour @@ -107,7 +121,7 @@ class DeprecationTest < ActiveSupport::TestCase callstack = caller_locations e = assert_raise ActiveSupport::DeprecationException do - ActiveSupport::Deprecation.behavior.first.call(message, callstack) + ActiveSupport::Deprecation.behavior.first.call(message, callstack, "horizon", "gem") end assert_equal message, e.message assert_equal callstack.map(&:to_s), e.backtrace.map(&:to_s) @@ -118,7 +132,7 @@ class DeprecationTest < ActiveSupport::TestCase behavior = ActiveSupport::Deprecation.behavior.first content = capture(:stderr) { - assert_nil behavior.call("Some error!", ["call stack!"]) + assert_nil behavior.call("Some error!", ["call stack!"], "horizon", "gem") } assert_match(/Some error!/, content) assert_match(/call stack!/, content) @@ -140,11 +154,32 @@ class DeprecationTest < ActiveSupport::TestCase behavior = ActiveSupport::Deprecation.behavior.first stderr_output = capture(:stderr) { - assert_nil behavior.call("Some error!", ["call stack!"]) + assert_nil behavior.call("Some error!", ["call stack!"], "horizon", "gem") } assert stderr_output.empty? end + def test_default_notify_behavior + ActiveSupport::Deprecation.behavior = :notify + behavior = ActiveSupport::Deprecation.behavior.first + + begin + events = [] + ActiveSupport::Notifications.subscribe("deprecation.my_gem_custom") { |_, **args| + events << args + } + + assert_nil behavior.call("Some error!", ["call stack!"], "horizon", "MyGem::Custom") + assert_equal 1, events.size + assert_equal "Some error!", events.first[:message] + assert_equal ["call stack!"], events.first[:callstack] + assert_equal "horizon", events.first[:deprecation_horizon] + assert_equal "MyGem::Custom", events.first[:gem_name] + ensure + ActiveSupport::Notifications.unsubscribe("deprecation.my_gem_custom") + end + end + def test_deprecated_instance_variable_proxy assert_not_deprecated { @dtc.request.size } @@ -162,6 +197,17 @@ class DeprecationTest < ActiveSupport::TestCase assert_not_deprecated { assert_equal Deprecatee::B::C.class, Deprecatee::A.class } end + def test_deprecated_constant_accessor + assert_not_deprecated { DeprecateeWithAccessor::B::C } + assert_deprecated("DeprecateeWithAccessor::A") { assert_equal DeprecateeWithAccessor::B::C, DeprecateeWithAccessor::A } + end + + def test_deprecated_constant_accessor_exception + raise DeprecateeWithAccessor::NewException.new("Test") + rescue DeprecateeWithAccessor::OldException => e + assert_kind_of DeprecateeWithAccessor::NewException, e + end + def test_assert_deprecated_raises_when_method_not_deprecated assert_raises(Minitest::Assertion) { assert_deprecated { @dtc.not } } end diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb new file mode 100644 index 0000000000..d68add46cd --- /dev/null +++ b/activesupport/test/hash_with_indifferent_access_test.rb @@ -0,0 +1,745 @@ +require "abstract_unit" +require "active_support/core_ext/hash" +require "bigdecimal" +require "active_support/core_ext/string/access" +require "active_support/ordered_hash" +require "active_support/core_ext/object/conversions" +require "active_support/core_ext/object/deep_dup" +require "active_support/inflections" + +class HashWithIndifferentAccessTest < ActiveSupport::TestCase + HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess + + class IndifferentHash < ActiveSupport::HashWithIndifferentAccess + end + + class SubclassingArray < Array + end + + class SubclassingHash < Hash + end + + class NonIndifferentHash < Hash + def nested_under_indifferent_access + self + end + end + + class HashByConversion + def initialize(hash) + @hash = hash + end + + def to_hash + @hash + end + end + + def setup + @strings = { "a" => 1, "b" => 2 } + @nested_strings = { "a" => { "b" => { "c" => 3 } } } + @symbols = { a: 1, b: 2 } + @nested_symbols = { a: { b: { c: 3 } } } + @mixed = { :a => 1, "b" => 2 } + @nested_mixed = { "a" => { b: { "c" => 3 } } } + @integers = { 0 => 1, 1 => 2 } + @nested_integers = { 0 => { 1 => { 2 => 3 } } } + @illegal_symbols = { [] => 3 } + @nested_illegal_symbols = { [] => { [] => 3 } } + end + + def test_symbolize_keys_for_hash_with_indifferent_access + assert_instance_of Hash, @symbols.with_indifferent_access.symbolize_keys + assert_equal @symbols, @symbols.with_indifferent_access.symbolize_keys + assert_equal @symbols, @strings.with_indifferent_access.symbolize_keys + assert_equal @symbols, @mixed.with_indifferent_access.symbolize_keys + end + + def test_deep_symbolize_keys_for_hash_with_indifferent_access + assert_instance_of Hash, @nested_symbols.with_indifferent_access.deep_symbolize_keys + assert_equal @nested_symbols, @nested_symbols.with_indifferent_access.deep_symbolize_keys + assert_equal @nested_symbols, @nested_strings.with_indifferent_access.deep_symbolize_keys + assert_equal @nested_symbols, @nested_mixed.with_indifferent_access.deep_symbolize_keys + end + + def test_symbolize_keys_bang_for_hash_with_indifferent_access + assert_raise(NoMethodError) { @symbols.with_indifferent_access.dup.symbolize_keys! } + assert_raise(NoMethodError) { @strings.with_indifferent_access.dup.symbolize_keys! } + assert_raise(NoMethodError) { @mixed.with_indifferent_access.dup.symbolize_keys! } + end + + def test_deep_symbolize_keys_bang_for_hash_with_indifferent_access + assert_raise(NoMethodError) { @nested_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! } + assert_raise(NoMethodError) { @nested_strings.with_indifferent_access.deep_dup.deep_symbolize_keys! } + assert_raise(NoMethodError) { @nested_mixed.with_indifferent_access.deep_dup.deep_symbolize_keys! } + end + + def test_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access + assert_equal @illegal_symbols, @illegal_symbols.with_indifferent_access.symbolize_keys + assert_raise(NoMethodError) { @illegal_symbols.with_indifferent_access.dup.symbolize_keys! } + end + + def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access + assert_equal @nested_illegal_symbols, @nested_illegal_symbols.with_indifferent_access.deep_symbolize_keys + assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! } + end + + def test_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access + assert_equal @integers, @integers.with_indifferent_access.symbolize_keys + assert_raise(NoMethodError) { @integers.with_indifferent_access.dup.symbolize_keys! } + end + + def test_deep_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access + assert_equal @nested_integers, @nested_integers.with_indifferent_access.deep_symbolize_keys + assert_raise(NoMethodError) { @nested_integers.with_indifferent_access.deep_dup.deep_symbolize_keys! } + end + + def test_stringify_keys_for_hash_with_indifferent_access + assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.stringify_keys + assert_equal @strings, @symbols.with_indifferent_access.stringify_keys + assert_equal @strings, @strings.with_indifferent_access.stringify_keys + assert_equal @strings, @mixed.with_indifferent_access.stringify_keys + end + + def test_deep_stringify_keys_for_hash_with_indifferent_access + assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.deep_stringify_keys + assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_stringify_keys + assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_stringify_keys + assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_stringify_keys + end + + def test_stringify_keys_bang_for_hash_with_indifferent_access + assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.dup.stringify_keys! + assert_equal @strings, @symbols.with_indifferent_access.dup.stringify_keys! + assert_equal @strings, @strings.with_indifferent_access.dup.stringify_keys! + assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys! + end + + def test_deep_stringify_keys_bang_for_hash_with_indifferent_access + assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.dup.deep_stringify_keys! + assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_dup.deep_stringify_keys! + assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_dup.deep_stringify_keys! + assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_dup.deep_stringify_keys! + end + + def test_nested_under_indifferent_access + foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access + assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"] + + foo = { "foo" => NonIndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access + assert_kind_of NonIndifferentHash, foo["foo"] + + foo = { "foo" => IndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access + assert_kind_of IndifferentHash, foo["foo"] + end + + def test_indifferent_assorted + @strings = @strings.with_indifferent_access + @symbols = @symbols.with_indifferent_access + @mixed = @mixed.with_indifferent_access + + assert_equal "a", @strings.__send__(:convert_key, :a) + + assert_equal 1, @strings.fetch("a") + assert_equal 1, @strings.fetch(:a.to_s) + assert_equal 1, @strings.fetch(:a) + + hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed } + method_map = { '[]': 1, fetch: 1, values_at: [1], + has_key?: true, include?: true, key?: true, + member?: true } + + hashes.each do |name, hash| + method_map.sort_by(&:to_s).each do |meth, expected| + assert_equal(expected, hash.__send__(meth, "a"), + "Calling #{name}.#{meth} 'a'") + assert_equal(expected, hash.__send__(meth, :a), + "Calling #{name}.#{meth} :a") + end + end + + assert_equal [1, 2], @strings.values_at("a", "b") + assert_equal [1, 2], @strings.values_at(:a, :b) + assert_equal [1, 2], @symbols.values_at("a", "b") + assert_equal [1, 2], @symbols.values_at(:a, :b) + assert_equal [1, 2], @mixed.values_at("a", "b") + assert_equal [1, 2], @mixed.values_at(:a, :b) + end + + def test_indifferent_fetch_values + skip unless Hash.method_defined?(:fetch_values) + + @mixed = @mixed.with_indifferent_access + + assert_equal [1, 2], @mixed.fetch_values("a", "b") + assert_equal [1, 2], @mixed.fetch_values(:a, :b) + assert_equal [1, 2], @mixed.fetch_values(:a, "b") + assert_equal [1, "c"], @mixed.fetch_values(:a, :c) { |key| key } + assert_raise(KeyError) { @mixed.fetch_values(:a, :c) } + end + + def test_indifferent_reading + hash = HashWithIndifferentAccess.new + hash["a"] = 1 + hash["b"] = true + hash["c"] = false + hash["d"] = nil + + assert_equal 1, hash[:a] + assert_equal true, hash[:b] + assert_equal false, hash[:c] + assert_nil hash[:d] + assert_nil hash[:e] + end + + def test_indifferent_reading_with_nonnil_default + hash = HashWithIndifferentAccess.new(1) + hash["a"] = 1 + hash["b"] = true + hash["c"] = false + hash["d"] = nil + + assert_equal 1, hash[:a] + assert_equal true, hash[:b] + assert_equal false, hash[:c] + assert_nil hash[:d] + assert_equal 1, hash[:e] + end + + def test_indifferent_writing + hash = HashWithIndifferentAccess.new + hash[:a] = 1 + hash["b"] = 2 + hash[3] = 3 + + assert_equal 1, hash["a"] + assert_equal 2, hash["b"] + assert_equal 1, hash[:a] + assert_equal 2, hash[:b] + assert_equal 3, hash[3] + end + + def test_indifferent_update + hash = HashWithIndifferentAccess.new + hash[:a] = "a" + hash["b"] = "b" + + updated_with_strings = hash.update(@strings) + updated_with_symbols = hash.update(@symbols) + updated_with_mixed = hash.update(@mixed) + + assert_equal 1, updated_with_strings[:a] + assert_equal 1, updated_with_strings["a"] + assert_equal 2, updated_with_strings["b"] + + assert_equal 1, updated_with_symbols[:a] + assert_equal 2, updated_with_symbols["b"] + assert_equal 2, updated_with_symbols[:b] + + assert_equal 1, updated_with_mixed[:a] + assert_equal 2, updated_with_mixed["b"] + + assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 } + end + + def test_update_with_to_hash_conversion + hash = HashWithIndifferentAccess.new + hash.update HashByConversion.new(a: 1) + assert_equal 1, hash["a"] + end + + def test_indifferent_merging + hash = HashWithIndifferentAccess.new + hash[:a] = "failure" + hash["b"] = "failure" + + other = { "a" => 1, :b => 2 } + + merged = hash.merge(other) + + assert_equal HashWithIndifferentAccess, merged.class + assert_equal 1, merged[:a] + assert_equal 2, merged["b"] + + hash.update(other) + + assert_equal 1, hash[:a] + assert_equal 2, hash["b"] + end + + def test_merge_with_to_hash_conversion + hash = HashWithIndifferentAccess.new + merged = hash.merge HashByConversion.new(a: 1) + assert_equal 1, merged["a"] + end + + def test_indifferent_replace + hash = HashWithIndifferentAccess.new + hash[:a] = 42 + + replaced = hash.replace(b: 12) + + assert hash.key?("b") + assert !hash.key?(:a) + assert_equal 12, hash[:b] + assert_same hash, replaced + end + + def test_replace_with_to_hash_conversion + hash = HashWithIndifferentAccess.new + hash[:a] = 42 + + replaced = hash.replace(HashByConversion.new(b: 12)) + + assert hash.key?("b") + assert !hash.key?(:a) + assert_equal 12, hash[:b] + assert_same hash, replaced + end + + def test_indifferent_merging_with_block + hash = HashWithIndifferentAccess.new + hash[:a] = 1 + hash["b"] = 3 + + other = { "a" => 4, :b => 2, "c" => 10 } + + merged = hash.merge(other) { |key, old, new| old > new ? old : new } + + assert_equal HashWithIndifferentAccess, merged.class + assert_equal 4, merged[:a] + assert_equal 3, merged["b"] + assert_equal 10, merged[:c] + + other_indifferent = HashWithIndifferentAccess.new("a" => 9, :b => 2) + + merged = hash.merge(other_indifferent) { |key, old, new| old + new } + + assert_equal HashWithIndifferentAccess, merged.class + assert_equal 10, merged[:a] + assert_equal 5, merged[:b] + end + + def test_indifferent_reverse_merging + hash = HashWithIndifferentAccess.new key: :old_value + hash.reverse_merge! key: :new_value + assert_equal :old_value, hash[:key] + + hash = HashWithIndifferentAccess.new("some" => "value", "other" => "value") + hash.reverse_merge!(some: "noclobber", another: "clobber") + assert_equal "value", hash[:some] + assert_equal "clobber", hash[:another] + end + + def test_indifferent_with_defaults_aliases_reverse_merge + hash = HashWithIndifferentAccess.new key: :old_value + actual = hash.with_defaults key: :new_value + assert_equal :old_value, actual[:key] + + hash = HashWithIndifferentAccess.new key: :old_value + hash.with_defaults! key: :new_value + assert_equal :old_value, hash[:key] + end + + def test_indifferent_deleting + get_hash = proc { { a: "foo" }.with_indifferent_access } + hash = get_hash.call + assert_equal "foo", hash.delete(:a) + assert_nil hash.delete(:a) + hash = get_hash.call + assert_equal "foo", hash.delete("a") + assert_nil hash.delete("a") + end + + def test_indifferent_select + hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| v == 1 } + + assert_equal({ "a" => 1 }, hash) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash + end + + def test_indifferent_select_returns_enumerator + enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).select + assert_instance_of Enumerator, enum + end + + def test_indifferent_select_returns_a_hash_when_unchanged + hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| true } + + assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash + end + + def test_indifferent_select_bang + indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings) + indifferent_strings.select! { |k, v| v == 1 } + + assert_equal({ "a" => 1 }, indifferent_strings) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings + end + + def test_indifferent_reject + hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject { |k, v| v != 1 } + + assert_equal({ "a" => 1 }, hash) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash + end + + def test_indifferent_reject_returns_enumerator + enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject + assert_instance_of Enumerator, enum + end + + def test_indifferent_reject_bang + indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings) + indifferent_strings.reject! { |k, v| v != 1 } + + assert_equal({ "a" => 1 }, indifferent_strings) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings + end + + def test_indifferent_compact + hash_contain_nil_value = @strings.merge("z" => nil) + hash = ActiveSupport::HashWithIndifferentAccess.new(hash_contain_nil_value) + compacted_hash = hash.compact + + assert_equal(@strings, compacted_hash) + assert_equal(hash_contain_nil_value, hash) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, compacted_hash + + empty_hash = ActiveSupport::HashWithIndifferentAccess.new + compacted_hash = empty_hash.compact + + assert_equal compacted_hash, empty_hash + + non_empty_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: :bar) + compacted_hash = non_empty_hash.compact + + assert_equal compacted_hash, non_empty_hash + end + + def test_indifferent_to_hash + # Should convert to a Hash with String keys. + assert_equal @strings, @mixed.with_indifferent_access.to_hash + + # Should preserve the default value. + mixed_with_default = @mixed.dup + mixed_with_default.default = "1234" + roundtrip = mixed_with_default.with_indifferent_access.to_hash + assert_equal @strings, roundtrip + assert_equal "1234", roundtrip.default + + # Ensure nested hashes are not HashWithIndiffereneAccess + new_to_hash = @nested_mixed.with_indifferent_access.to_hash + assert_not new_to_hash.instance_of?(HashWithIndifferentAccess) + assert_not new_to_hash["a"].instance_of?(HashWithIndifferentAccess) + assert_not new_to_hash["a"]["b"].instance_of?(HashWithIndifferentAccess) + end + + def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access + hash = HashWithIndifferentAccess.new { |h, k| h[k] = [] } + hash[:a] << 1 + + assert_equal [1], hash[:a] + end + + def test_with_indifferent_access_has_no_side_effects_on_existing_hash + hash = { content: [{ :foo => :bar, "bar" => "baz" }] } + hash.with_indifferent_access + + assert_equal [:foo, "bar"], hash[:content].first.keys + end + + def test_indifferent_hash_with_array_of_hashes + hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] } }.with_indifferent_access + assert_equal "1", hash[:urls][:url].first[:address] + + hash = hash.to_hash + assert_not hash.instance_of?(HashWithIndifferentAccess) + assert_not hash["urls"].instance_of?(HashWithIndifferentAccess) + assert_not hash["urls"]["url"].first.instance_of?(HashWithIndifferentAccess) + end + + def test_should_preserve_array_subclass_when_value_is_array + array = SubclassingArray.new + array << { "address" => "1" } + hash = { "urls" => { "url" => array } }.with_indifferent_access + assert_equal SubclassingArray, hash[:urls][:url].class + end + + def test_should_preserve_array_class_when_hash_value_is_frozen_array + array = SubclassingArray.new + array << { "address" => "1" } + hash = { "urls" => { "url" => array.freeze } }.with_indifferent_access + assert_equal SubclassingArray, hash[:urls][:url].class + end + + def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash + h = HashWithIndifferentAccess.new + h[:first] = 1 + h = h.stringify_keys + assert_equal 1, h["first"] + h = HashWithIndifferentAccess.new + h["first"] = 1 + h = h.symbolize_keys + assert_equal 1, h[:first] + end + + def test_deep_stringify_and_deep_symbolize_keys_on_indifferent_preserves_hash + h = HashWithIndifferentAccess.new + h[:first] = 1 + h = h.deep_stringify_keys + assert_equal 1, h["first"] + h = HashWithIndifferentAccess.new + h["first"] = 1 + h = h.deep_symbolize_keys + assert_equal 1, h[:first] + end + + def test_to_options_on_indifferent_preserves_hash + h = HashWithIndifferentAccess.new + h["first"] = 1 + h.to_options! + assert_equal 1, h["first"] + end + + def test_to_options_on_indifferent_preserves_works_as_hash_with_dup + h = HashWithIndifferentAccess.new(a: { b: "b" }) + dup = h.dup + + dup[:a][:c] = "c" + assert_equal "c", h[:a][:c] + end + + def test_indifferent_sub_hashes + h = { "user" => { "id" => 5 } }.with_indifferent_access + ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } } + + h = { user: { id: 5 } }.with_indifferent_access + ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } } + end + + def test_indifferent_duplication + # Should preserve default value + h = HashWithIndifferentAccess.new + h.default = "1234" + assert_equal h.default, h.dup.default + + # Should preserve class for subclasses + h = IndifferentHash.new + assert_equal h.class, h.dup.class + end + + def test_nested_dig_indifferent_access + skip if RUBY_VERSION < "2.3.0" + data = { "this" => { "views" => 1234 } }.with_indifferent_access + assert_equal 1234, data.dig(:this, :views) + end + + def test_assorted_keys_not_stringified + original = { Object.new => 2, 1 => 2, [] => true } + indiff = original.with_indifferent_access + assert(!indiff.keys.any? { |k| k.kind_of? String }, "A key was converted to a string!") + end + + def test_deep_merge_on_indifferent_access + hash_1 = HashWithIndifferentAccess.new(a: "a", b: "b", c: { c1: "c1", c2: "c2", c3: { d1: "d1" } }) + hash_2 = HashWithIndifferentAccess.new(a: 1, c: { c1: 2, c3: { d2: "d2" } }) + hash_3 = { a: 1, c: { c1: 2, c3: { d2: "d2" } } } + expected = { "a" => 1, "b" => "b", "c" => { "c1" => 2, "c2" => "c2", "c3" => { "d1" => "d1", "d2" => "d2" } } } + assert_equal expected, hash_1.deep_merge(hash_2) + assert_equal expected, hash_1.deep_merge(hash_3) + + hash_1.deep_merge!(hash_2) + assert_equal expected, hash_1 + end + + def test_store_on_indifferent_access + hash = HashWithIndifferentAccess.new + hash.store(:test1, 1) + hash.store("test1", 11) + hash[:test2] = 2 + hash["test2"] = 22 + expected = { "test1" => 11, "test2" => 22 } + assert_equal expected, hash + end + + def test_constructor_on_indifferent_access + hash = HashWithIndifferentAccess[:foo, 1] + assert_equal 1, hash[:foo] + assert_equal 1, hash["foo"] + hash[:foo] = 3 + assert_equal 3, hash[:foo] + assert_equal 3, hash["foo"] + end + + def test_indifferent_slice + original = { a: "x", b: "y", c: 10 }.with_indifferent_access + expected = { a: "x", b: "y" }.with_indifferent_access + + [["a", "b"], [:a, :b]].each do |keys| + # Should return a new hash with only the given keys. + assert_equal expected, original.slice(*keys), keys.inspect + assert_not_equal expected, original + end + end + + def test_indifferent_slice_inplace + original = { a: "x", b: "y", c: 10 }.with_indifferent_access + expected = { c: 10 }.with_indifferent_access + + [["a", "b"], [:a, :b]].each do |keys| + # Should replace the hash with only the given keys. + copy = original.dup + assert_equal expected, copy.slice!(*keys) + end + end + + def test_indifferent_slice_access_with_symbols + original = { "login" => "bender", "password" => "shiny", "stuff" => "foo" } + original = original.with_indifferent_access + + slice = original.slice(:login, :password) + + assert_equal "bender", slice[:login] + assert_equal "bender", slice["login"] + end + + def test_indifferent_extract + original = { :a => 1, "b" => 2, :c => 3, "d" => 4 }.with_indifferent_access + expected = { a: 1, b: 2 }.with_indifferent_access + remaining = { c: 3, d: 4 }.with_indifferent_access + + [["a", "b"], [:a, :b]].each do |keys| + copy = original.dup + assert_equal expected, copy.extract!(*keys) + assert_equal remaining, copy + end + end + + def test_new_with_to_hash_conversion + hash = HashWithIndifferentAccess.new(HashByConversion.new(a: 1)) + assert hash.key?("a") + assert_equal 1, hash[:a] + end + + def test_dup_with_default_proc + hash = HashWithIndifferentAccess.new + hash.default_proc = proc { |h, v| raise "walrus" } + assert_nothing_raised { hash.dup } + end + + def test_dup_with_default_proc_sets_proc + hash = HashWithIndifferentAccess.new + hash.default_proc = proc { |h, k| k + 1 } + new_hash = hash.dup + + assert_equal 3, new_hash[2] + + new_hash.default = 2 + assert_equal 2, new_hash[:non_existent] + end + + def test_to_hash_with_raising_default_proc + hash = HashWithIndifferentAccess.new + hash.default_proc = proc { |h, k| raise "walrus" } + + assert_nothing_raised { hash.to_hash } + end + + def test_new_with_to_hash_conversion_copies_default + normal_hash = Hash.new(3) + normal_hash[:a] = 1 + + hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash)) + assert_equal 1, hash[:a] + assert_equal 3, hash[:b] + end + + def test_new_with_to_hash_conversion_copies_default_proc + normal_hash = Hash.new { 1 + 2 } + normal_hash[:a] = 1 + + hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash)) + assert_equal 1, hash[:a] + assert_equal 3, hash[:b] + end + + def test_inheriting_from_top_level_hash_with_indifferent_access_preserves_ancestors_chain + klass = Class.new(::HashWithIndifferentAccess) + assert_equal ActiveSupport::HashWithIndifferentAccess, klass.ancestors[1] + end + + def test_inheriting_from_hash_with_indifferent_access_properly_dumps_ivars + klass = Class.new(::HashWithIndifferentAccess) do + def initialize(*) + @foo = "bar" + super + end + end + + yaml_output = klass.new.to_yaml + + # `hash-with-ivars` was introduced in 2.0.9 (https://git.io/vyUQW) + if Gem::Version.new(Psych::VERSION) >= Gem::Version.new("2.0.9") + assert_includes yaml_output, "hash-with-ivars" + assert_includes yaml_output, "@foo: bar" + else + assert_includes yaml_output, "hash" + end + end + + def test_should_use_default_proc_for_unknown_key + hash_wia = HashWithIndifferentAccess.new { 1 + 2 } + assert_equal 3, hash_wia[:new_key] + end + + def test_should_return_nil_if_no_key_is_supplied + hash_wia = HashWithIndifferentAccess.new { 1 + 2 } + assert_nil hash_wia.default + end + + def test_should_use_default_value_for_unknown_key + hash_wia = HashWithIndifferentAccess.new(3) + assert_equal 3, hash_wia[:new_key] + end + + def test_should_use_default_value_if_no_key_is_supplied + hash_wia = HashWithIndifferentAccess.new(3) + assert_equal 3, hash_wia.default + end + + def test_should_nil_if_no_default_value_is_supplied + hash_wia = HashWithIndifferentAccess.new + assert_nil hash_wia.default + end + + def test_should_return_dup_for_with_indifferent_access + hash_wia = HashWithIndifferentAccess.new + assert_equal hash_wia, hash_wia.with_indifferent_access + assert_not_same hash_wia, hash_wia.with_indifferent_access + end + + def test_allows_setting_frozen_array_values_with_indifferent_access + value = [1, 2, 3].freeze + hash = HashWithIndifferentAccess.new + hash[:key] = value + assert_equal hash[:key], value + end + + def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access + hash = Hash.new(3) + hash_wia = hash.with_indifferent_access + assert_equal 3, hash_wia.default + end + + def test_should_copy_the_default_proc_when_converting_to_hash_with_indifferent_access + hash = Hash.new do + 2 + 1 + end + assert_equal 3, hash[:foo] + + hash_wia = hash.with_indifferent_access + assert_equal 3, hash_wia[:foo] + assert_equal 3, hash_wia[:bar] + end +end diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 03d7b3fe94..ef956eda90 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -119,6 +119,13 @@ class InflectorTest < ActiveSupport::TestCase end end + MixtureToTitleCaseWithKeepIdSuffix.each_with_index do |(before, titleized), index| + define_method "test_titleize_with_keep_id_suffix_mixture_to_title_case_#{index}" do + assert_equal(titleized, ActiveSupport::Inflector.titleize(before, keep_id_suffix: true), + "mixture to TitleCase with keep_id_suffix failed for #{before}") + end + end + def test_camelize CamelToUnderscore.each do |camel, underscore| assert_equal(camel, ActiveSupport::Inflector.camelize(underscore)) @@ -324,6 +331,12 @@ class InflectorTest < ActiveSupport::TestCase end end + def test_humanize_with_keep_id_suffix + UnderscoreToHumanWithKeepIdSuffix.each do |underscore, human| + assert_equal(human, ActiveSupport::Inflector.humanize(underscore, keep_id_suffix: true)) + end + end + def test_humanize_by_rule ActiveSupport::Inflector.inflections do |inflect| inflect.human(/_cnt$/i, '\1_count') @@ -407,6 +420,8 @@ class InflectorTest < ActiveSupport::TestCase inflect.singular(/es$/, "") inflect.irregular("el", "los") + + inflect.uncountable("agua") end assert_equal("hijos", "hijo".pluralize(:es)) @@ -419,12 +434,17 @@ class InflectorTest < ActiveSupport::TestCase assert_equal("los", "el".pluralize(:es)) assert_equal("els", "el".pluralize) + assert_equal("agua", "agua".pluralize(:es)) + assert_equal("aguas", "agua".pluralize) + ActiveSupport::Inflector.inflections(:es) { |inflect| inflect.clear } assert ActiveSupport::Inflector.inflections(:es).plurals.empty? assert ActiveSupport::Inflector.inflections(:es).singulars.empty? + assert ActiveSupport::Inflector.inflections(:es).uncountables.empty? assert !ActiveSupport::Inflector.inflections.plurals.empty? assert !ActiveSupport::Inflector.inflections.singulars.empty? + assert !ActiveSupport::Inflector.inflections.uncountables.empty? end def test_clear_all diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index f3352e3301..d61ca3fc18 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -248,12 +248,27 @@ module InflectorTestCases "_external_id" => "External" } + UnderscoreToHumanWithKeepIdSuffix = { + "this_is_a_string_ending_with_id" => "This is a string ending with id", + "employee_id" => "Employee id", + "employee_id_something_else" => "Employee id something else", + "underground" => "Underground", + "_id" => "Id", + "_external_id" => "External id" + } + UnderscoreToHumanWithoutCapitalize = { "employee_salary" => "employee salary", "employee_id" => "employee", "underground" => "underground" } + MixtureToTitleCaseWithKeepIdSuffix = { + "this_is_a_string_ending_with_id" => "This Is A String Ending With Id", + "EmployeeId" => "Employee Id", + "Author Id" => "Author Id" + } + MixtureToTitleCase = { "active_record" => "Active Record", "ActiveRecord" => "Active Record", diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb index 56a436f751..4c3515b5e1 100644 --- a/activesupport/test/message_encryptor_test.rb +++ b/activesupport/test/message_encryptor_test.rb @@ -86,20 +86,32 @@ class MessageEncryptorTest < ActiveSupport::TestCase assert_equal @data, encryptor.decrypt_and_verify(message) end + def test_aead_mode_with_hmac_cbc_cipher_text + encryptor = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm") + + assert_aead_not_decrypted(encryptor, "eHdGeExnZEwvMSt3U3dKaFl1WFo0TjVvYzA0eGpjbm5WSkt5MXlsNzhpZ0ZnbWhBWFlQZTRwaXE1bVJCS2oxMDZhYVp2dVN3V0lNZUlWQ3c2eVhQbnhnVjFmeVVubmhRKzF3WnZyWHVNMDg9LS1HSisyakJVSFlPb05ISzRMaXRzcFdBPT0=--831a1d54a3cda8a0658dc668a03dedcbce13b5ca") + end + def test_messing_with_aead_values_causes_failures encryptor = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm") text, iv, auth_tag = encryptor.encrypt_and_sign(@data).split("--") - assert_not_decrypted([iv, text, auth_tag] * "--") - assert_not_decrypted([munge(text), iv, auth_tag] * "--") - assert_not_decrypted([text, munge(iv), auth_tag] * "--") - assert_not_decrypted([text, iv, munge(auth_tag)] * "--") - assert_not_decrypted([munge(text), munge(iv), munge(auth_tag)] * "--") - assert_not_decrypted([text, iv] * "--") - assert_not_decrypted([text, iv, auth_tag[0..-2]] * "--") + assert_aead_not_decrypted(encryptor, [iv, text, auth_tag] * "--") + assert_aead_not_decrypted(encryptor, [munge(text), iv, auth_tag] * "--") + assert_aead_not_decrypted(encryptor, [text, munge(iv), auth_tag] * "--") + assert_aead_not_decrypted(encryptor, [text, iv, munge(auth_tag)] * "--") + assert_aead_not_decrypted(encryptor, [munge(text), munge(iv), munge(auth_tag)] * "--") + assert_aead_not_decrypted(encryptor, [text, iv] * "--") + assert_aead_not_decrypted(encryptor, [text, iv, auth_tag[0..-2]] * "--") end private + def assert_aead_not_decrypted(encryptor, value) + assert_raise(ActiveSupport::MessageEncryptor::InvalidMessage) do + encryptor.decrypt_and_verify(value) + end + end + def assert_not_decrypted(value) assert_raise(ActiveSupport::MessageEncryptor::InvalidMessage) do @encryptor.decrypt_and_verify(@verifier.generate(value)) diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb index f7eb047d44..3bdd1651e7 100644 --- a/activesupport/test/rescuable_test.rb +++ b/activesupport/test/rescuable_test.rb @@ -43,7 +43,9 @@ class Stargate def dispatch(method) send(method) rescue Exception => e - rescue_with_handler(e) + unless rescue_with_handler(e) + @result = "unhandled" + end end def attack @@ -58,6 +60,26 @@ class Stargate raise MadRonon.new("dex") end + def crash + raise "unhandled RuntimeError" + end + + def looped_crash + ex1 = StandardError.new("error 1") + ex2 = StandardError.new("error 2") + begin + begin + raise ex1 + rescue + # sets the cause on ex2 to be ex1 + raise ex2 + end + rescue + # sets the cause on ex1 to be ex2 + raise ex1 + end + end + def fall_back_to_cause # This exception is the cause and has a handler. ronanize @@ -139,4 +161,14 @@ class RescuableTest < ActiveSupport::TestCase @stargate.dispatch :fall_back_to_cause assert_equal "dex", @stargate.result end + + def test_unhandled_exceptions + @stargate.dispatch(:crash) + assert_equal "unhandled", @stargate.result + end + + def test_rescue_handles_loops_in_exception_cause_chain + @stargate.dispatch :looped_crash + assert_equal "unhandled", @stargate.result + end end diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb index af7fc44d66..40dfbe2542 100644 --- a/activesupport/test/test_case_test.rb +++ b/activesupport/test/test_case_test.rb @@ -237,9 +237,6 @@ class AssertDifferenceTest < ActiveSupport::TestCase end end -class AlsoDoingNothingTest < ActiveSupport::TestCase -end - # Setup and teardown callbacks. class SetupAndTeardownTest < ActiveSupport::TestCase setup :reset_callback_record, :foo diff --git a/activesupport/test/testing/file_fixtures_test.rb b/activesupport/test/testing/file_fixtures_test.rb index e44fe58ce9..9f28252c31 100644 --- a/activesupport/test/testing/file_fixtures_test.rb +++ b/activesupport/test/testing/file_fixtures_test.rb @@ -3,12 +3,12 @@ require "abstract_unit" require "pathname" class FileFixturesTest < ActiveSupport::TestCase - self.file_fixture_path = File.expand_path("../../file_fixtures", __FILE__) + self.file_fixture_path = File.expand_path("../file_fixtures", __dir__) test "#file_fixture returns Pathname to file fixture" do path = file_fixture("sample.txt") assert_kind_of Pathname, path - assert_match %r{.*/test/file_fixtures/sample.txt$}, path.to_s + assert_match %r{.*/test/file_fixtures/sample\.txt$}, path.to_s end test "raises an exception when the fixture file does not exist" do @@ -20,11 +20,11 @@ class FileFixturesTest < ActiveSupport::TestCase end class FileFixturesPathnameDirectoryTest < ActiveSupport::TestCase - self.file_fixture_path = Pathname.new(File.expand_path("../../file_fixtures", __FILE__)) + self.file_fixture_path = Pathname.new(File.expand_path("../file_fixtures", __dir__)) test "#file_fixture_path returns Pathname to file fixture" do path = file_fixture("sample.txt") assert_kind_of Pathname, path - assert_match %r{.*/test/file_fixtures/sample.txt$}, path.to_s + assert_match %r{.*/test/file_fixtures/sample\.txt$}, path.to_s end end diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb index e0d3fb0cf5..9d354f14f4 100644 --- a/activesupport/test/time_travel_test.rb +++ b/activesupport/test/time_travel_test.rb @@ -99,7 +99,7 @@ class TimeTravelTest < ActiveSupport::TestCase #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) + assert_match(/Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing\./, e.message) end end end diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 1615d8fdb2..de111cc40e 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -714,6 +714,10 @@ class TimeZoneTest < ActiveSupport::TestCase assert_not_includes ActiveSupport::TimeZone.country_zones(:ru), ActiveSupport::TimeZone["Kuala Lumpur"] end + def test_country_zones_without_mappings + assert_includes ActiveSupport::TimeZone.country_zones(:sv), ActiveSupport::TimeZone["America/El_Salvador"] + end + def test_to_yaml assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Pacific/Honolulu\n", ActiveSupport::TimeZone["Hawaii"].to_yaml) assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Europe/London\n", ActiveSupport::TimeZone["Europe/London"].to_yaml) diff --git a/activesupport/test/xml_mini/jdom_engine_test.rb b/activesupport/test/xml_mini/jdom_engine_test.rb index e783cea67c..fc35ac113b 100644 --- a/activesupport/test/xml_mini/jdom_engine_test.rb +++ b/activesupport/test/xml_mini/jdom_engine_test.rb @@ -2,7 +2,7 @@ require_relative "xml_mini_engine_test" XMLMiniEngineTest.run_with_platform("java") do class JDOMEngineTest < XMLMiniEngineTest - FILES_DIR = File.dirname(__FILE__) + "/../fixtures/xml" + FILES_DIR = File.expand_path("../fixtures/xml", __dir__) def test_not_allowed_to_expand_entities_to_files attack_xml = <<-EOT diff --git a/activesupport/test/xml_mini/xml_mini_engine_test.rb b/activesupport/test/xml_mini/xml_mini_engine_test.rb index 5be9084c9d..244e0b0d3a 100644 --- a/activesupport/test/xml_mini/xml_mini_engine_test.rb +++ b/activesupport/test/xml_mini/xml_mini_engine_test.rb @@ -75,6 +75,11 @@ class XMLMiniEngineTest < ActiveSupport::TestCase assert_equal({}, ActiveSupport::XmlMini.parse("")) end + def test_parse_from_frozen_string + xml_string = "<root/>".freeze + assert_equal({ "root" => {} }, ActiveSupport::XmlMini.parse(xml_string)) + end + def test_array_type_makes_an_array assert_equal_rexml(<<-eoxml) <blog> |