diff options
Diffstat (limited to 'spec/ruby/core/hash')
49 files changed, 1051 insertions, 591 deletions
diff --git a/spec/ruby/core/hash/any_spec.rb b/spec/ruby/core/hash/any_spec.rb index bd33e8cd8f..c26dfabde6 100644 --- a/spec/ruby/core/hash/any_spec.rb +++ b/spec/ruby/core/hash/any_spec.rb @@ -4,10 +4,10 @@ describe "Hash#any?" do describe 'with no block given' do it "checks if there are any members of a Hash" do empty_hash = {} - empty_hash.any?.should == false + empty_hash.should_not.any? hash_with_members = { 'key' => 'value' } - hash_with_members.any?.should == true + hash_with_members.should.any? end end diff --git a/spec/ruby/core/hash/assoc_spec.rb b/spec/ruby/core/hash/assoc_spec.rb index 64442918d1..62b2a11b30 100644 --- a/spec/ruby/core/hash/assoc_spec.rb +++ b/spec/ruby/core/hash/assoc_spec.rb @@ -22,11 +22,11 @@ describe "Hash#assoc" do end it "only returns the first matching key-value pair for identity hashes" do - # Avoid literal String keys in Hash#[]= due to https://bugs.ruby-lang.org/issues/12855 + # Avoid literal String keys since string literals can be frozen and interned e.g. with --enable-frozen-string-literal h = {}.compare_by_identity - k1 = 'pear' + k1 = 'pear'.dup h[k1] = :red - k2 = 'pear' + k2 = 'pear'.dup h[k2] = :green h.size.should == 2 h.keys.grep(/pear/).size.should == 2 diff --git a/spec/ruby/core/hash/clear_spec.rb b/spec/ruby/core/hash/clear_spec.rb index 706fe57e1c..cf05e36ac9 100644 --- a/spec/ruby/core/hash/clear_spec.rb +++ b/spec/ruby/core/hash/clear_spec.rb @@ -25,8 +25,8 @@ describe "Hash#clear" do h.default_proc.should_not == nil end - it "raises a #{frozen_error_class} if called on a frozen instance" do - lambda { HashSpecs.frozen_hash.clear }.should raise_error(frozen_error_class) - lambda { HashSpecs.empty_frozen_hash.clear }.should raise_error(frozen_error_class) + it "raises a FrozenError if called on a frozen instance" do + -> { HashSpecs.frozen_hash.clear }.should raise_error(FrozenError) + -> { HashSpecs.empty_frozen_hash.clear }.should raise_error(FrozenError) end end diff --git a/spec/ruby/core/hash/compact_spec.rb b/spec/ruby/core/hash/compact_spec.rb index ec9d4b5d79..13371bce43 100644 --- a/spec/ruby/core/hash/compact_spec.rb +++ b/spec/ruby/core/hash/compact_spec.rb @@ -18,6 +18,30 @@ describe "Hash#compact" do @hash.compact @hash.should == @initial_pairs end + + ruby_version_is '3.3' do + it "retains the default value" do + hash = Hash.new(1) + hash.compact.default.should == 1 + hash[:a] = 1 + hash.compact.default.should == 1 + end + + it "retains the default_proc" do + pr = proc { |h, k| h[k] = [] } + hash = Hash.new(&pr) + hash.compact.default_proc.should == pr + hash[:a] = 1 + hash.compact.default_proc.should == pr + end + + it "retains compare_by_identity flag" do + hash = {}.compare_by_identity + hash.compact.compare_by_identity?.should == true + hash[:a] = 1 + hash.compact.compare_by_identity?.should == true + end + end end describe "Hash#compact!" do @@ -51,8 +75,8 @@ describe "Hash#compact!" do @hash.freeze end - it "keeps pairs and raises a #{frozen_error_class}" do - ->{ @hash.compact! }.should raise_error(frozen_error_class) + it "keeps pairs and raises a FrozenError" do + ->{ @hash.compact! }.should raise_error(FrozenError) @hash.should == @initial_pairs end end diff --git a/spec/ruby/core/hash/compare_by_identity_spec.rb b/spec/ruby/core/hash/compare_by_identity_spec.rb index 33db59124e..2975526a97 100644 --- a/spec/ruby/core/hash/compare_by_identity_spec.rb +++ b/spec/ruby/core/hash/compare_by_identity_spec.rb @@ -33,7 +33,7 @@ describe "Hash#compare_by_identity" do it "has no effect on an already compare_by_identity hash" do @idh[:foo] = :bar @idh.compare_by_identity.should equal @idh - @idh.compare_by_identity?.should == true + @idh.should.compare_by_identity? @idh[:foo].should == :bar end @@ -80,24 +80,26 @@ describe "Hash#compare_by_identity" do @h[o].should == :o end - it "raises a #{frozen_error_class} on frozen hashes" do + it "raises a FrozenError on frozen hashes" do @h = @h.freeze - lambda { @h.compare_by_identity }.should raise_error(frozen_error_class) + -> { @h.compare_by_identity }.should raise_error(FrozenError) end - # Behaviour confirmed in bug #1871 + # Behaviour confirmed in https://bugs.ruby-lang.org/issues/1871 it "persists over #dups" do - @idh['foo'] = :bar - @idh['foo'] = :glark + @idh['foo'.dup] = :bar + @idh['foo'.dup] = :glark @idh.dup.should == @idh @idh.dup.size.should == @idh.size + @idh.dup.should.compare_by_identity? end it "persists over #clones" do - @idh['foo'] = :bar - @idh['foo'] = :glark + @idh['foo'.dup] = :bar + @idh['foo'.dup] = :glark @idh.clone.should == @idh @idh.clone.size.should == @idh.size + @idh.dup.should.compare_by_identity? end it "does not copy string keys" do @@ -108,9 +110,16 @@ describe "Hash#compare_by_identity" do @idh.keys.first.should equal foo end + # Check `#[]=` call with a String literal. + # Don't use `#+` because with `#+` it's no longer a String literal. + # + # See https://bugs.ruby-lang.org/issues/12855 it "gives different identity for string literals" do + eval <<~RUBY + # frozen_string_literal: false @idh['foo'] = 1 @idh['foo'] = 2 + RUBY @idh.values.should == [1, 2] @idh.size.should == 2 end diff --git a/spec/ruby/core/hash/constructor_spec.rb b/spec/ruby/core/hash/constructor_spec.rb index 14674a018b..0f97f7b40e 100644 --- a/spec/ruby/core/hash/constructor_spec.rb +++ b/spec/ruby/core/hash/constructor_spec.rb @@ -42,30 +42,17 @@ describe "Hash.[]" do Hash[ary].should == { a: :b } end - ruby_version_is "" ... "2.7" do - it "ignores elements that are not arrays" do - -> { - Hash[[:a]].should == {} - }.should complain(/ignoring wrong elements/) - -> { - Hash[[:nil]].should == {} - }.should complain(/ignoring wrong elements/) - end - end - - ruby_version_is "2.7" do - it "raises for elements that are not arrays" do - -> { - Hash[[:a]].should == {} - }.should raise_error(ArgumentError) - -> { - Hash[[:nil]].should == {} - }.should raise_error(ArgumentError) - end + it "raises for elements that are not arrays" do + -> { + Hash[[:a]].should == {} + }.should raise_error(ArgumentError) + -> { + Hash[[:nil]].should == {} + }.should raise_error(ArgumentError) end it "raises an ArgumentError for arrays of more than 2 elements" do - lambda{ Hash[[[:a, :b, :c]]].should == {} }.should raise_error(ArgumentError) + ->{ Hash[[[:a, :b, :c]]].should == {} }.should raise_error(ArgumentError) end it "raises an ArgumentError when passed a list of value-invalid-pairs in an array" do @@ -89,8 +76,8 @@ describe "Hash.[]" do end it "raises an ArgumentError when passed an odd number of arguments" do - lambda { Hash[1, 2, 3] }.should raise_error(ArgumentError) - lambda { Hash[1, 2, { 3 => 4 }] }.should raise_error(ArgumentError) + -> { Hash[1, 2, 3] }.should raise_error(ArgumentError) + -> { Hash[1, 2, { 3 => 4 }] }.should raise_error(ArgumentError) end it "calls to_hash" do @@ -116,8 +103,27 @@ describe "Hash.[]" do HashSpecs::MyInitializerHash[Hash[1, 2]].should be_an_instance_of(HashSpecs::MyInitializerHash) end - it "removes the default_proc" do + it "does not retain the default value" do + hash = Hash.new(1) + Hash[hash].default.should be_nil + hash[:a] = 1 + Hash[hash].default.should be_nil + end + + it "does not retain the default_proc" do hash = Hash.new { |h, k| h[k] = [] } Hash[hash].default_proc.should be_nil + hash[:a] = 1 + Hash[hash].default_proc.should be_nil + end + + ruby_version_is '3.3' do + it "does not retain compare_by_identity flag" do + hash = { a: 1 }.compare_by_identity + Hash[hash].compare_by_identity?.should == false + + hash = {}.compare_by_identity + Hash[hash].compare_by_identity?.should == false + end end end diff --git a/spec/ruby/core/hash/deconstruct_keys_spec.rb b/spec/ruby/core/hash/deconstruct_keys_spec.rb new file mode 100644 index 0000000000..bbcd8932e5 --- /dev/null +++ b/spec/ruby/core/hash/deconstruct_keys_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../spec_helper' + +describe "Hash#deconstruct_keys" do + it "returns self" do + hash = {a: 1, b: 2} + + hash.deconstruct_keys([:a, :b]).should equal hash + end + + it "requires one argument" do + -> { + {a: 1}.deconstruct_keys + }.should raise_error(ArgumentError, /wrong number of arguments \(given 0, expected 1\)/) + end + + it "ignores argument" do + hash = {a: 1, b: 2} + + hash.deconstruct_keys([:a]).should == {a: 1, b: 2} + hash.deconstruct_keys(0 ).should == {a: 1, b: 2} + hash.deconstruct_keys('' ).should == {a: 1, b: 2} + end +end diff --git a/spec/ruby/core/hash/default_proc_spec.rb b/spec/ruby/core/hash/default_proc_spec.rb index 2254609607..f4e4803632 100644 --- a/spec/ruby/core/hash/default_proc_spec.rb +++ b/spec/ruby/core/hash/default_proc_spec.rb @@ -37,7 +37,7 @@ describe "Hash#default_proc=" do end it "raises an error if passed stuff not convertible to procs" do - lambda{{}.default_proc = 42}.should raise_error(TypeError) + ->{{}.default_proc = 42}.should raise_error(TypeError) end it "returns the passed Proc" do @@ -58,23 +58,23 @@ describe "Hash#default_proc=" do it "accepts a lambda with an arity of 2" do h = {} - lambda do - h.default_proc = lambda {|a,b| } + -> do + h.default_proc = -> a, b { } end.should_not raise_error(TypeError) end it "raises a TypeError if passed a lambda with an arity other than 2" do h = {} - lambda do - h.default_proc = lambda {|a| } + -> do + h.default_proc = -> a { } end.should raise_error(TypeError) - lambda do - h.default_proc = lambda {|a,b,c| } + -> do + h.default_proc = -> a, b, c { } end.should raise_error(TypeError) end - it "raises a #{frozen_error_class} if self is frozen" do - lambda { {}.freeze.default_proc = Proc.new {} }.should raise_error(frozen_error_class) - lambda { {}.freeze.default_proc = nil }.should raise_error(frozen_error_class) + it "raises a FrozenError if self is frozen" do + -> { {}.freeze.default_proc = Proc.new {} }.should raise_error(FrozenError) + -> { {}.freeze.default_proc = nil }.should raise_error(FrozenError) end end diff --git a/spec/ruby/core/hash/default_spec.rb b/spec/ruby/core/hash/default_spec.rb index afc4f9780f..d8b62ea196 100644 --- a/spec/ruby/core/hash/default_spec.rb +++ b/spec/ruby/core/hash/default_spec.rb @@ -30,7 +30,7 @@ describe "Hash#default=" do end it "unsets the default proc" do - [99, nil, lambda { 6 }].each do |default| + [99, nil, -> { 6 }].each do |default| h = Hash.new { 5 } h.default_proc.should_not == nil h.default = default @@ -39,8 +39,8 @@ describe "Hash#default=" do end end - it "raises a #{frozen_error_class} if called on a frozen instance" do - lambda { HashSpecs.frozen_hash.default = nil }.should raise_error(frozen_error_class) - lambda { HashSpecs.empty_frozen_hash.default = nil }.should raise_error(frozen_error_class) + it "raises a FrozenError if called on a frozen instance" do + -> { HashSpecs.frozen_hash.default = nil }.should raise_error(FrozenError) + -> { HashSpecs.empty_frozen_hash.default = nil }.should raise_error(FrozenError) end end diff --git a/spec/ruby/core/hash/delete_if_spec.rb b/spec/ruby/core/hash/delete_if_spec.rb index 58fba1ff80..c9e670ffc3 100644 --- a/spec/ruby/core/hash/delete_if_spec.rb +++ b/spec/ruby/core/hash/delete_if_spec.rb @@ -34,9 +34,9 @@ describe "Hash#delete_if" do each_pairs.should == delete_pairs end - it "raises a #{frozen_error_class} if called on a frozen instance" do - lambda { HashSpecs.frozen_hash.delete_if { false } }.should raise_error(frozen_error_class) - lambda { HashSpecs.empty_frozen_hash.delete_if { true } }.should raise_error(frozen_error_class) + it "raises a FrozenError if called on a frozen instance" do + -> { HashSpecs.frozen_hash.delete_if { false } }.should raise_error(FrozenError) + -> { HashSpecs.empty_frozen_hash.delete_if { true } }.should raise_error(FrozenError) end it_behaves_like :hash_iteration_no_block, :delete_if diff --git a/spec/ruby/core/hash/delete_spec.rb b/spec/ruby/core/hash/delete_spec.rb index 6f0079dafa..3e3479c69c 100644 --- a/spec/ruby/core/hash/delete_spec.rb +++ b/spec/ruby/core/hash/delete_spec.rb @@ -24,7 +24,7 @@ describe "Hash#delete" do it "allows removing a key while iterating" do h = { a: 1, b: 2 } visited = [] - h.each_pair { |k,v| + h.each_pair { |k, v| visited << k h.delete(k) } @@ -32,13 +32,27 @@ describe "Hash#delete" do h.should == {} end + it "allows removing a key while iterating for big hashes" do + h = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10, + k: 11, l: 12, m: 13, n: 14, o: 15, p: 16, q: 17, r: 18, s: 19, t: 20, + u: 21, v: 22, w: 23, x: 24, y: 25, z: 26 } + visited = [] + h.each_pair { |k, v| + visited << k + h.delete(k) + } + visited.should == [:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, + :n, :o, :p, :q, :r, :s, :t, :u, :v, :w, :x, :y, :z] + h.should == {} + end + it "accepts keys with private #hash method" do key = HashSpecs::KeyWithPrivateHash.new { key => 5 }.delete(key).should == 5 end - it "raises a #{frozen_error_class} if called on a frozen instance" do - lambda { HashSpecs.frozen_hash.delete("foo") }.should raise_error(frozen_error_class) - lambda { HashSpecs.empty_frozen_hash.delete("foo") }.should raise_error(frozen_error_class) + it "raises a FrozenError if called on a frozen instance" do + -> { HashSpecs.frozen_hash.delete("foo") }.should raise_error(FrozenError) + -> { HashSpecs.empty_frozen_hash.delete("foo") }.should raise_error(FrozenError) end end diff --git a/spec/ruby/core/hash/dig_spec.rb b/spec/ruby/core/hash/dig_spec.rb index dcba049ac4..aa0ecadd2f 100644 --- a/spec/ruby/core/hash/dig_spec.rb +++ b/spec/ruby/core/hash/dig_spec.rb @@ -28,7 +28,7 @@ describe "Hash#dig" do end it "raises an ArgumentError if no arguments provided" do - lambda { { the: 'borg' }.dig() }.should raise_error(ArgumentError) + -> { { the: 'borg' }.dig() }.should raise_error(ArgumentError) end it "handles type-mixed deep digging" do @@ -47,8 +47,8 @@ describe "Hash#dig" do it "raises TypeError if an intermediate element does not respond to #dig" do h = {} h[:foo] = [ { bar: [ 1 ] }, [ nil, 'str' ] ] - lambda { h.dig(:foo, 0, :bar, 0, 0) }.should raise_error(TypeError) - lambda { h.dig(:foo, 1, 1, 0) }.should raise_error(TypeError) + -> { h.dig(:foo, 0, :bar, 0, 0) }.should raise_error(TypeError) + -> { h.dig(:foo, 1, 1, 0) }.should raise_error(TypeError) end it "calls #dig on the result of #[] with the remaining arguments" do diff --git a/spec/ruby/core/hash/element_reference_spec.rb b/spec/ruby/core/hash/element_reference_spec.rb index 2eb65d3789..d5859cb342 100644 --- a/spec/ruby/core/hash/element_reference_spec.rb +++ b/spec/ruby/core/hash/element_reference_spec.rb @@ -12,7 +12,7 @@ describe "Hash#[]" do h[[]].should == "baz" end - it "returns nil as default default value" do + it "returns nil as default value" do { 0 => 0 }[5].should == nil end @@ -30,7 +30,7 @@ describe "Hash#[]" do end it "does not create copies of the immediate default value" do - str = "foo" + str = +"foo" h = Hash.new(str) a = h[:a] b = h[:b] @@ -117,4 +117,18 @@ describe "Hash#[]" do key = HashSpecs::KeyWithPrivateHash.new { key => 42 }[key].should == 42 end + + it "does not dispatch to hash for Boolean, Integer, Float, String, or Symbol" do + code = <<-EOC + load '#{fixture __FILE__, "name.rb"}' + hash = { true => 42, false => 42, 1 => 42, 2.0 => 42, "hello" => 42, :ok => 42 } + [true, false, 1, 2.0, "hello", :ok].each do |value| + raise "incorrect value" unless hash[value] == 42 + end + puts "Ok." + EOC + result = ruby_exe(code, args: "2>&1") + result.should == "Ok.\n" + end + end diff --git a/spec/ruby/core/hash/empty_spec.rb b/spec/ruby/core/hash/empty_spec.rb index e9be44bab0..881e1cc34b 100644 --- a/spec/ruby/core/hash/empty_spec.rb +++ b/spec/ruby/core/hash/empty_spec.rb @@ -3,13 +3,13 @@ require_relative 'fixtures/classes' describe "Hash#empty?" do it "returns true if the hash has no entries" do - {}.empty?.should == true - { 1 => 1 }.empty?.should == false + {}.should.empty? + { 1 => 1 }.should_not.empty? end it "returns true if the hash has no entries and has a default value" do - Hash.new(5).empty?.should == true - Hash.new { 5 }.empty?.should == true - Hash.new { |hsh, k| hsh[k] = k }.empty?.should == true + Hash.new(5).should.empty? + Hash.new { 5 }.should.empty? + Hash.new { |hsh, k| hsh[k] = k }.should.empty? end end diff --git a/spec/ruby/core/hash/except_spec.rb b/spec/ruby/core/hash/except_spec.rb new file mode 100644 index 0000000000..026e454b13 --- /dev/null +++ b/spec/ruby/core/hash/except_spec.rb @@ -0,0 +1,42 @@ +require_relative '../../spec_helper' + +describe "Hash#except" do + before :each do + @hash = { a: 1, b: 2, c: 3 } + end + + it "returns a new duplicate hash without arguments" do + ret = @hash.except + ret.should_not equal(@hash) + ret.should == @hash + end + + it "returns a hash without the requested subset" do + @hash.except(:c, :a).should == { b: 2 } + end + + it "ignores keys not present in the original hash" do + @hash.except(:a, :chunky_bacon).should == { b: 2, c: 3 } + end + + it "does not retain the default value" do + h = Hash.new(1) + h.except(:a).default.should be_nil + h[:a] = 1 + h.except(:a).default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.except(:a).default_proc.should be_nil + h[:a] = 1 + h.except(:a).default_proc.should be_nil + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.except(:a) + h2.compare_by_identity?.should == true + end +end diff --git a/spec/ruby/core/hash/fetch_spec.rb b/spec/ruby/core/hash/fetch_spec.rb index 197832e311..6e0d207224 100644 --- a/spec/ruby/core/hash/fetch_spec.rb +++ b/spec/ruby/core/hash/fetch_spec.rb @@ -4,10 +4,10 @@ require_relative '../../shared/hash/key_error' describe "Hash#fetch" do context "when the key is not found" do - it_behaves_like :key_error, ->(obj, key) { obj.fetch(key) }, Hash.new(a: 5) - it_behaves_like :key_error, ->(obj, key) { obj.fetch(key) }, {} - it_behaves_like :key_error, ->(obj, key) { obj.fetch(key) }, Hash.new { 5 } - it_behaves_like :key_error, ->(obj, key) { obj.fetch(key) }, Hash.new(5) + it_behaves_like :key_error, -> obj, key { obj.fetch(key) }, Hash.new({ a: 5 }) + it_behaves_like :key_error, -> obj, key { obj.fetch(key) }, {} + it_behaves_like :key_error, -> obj, key { obj.fetch(key) }, Hash.new { 5 } + it_behaves_like :key_error, -> obj, key { obj.fetch(key) }, Hash.new(5) it "formats the object with #inspect in the KeyError message" do -> { @@ -31,14 +31,14 @@ describe "Hash#fetch" do end it "gives precedence to the default block over the default argument when passed both" do - lambda { + -> { @result = {}.fetch(9, :foo) { |i| i * i } }.should complain(/block supersedes default value argument/) @result.should == 81 end it "raises an ArgumentError when not passed one or two arguments" do - lambda { {}.fetch() }.should raise_error(ArgumentError) - lambda { {}.fetch(1, 2, 3) }.should raise_error(ArgumentError) + -> { {}.fetch() }.should raise_error(ArgumentError) + -> { {}.fetch(1, 2, 3) }.should raise_error(ArgumentError) end end diff --git a/spec/ruby/core/hash/fetch_values_spec.rb b/spec/ruby/core/hash/fetch_values_spec.rb index a4af153bf7..0cd48565af 100644 --- a/spec/ruby/core/hash/fetch_values_spec.rb +++ b/spec/ruby/core/hash/fetch_values_spec.rb @@ -12,10 +12,14 @@ describe "Hash#fetch_values" do @hash.fetch_values(:a).should == [1] @hash.fetch_values(:a, :c).should == [1, 3] end + + it "returns the values for keys ordered in the order of the requested keys" do + @hash.fetch_values(:c, :a).should == [3, 1] + end end describe "with unmatched keys" do - it_behaves_like :key_error, ->(obj, key) { obj.fetch_values(key) }, Hash.new(a: 5) + it_behaves_like :key_error, -> obj, key { obj.fetch_values(key) }, Hash.new({ a: 5 }) it "returns the default value from block" do @hash.fetch_values(:z) { |key| "`#{key}' is not found" }.should == ["`z' is not found"] diff --git a/spec/ruby/core/hash/filter_spec.rb b/spec/ruby/core/hash/filter_spec.rb index 4382c94e62..7dabe44984 100644 --- a/spec/ruby/core/hash/filter_spec.rb +++ b/spec/ruby/core/hash/filter_spec.rb @@ -1,12 +1,10 @@ require_relative '../../spec_helper' require_relative 'shared/select' -ruby_version_is "2.6" do - describe "Hash#filter" do - it_behaves_like :hash_select, :filter - end +describe "Hash#filter" do + it_behaves_like :hash_select, :filter +end - describe "Hash#filter!" do - it_behaves_like :hash_select!, :filter! - end +describe "Hash#filter!" do + it_behaves_like :hash_select!, :filter! end diff --git a/spec/ruby/core/hash/fixtures/name.rb b/spec/ruby/core/hash/fixtures/name.rb new file mode 100644 index 0000000000..b203bf6ae4 --- /dev/null +++ b/spec/ruby/core/hash/fixtures/name.rb @@ -0,0 +1,30 @@ +class TrueClass + def hash + raise "TrueClass#hash should not be called" + end +end +class FalseClass + def hash + raise "FalseClass#hash should not be called" + end +end +class Integer + def hash + raise "Integer#hash should not be called" + end +end +class Float + def hash + raise "Float#hash should not be called" + end +end +class String + def hash + raise "String#hash should not be called" + end +end +class Symbol + def hash + raise "Symbol#hash should not be called" + end +end diff --git a/spec/ruby/core/hash/flatten_spec.rb b/spec/ruby/core/hash/flatten_spec.rb index 5a7ddd8baa..825da15bfc 100644 --- a/spec/ruby/core/hash/flatten_spec.rb +++ b/spec/ruby/core/hash/flatten_spec.rb @@ -55,7 +55,7 @@ describe "Hash#flatten" do end it "raises a TypeError if given a non-Integer argument" do - lambda do + -> do @h.flatten(Object.new) end.should raise_error(TypeError) end diff --git a/spec/ruby/core/hash/hash_spec.rb b/spec/ruby/core/hash/hash_spec.rb index 3649d4d8de..cd67f7a652 100644 --- a/spec/ruby/core/hash/hash_spec.rb +++ b/spec/ruby/core/hash/hash_spec.rb @@ -41,4 +41,11 @@ describe "Hash#hash" do h.hash.should == {x: [h]}.hash # Like above, because h.eql?(x: [h]) end + + it "allows omitting values" do + a = 1 + b = 2 + + {a:, b:}.should == { a: 1, b: 2 } + end end diff --git a/spec/ruby/core/hash/index_spec.rb b/spec/ruby/core/hash/index_spec.rb deleted file mode 100644 index 2b52c69949..0000000000 --- a/spec/ruby/core/hash/index_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/index' - -describe "Hash#index" do - it_behaves_like :hash_index, :index -end diff --git a/spec/ruby/core/hash/initialize_spec.rb b/spec/ruby/core/hash/initialize_spec.rb index 344571631a..d13496ba3b 100644 --- a/spec/ruby/core/hash/initialize_spec.rb +++ b/spec/ruby/core/hash/initialize_spec.rb @@ -45,17 +45,17 @@ describe "Hash#initialize" do h.send(:initialize).should equal(h) end - it "raises a #{frozen_error_class} if called on a frozen instance" do - block = lambda { HashSpecs.frozen_hash.instance_eval { initialize() }} - block.should raise_error(frozen_error_class) + it "raises a FrozenError if called on a frozen instance" do + block = -> { HashSpecs.frozen_hash.instance_eval { initialize() }} + block.should raise_error(FrozenError) - block = lambda { HashSpecs.frozen_hash.instance_eval { initialize(nil) } } - block.should raise_error(frozen_error_class) + block = -> { HashSpecs.frozen_hash.instance_eval { initialize(nil) } } + block.should raise_error(FrozenError) - block = lambda { HashSpecs.frozen_hash.instance_eval { initialize(5) } } - block.should raise_error(frozen_error_class) + block = -> { HashSpecs.frozen_hash.instance_eval { initialize(5) } } + block.should raise_error(FrozenError) - block = lambda { HashSpecs.frozen_hash.instance_eval { initialize { 5 } } } - block.should raise_error(frozen_error_class) + block = -> { HashSpecs.frozen_hash.instance_eval { initialize { 5 } } } + block.should raise_error(FrozenError) end end diff --git a/spec/ruby/core/hash/invert_spec.rb b/spec/ruby/core/hash/invert_spec.rb index 73377a9e97..c06e15ff7c 100644 --- a/spec/ruby/core/hash/invert_spec.rb +++ b/spec/ruby/core/hash/invert_spec.rb @@ -24,4 +24,25 @@ describe "Hash#invert" do HashSpecs::MyHash[1 => 2, 3 => 4].invert.class.should == Hash HashSpecs::MyHash[].invert.class.should == Hash end + + it "does not retain the default value" do + h = Hash.new(1) + h.invert.default.should be_nil + h[:a] = 1 + h.invert.default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.invert.default_proc.should be_nil + h[:a] = 1 + h.invert.default_proc.should be_nil + end + + it "does not retain compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.invert + h2.compare_by_identity?.should == false + end end diff --git a/spec/ruby/core/hash/keep_if_spec.rb b/spec/ruby/core/hash/keep_if_spec.rb index 80f7fbbf64..d50d969467 100644 --- a/spec/ruby/core/hash/keep_if_spec.rb +++ b/spec/ruby/core/hash/keep_if_spec.rb @@ -27,9 +27,9 @@ describe "Hash#keep_if" do h.keep_if { true }.should equal(h) end - it "raises a #{frozen_error_class} if called on a frozen instance" do - lambda { HashSpecs.frozen_hash.keep_if { true } }.should raise_error(frozen_error_class) - lambda { HashSpecs.empty_frozen_hash.keep_if { false } }.should raise_error(frozen_error_class) + it "raises a FrozenError if called on a frozen instance" do + -> { HashSpecs.frozen_hash.keep_if { true } }.should raise_error(FrozenError) + -> { HashSpecs.empty_frozen_hash.keep_if { false } }.should raise_error(FrozenError) end it_behaves_like :hash_iteration_no_block, :keep_if diff --git a/spec/ruby/core/hash/merge_spec.rb b/spec/ruby/core/hash/merge_spec.rb index 77e5c42071..6710d121ef 100644 --- a/spec/ruby/core/hash/merge_spec.rb +++ b/spec/ruby/core/hash/merge_spec.rb @@ -28,7 +28,7 @@ describe "Hash#merge" do r = h1.merge(h2) { |k,x,y| "#{k}:#{x+2*y}" } r.should == { a: "a:-2", b: "b:9", c: -3, d: 5 } - lambda { + -> { h1.merge(h2) { |k, x, y| raise(IndexError) } }.should raise_error(IndexError) @@ -81,19 +81,40 @@ describe "Hash#merge" do merge_pairs.should == expected_pairs end - ruby_version_is "2.6" do - it "accepts multiple hashes" do - result = { a: 1 }.merge({ b: 2 }, { c: 3 }, { d: 4 }) - result.should == { a: 1, b: 2, c: 3, d: 4 } - end + it "accepts multiple hashes" do + result = { a: 1 }.merge({ b: 2 }, { c: 3 }, { d: 4 }) + result.should == { a: 1, b: 2, c: 3, d: 4 } + end + + it "accepts zero arguments and returns a copy of self" do + hash = { a: 1 } + merged = hash.merge - it "accepts zero arguments and returns a copy of self" do - hash = { a: 1 } - merged = hash.merge + merged.should eql(hash) + merged.should_not equal(hash) + end + + it "retains the default value" do + h = Hash.new(1) + h.merge(b: 1, d: 2).default.should == 1 + end + + it "retains the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.merge(b: 1, d: 2).default_proc.should == pr + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.merge(b: 1, d: 2) + h2.compare_by_identity?.should == true + end - merged.should eql(hash) - merged.should_not equal(hash) - end + it "ignores compare_by_identity flag of an argument" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = { b: 1, d: 2 }.merge(h) + h2.compare_by_identity?.should == false end end diff --git a/spec/ruby/core/hash/new_spec.rb b/spec/ruby/core/hash/new_spec.rb index c21266777f..5ae3e1f98d 100644 --- a/spec/ruby/core/hash/new_spec.rb +++ b/spec/ruby/core/hash/new_spec.rb @@ -26,11 +26,42 @@ describe "Hash.new" do end it "raises an ArgumentError if more than one argument is passed" do - lambda { Hash.new(5,6) }.should raise_error(ArgumentError) + -> { Hash.new(5,6) }.should raise_error(ArgumentError) end it "raises an ArgumentError if passed both default argument and default block" do - lambda { Hash.new(5) { 0 } }.should raise_error(ArgumentError) - lambda { Hash.new(nil) { 0 } }.should raise_error(ArgumentError) + -> { Hash.new(5) { 0 } }.should raise_error(ArgumentError) + -> { Hash.new(nil) { 0 } }.should raise_error(ArgumentError) + end + + ruby_version_is "3.3"..."3.4" do + it "emits a deprecation warning if keyword arguments are passed" do + -> { Hash.new(unknown: true) }.should complain( + Regexp.new(Regexp.escape("Calling Hash.new with keyword arguments is deprecated and will be removed in Ruby 3.4; use Hash.new({ key: value }) instead")) + ) + + -> { Hash.new(1, unknown: true) }.should raise_error(ArgumentError) + -> { Hash.new(unknown: true) { 0 } }.should raise_error(ArgumentError) + + Hash.new({ unknown: true }).default.should == { unknown: true } + end + end + + ruby_version_is "3.4" do + it "accepts a capacity: argument" do + Hash.new(5, capacity: 42).default.should == 5 + Hash.new(capacity: 42).default.should == nil + (Hash.new(capacity: 42) { 1 }).default_proc.should_not == nil + end + + it "ignores negative capacity" do + -> { Hash.new(capacity: -42) }.should_not raise_error + end + + it "raises an error if unknown keyword arguments are passed" do + -> { Hash.new(unknown: true) }.should raise_error(ArgumentError) + -> { Hash.new(1, unknown: true) }.should raise_error(ArgumentError) + -> { Hash.new(unknown: true) { 0 } }.should raise_error(ArgumentError) + end end end diff --git a/spec/ruby/core/hash/rehash_spec.rb b/spec/ruby/core/hash/rehash_spec.rb index 32ba3fcfb9..db3e91b166 100644 --- a/spec/ruby/core/hash/rehash_spec.rb +++ b/spec/ruby/core/hash/rehash_spec.rb @@ -2,21 +2,30 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' describe "Hash#rehash" do - it "reorganizes the hash by recomputing all key hash codes" do - k1 = [1] - k2 = [2] + it "reorganizes the Hash by recomputing all key hash codes" do + k1 = Object.new + k2 = Object.new + def k1.hash; 0; end + def k2.hash; 1; end + h = {} - h[k1] = 0 - h[k2] = 1 + h[k1] = :v1 + h[k2] = :v2 + + def k1.hash; 1; end - k1 << 2 + # The key should no longer be found as the #hash changed. + # Hash values 0 and 1 should not conflict, even with 1-bit stored hash. h.key?(k1).should == false + h.keys.include?(k1).should == true h.rehash.should equal(h) h.key?(k1).should == true - h[k1].should == 0 + h[k1].should == :v1 + end + it "calls #hash for each key" do k1 = mock('k1') k2 = mock('k2') v1 = mock('v1') @@ -50,8 +59,56 @@ describe "Hash#rehash" do h.keys.should == [a] end - it "raises a #{frozen_error_class} if called on a frozen instance" do - lambda { HashSpecs.frozen_hash.rehash }.should raise_error(frozen_error_class) - lambda { HashSpecs.empty_frozen_hash.rehash }.should raise_error(frozen_error_class) + it "removes duplicate keys for large hashes" do + a = [1,2] + b = [1] + + h = {} + h[a] = true + h[b] = true + 100.times { |n| h[n] = true } + b << 2 + h.size.should == 102 + h.keys.should.include? a + h.keys.should.include? b + h.rehash + h.size.should == 101 + h.keys.should.include? a + h.keys.should_not.include? [1] + end + + it "iterates keys in insertion order" do + key = Class.new do + attr_reader :name + + def initialize(name) + @name = name + end + + def hash + 123 + end + end + + a, b, c, d = key.new('a'), key.new('b'), key.new('c'), key.new('d') + h = { a => 1, b => 2, c => 3, d => 4 } + h.size.should == 4 + + key.class_exec do + def eql?(other) + true + end + end + + h.rehash + h.size.should == 1 + k, v = h.first + k.name.should == 'a' + v.should == 4 + end + + it "raises a FrozenError if called on a frozen instance" do + -> { HashSpecs.frozen_hash.rehash }.should raise_error(FrozenError) + -> { HashSpecs.empty_frozen_hash.rehash }.should raise_error(FrozenError) end end diff --git a/spec/ruby/core/hash/reject_spec.rb b/spec/ruby/core/hash/reject_spec.rb index a11e80a34a..8381fc7fc1 100644 --- a/spec/ruby/core/hash/reject_spec.rb +++ b/spec/ruby/core/hash/reject_spec.rb @@ -31,11 +31,6 @@ describe "Hash#reject" do HashSpecs::MyHash[1 => 2, 3 => 4].reject { false }.should be_kind_of(Hash) HashSpecs::MyHash[1 => 2, 3 => 4].reject { true }.should be_kind_of(Hash) end - - it "does not taint the resulting hash" do - h = { a: 1 }.taint - h.reject {false}.tainted?.should == false - end end it "processes entries with the same order as reject!" do @@ -49,6 +44,27 @@ describe "Hash#reject" do reject_pairs.should == reject_bang_pairs end + it "does not retain the default value" do + h = Hash.new(1) + h.reject { false }.default.should be_nil + h[:a] = 1 + h.reject { false }.default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.reject { false }.default_proc.should be_nil + h[:a] = 1 + h.reject { false }.default_proc.should be_nil + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.reject { |k, _| k == :a } + h2.compare_by_identity?.should == true + end + it_behaves_like :hash_iteration_no_block, :reject it_behaves_like :enumeratorized_with_origin_size, :reject, { 1 => 2, 3 => 4, 5 => 6 } end @@ -87,12 +103,12 @@ describe "Hash#reject!" do reject_bang_pairs.should == delete_if_pairs end - it "raises a #{frozen_error_class} if called on a frozen instance that is modified" do - lambda { HashSpecs.empty_frozen_hash.reject! { true } }.should raise_error(frozen_error_class) + it "raises a FrozenError if called on a frozen instance that is modified" do + -> { HashSpecs.empty_frozen_hash.reject! { true } }.should raise_error(FrozenError) end - it "raises a #{frozen_error_class} if called on a frozen instance that would not be modified" do - lambda { HashSpecs.frozen_hash.reject! { false } }.should raise_error(frozen_error_class) + it "raises a FrozenError if called on a frozen instance that would not be modified" do + -> { HashSpecs.frozen_hash.reject! { false } }.should raise_error(FrozenError) end it_behaves_like :hash_iteration_no_block, :reject! diff --git a/spec/ruby/core/hash/replace_spec.rb b/spec/ruby/core/hash/replace_spec.rb index 92b2118fd3..db30145e1a 100644 --- a/spec/ruby/core/hash/replace_spec.rb +++ b/spec/ruby/core/hash/replace_spec.rb @@ -1,7 +1,79 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -require_relative 'shared/replace' describe "Hash#replace" do - it_behaves_like :hash_replace, :replace + it "replaces the contents of self with other" do + h = { a: 1, b: 2 } + h.replace(c: -1, d: -2).should equal(h) + h.should == { c: -1, d: -2 } + end + + it "tries to convert the passed argument to a hash using #to_hash" do + obj = mock('{1=>2,3=>4}') + obj.should_receive(:to_hash).and_return({ 1 => 2, 3 => 4 }) + + h = {} + h.replace(obj) + h.should == { 1 => 2, 3 => 4 } + end + + it "calls to_hash on hash subclasses" do + h = {} + h.replace(HashSpecs::ToHashHash[1 => 2]) + h.should == { 1 => 2 } + end + + it "does not retain the default value" do + hash = Hash.new(1) + hash.replace(b: 2).default.should be_nil + end + + it "transfers the default value of an argument" do + hash = Hash.new(1) + { a: 1 }.replace(hash).default.should == 1 + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + hash = Hash.new(&pr) + hash.replace(b: 2).default_proc.should be_nil + end + + it "transfers the default_proc of an argument" do + pr = proc { |h, k| h[k] = [] } + hash = Hash.new(&pr) + { a: 1 }.replace(hash).default_proc.should == pr + end + + it "does not call the default_proc of an argument" do + hash_a = Hash.new { |h, k| k * 5 } + hash_b = Hash.new(-> { raise "Should not invoke lambda" }) + hash_a.replace(hash_b) + hash_a.default.should == hash_b.default + end + + it "transfers compare_by_identity flag of an argument" do + h = { a: 1, c: 3 } + h2 = { b: 2, d: 4 }.compare_by_identity + h.replace(h2) + h.compare_by_identity?.should == true + end + + it "does not retain compare_by_identity flag" do + h = { a: 1, c: 3 }.compare_by_identity + h.replace(b: 2, d: 4) + h.compare_by_identity?.should == false + end + + it "raises a FrozenError if called on a frozen instance that would not be modified" do + -> do + HashSpecs.frozen_hash.replace(HashSpecs.frozen_hash) + end.should raise_error(FrozenError) + end + + it "raises a FrozenError if called on a frozen instance that is modified" do + -> do + HashSpecs.frozen_hash.replace(HashSpecs.empty_frozen_hash) + end.should raise_error(FrozenError) + end end diff --git a/spec/ruby/core/hash/ruby2_keywords_hash_spec.rb b/spec/ruby/core/hash/ruby2_keywords_hash_spec.rb new file mode 100644 index 0000000000..7dbb9c0a98 --- /dev/null +++ b/spec/ruby/core/hash/ruby2_keywords_hash_spec.rb @@ -0,0 +1,83 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Hash.ruby2_keywords_hash?" do + it "returns false if the Hash is not a keywords Hash" do + Hash.ruby2_keywords_hash?({}).should == false + end + + it "returns true if the Hash is a keywords Hash marked by Module#ruby2_keywords" do + obj = Class.new { + ruby2_keywords def m(*args) + args.last + end + }.new + Hash.ruby2_keywords_hash?(obj.m(a: 1)).should == true + end + + it "raises TypeError for non-Hash" do + -> { Hash.ruby2_keywords_hash?(nil) }.should raise_error(TypeError) + end +end + +describe "Hash.ruby2_keywords_hash" do + it "returns a copy of a Hash and marks the copy as a keywords Hash" do + h = {a: 1}.freeze + kw = Hash.ruby2_keywords_hash(h) + Hash.ruby2_keywords_hash?(h).should == false + Hash.ruby2_keywords_hash?(kw).should == true + kw.should == h + end + + it "returns an instance of the subclass if called on an instance of a subclass of Hash" do + h = HashSpecs::MyHash.new + h[:a] = 1 + kw = Hash.ruby2_keywords_hash(h) + kw.class.should == HashSpecs::MyHash + Hash.ruby2_keywords_hash?(h).should == false + Hash.ruby2_keywords_hash?(kw).should == true + kw.should == h + end + + it "copies instance variables" do + h = {a: 1} + h.instance_variable_set(:@foo, 42) + kw = Hash.ruby2_keywords_hash(h) + kw.instance_variable_get(:@foo).should == 42 + end + + it "copies the hash internals" do + h = {a: 1} + kw = Hash.ruby2_keywords_hash(h) + h[:a] = 2 + kw[:a].should == 1 + end + + it "raises TypeError for non-Hash" do + -> { Hash.ruby2_keywords_hash(nil) }.should raise_error(TypeError) + end + + it "retains the default value" do + hash = Hash.new(1) + Hash.ruby2_keywords_hash(hash).default.should == 1 + hash[:a] = 1 + Hash.ruby2_keywords_hash(hash).default.should == 1 + end + + it "retains the default_proc" do + pr = proc { |h, k| h[k] = [] } + hash = Hash.new(&pr) + Hash.ruby2_keywords_hash(hash).default_proc.should == pr + hash[:a] = 1 + Hash.ruby2_keywords_hash(hash).default_proc.should == pr + end + + ruby_version_is '3.3' do + it "retains compare_by_identity_flag" do + hash = {}.compare_by_identity + Hash.ruby2_keywords_hash(hash).compare_by_identity?.should == true + hash[:a] = 1 + Hash.ruby2_keywords_hash(hash).compare_by_identity?.should == true + end + end +end diff --git a/spec/ruby/core/hash/shared/comparison.rb b/spec/ruby/core/hash/shared/comparison.rb index bbb9bfd6ad..07564e4cec 100644 --- a/spec/ruby/core/hash/shared/comparison.rb +++ b/spec/ruby/core/hash/shared/comparison.rb @@ -1,8 +1,8 @@ describe :hash_comparison, shared: true do it "raises a TypeError if the right operand is not a hash" do - lambda { { a: 1 }.send(@method, 1) }.should raise_error(TypeError) - lambda { { a: 1 }.send(@method, nil) }.should raise_error(TypeError) - lambda { { a: 1 }.send(@method, []) }.should raise_error(TypeError) + -> { { a: 1 }.send(@method, 1) }.should raise_error(TypeError) + -> { { a: 1 }.send(@method, nil) }.should raise_error(TypeError) + -> { { a: 1 }.send(@method, []) }.should raise_error(TypeError) end it "returns false if both hashes have the same keys but different values" do diff --git a/spec/ruby/core/hash/shared/each.rb b/spec/ruby/core/hash/shared/each.rb index d1f2e5f672..f9839ff58f 100644 --- a/spec/ruby/core/hash/shared/each.rb +++ b/spec/ruby/core/hash/shared/each.rb @@ -21,19 +21,39 @@ describe :hash_each, shared: true do ary.sort.should == ["a", "b", "c"] end - it "yields 2 values and not an Array of 2 elements when given a callable of arity 2" do + it "always yields an Array of 2 elements, even when given a callable of arity 2" do obj = Object.new def obj.foo(key, value) - ScratchPad << key << value + end + + -> { + { "a" => 1 }.send(@method, &obj.method(:foo)) + }.should raise_error(ArgumentError) + + -> { + { "a" => 1 }.send(@method, &-> key, value { }) + }.should raise_error(ArgumentError) + end + + it "yields an Array of 2 elements when given a callable of arity 1" do + obj = Object.new + def obj.foo(key_value) + ScratchPad << key_value end ScratchPad.record([]) { "a" => 1 }.send(@method, &obj.method(:foo)) - ScratchPad.recorded.should == ["a", 1] + ScratchPad.recorded.should == [["a", 1]] + end - ScratchPad.record([]) - { "a" => 1 }.send(@method, &-> key, value { ScratchPad << key << value }) - ScratchPad.recorded.should == ["a", 1] + it "raises an error for a Hash when an arity enforcing callable of arity >2 is passed in" do + obj = Object.new + def obj.foo(key, value, extra) + end + + -> { + { "a" => 1 }.send(@method, &obj.method(:foo)) + }.should raise_error(ArgumentError) end it "uses the same order as keys() and values()" do diff --git a/spec/ruby/core/hash/shared/eql.rb b/spec/ruby/core/hash/shared/eql.rb index 1aed5f51fb..68db49f76d 100644 --- a/spec/ruby/core/hash/shared/eql.rb +++ b/spec/ruby/core/hash/shared/eql.rb @@ -118,7 +118,7 @@ describe :hash_eql_additional, shared: true do { 1.0 => "x" }.send(@method, { 1 => "x" }).should be_false end - it "returns true iff other Hash has the same number of keys and each key-value pair matches" do + it "returns true if and only if other Hash has the same number of keys and each key-value pair matches" do a = { a: 5 } b = {} a.send(@method, b).should be_false @@ -155,12 +155,8 @@ describe :hash_eql_additional, shared: true do obj = mock('0') obj.should_receive(:hash).at_least(1).and_return(0) - # It's undefined whether the impl does a[0].eql?(a[1]) or - # a[1].eql?(a[0]) so we taint both. def obj.eql?(o) return true if self.equal?(o) - taint - o.taint false end @@ -168,18 +164,12 @@ describe :hash_eql_additional, shared: true do end { a[0] => 1 }.send(@method, { a[1] => 1 }).should be_false - a[0].tainted?.should be_true - a[1].tainted?.should be_true a = Array.new(2) do obj = mock('0') obj.should_receive(:hash).at_least(1).and_return(0) def obj.eql?(o) - # It's undefined whether the impl does a[0].send(@method, a[1]) or - # a[1].send(@method, a[0]) so we taint both. - taint - o.taint true end @@ -187,8 +177,6 @@ describe :hash_eql_additional, shared: true do end { a[0] => 1 }.send(@method, { a[1] => 1 }).should be_true - a[0].tainted?.should be_true - a[1].tainted?.should be_true end it "compares the values in self to values in other hash" do diff --git a/spec/ruby/core/hash/shared/equal.rb b/spec/ruby/core/hash/shared/equal.rb deleted file mode 100644 index 43606437fe..0000000000 --- a/spec/ruby/core/hash/shared/equal.rb +++ /dev/null @@ -1,90 +0,0 @@ -describe :hash_equal, shared: true do - it "does not compare values when keys don't match" do - value = mock('x') - value.should_not_receive(:==) - value.should_not_receive(:eql?) - { 1 => value }.send(@method, { 2 => value }).should be_false - end - - it "returns false when the numbers of keys differ without comparing any elements" do - obj = mock('x') - h = { obj => obj } - - obj.should_not_receive(:==) - obj.should_not_receive(:eql?) - - {}.send(@method, h).should be_false - h.send(@method, {}).should be_false - end - - it "first compares keys via hash" do - x = mock('x') - x.should_receive(:hash).and_return(0) - y = mock('y') - y.should_receive(:hash).and_return(0) - - { x => 1 }.send(@method, { y => 1 }).should be_false - end - - it "does not compare keys with different hash codes via eql?" do - x = mock('x') - y = mock('y') - x.should_not_receive(:eql?) - y.should_not_receive(:eql?) - - x.should_receive(:hash).and_return(0) - y.should_receive(:hash).and_return(1) - - def x.hash() 0 end - def y.hash() 1 end - - { x => 1 }.send(@method, { y => 1 }).should be_false - end - - it "computes equality for recursive hashes" do - h = {} - h[:a] = h - h.send(@method, h[:a]).should be_true - (h == h[:a]).should be_true - end - - it "computes equality for complex recursive hashes" do - a, b = {}, {} - a.merge! self: a, other: b - b.merge! self: b, other: a - a.send(@method, b).should be_true # they both have the same structure! - - c = {} - c.merge! other: c, self: c - c.send(@method, a).should be_true # subtle, but they both have the same structure! - a[:delta] = c[:delta] = a - c.send(@method, a).should be_false # not quite the same structure, as a[:other][:delta] = nil - c[:delta] = 42 - c.send(@method, a).should be_false - a[:delta] = 42 - c.send(@method, a).should be_false - b[:delta] = 42 - c.send(@method, a).should be_true - end - - it "computes equality for recursive hashes & arrays" do - x, y, z = [], [], [] - a, b, c = {foo: x, bar: 42}, {foo: y, bar: 42}, {foo: z, bar: 42} - x << a - y << c - z << b - b.send(@method, c).should be_true # they clearly have the same structure! - y.send(@method, z).should be_true - a.send(@method, b).should be_true # subtle, but they both have the same structure! - x.send(@method, y).should be_true - y << x - y.send(@method, z).should be_false - z << x - y.send(@method, z).should be_true - - a[:foo], a[:bar] = a[:bar], a[:foo] - a.send(@method, b).should be_false - b[:bar] = b[:foo] - b.send(@method, c).should be_false - end -end diff --git a/spec/ruby/core/hash/shared/index.rb b/spec/ruby/core/hash/shared/index.rb index 4858ba85f5..7f6a186464 100644 --- a/spec/ruby/core/hash/shared/index.rb +++ b/spec/ruby/core/hash/shared/index.rb @@ -3,25 +3,35 @@ require_relative '../fixtures/classes' describe :hash_index, shared: true do it "returns the corresponding key for value" do - { 2 => 'a', 1 => 'b' }.send(@method, 'b').should == 1 + suppress_warning do # for Hash#index + { 2 => 'a', 1 => 'b' }.send(@method, 'b').should == 1 + end end it "returns nil if the value is not found" do - { a: -1, b: 3.14, c: 2.718 }.send(@method, 1).should be_nil + suppress_warning do # for Hash#index + { a: -1, b: 3.14, c: 2.718 }.send(@method, 1).should be_nil + end end it "doesn't return default value if the value is not found" do - Hash.new(5).send(@method, 5).should be_nil + suppress_warning do # for Hash#index + Hash.new(5).send(@method, 5).should be_nil + end end it "compares values using ==" do - { 1 => 0 }.send(@method, 0.0).should == 1 - { 1 => 0.0 }.send(@method, 0).should == 1 + suppress_warning do # for Hash#index + { 1 => 0 }.send(@method, 0.0).should == 1 + { 1 => 0.0 }.send(@method, 0).should == 1 + end needle = mock('needle') inhash = mock('inhash') inhash.should_receive(:==).with(needle).and_return(true) - { 1 => inhash }.send(@method, needle).should == 1 + suppress_warning do # for Hash#index + { 1 => inhash }.send(@method, needle).should == 1 + end end end diff --git a/spec/ruby/core/hash/shared/replace.rb b/spec/ruby/core/hash/shared/replace.rb deleted file mode 100644 index eb51130781..0000000000 --- a/spec/ruby/core/hash/shared/replace.rb +++ /dev/null @@ -1,51 +0,0 @@ -describe :hash_replace, shared: true do - it "replaces the contents of self with other" do - h = { a: 1, b: 2 } - h.send(@method, c: -1, d: -2).should equal(h) - h.should == { c: -1, d: -2 } - end - - it "tries to convert the passed argument to a hash using #to_hash" do - obj = mock('{1=>2,3=>4}') - obj.should_receive(:to_hash).and_return({ 1 => 2, 3 => 4 }) - - h = {} - h.send(@method, obj) - h.should == { 1 => 2, 3 => 4 } - end - - it "calls to_hash on hash subclasses" do - h = {} - h.send(@method, HashSpecs::ToHashHash[1 => 2]) - h.should == { 1 => 2 } - end - - it "does not transfer default values" do - hash_a = {} - hash_b = Hash.new(5) - hash_a.send(@method, hash_b) - hash_a.default.should == 5 - - hash_a = {} - hash_b = Hash.new { |h, k| k * 2 } - hash_a.send(@method, hash_b) - hash_a.default(5).should == 10 - - hash_a = Hash.new { |h, k| k * 5 } - hash_b = Hash.new(lambda { raise "Should not invoke lambda" }) - hash_a.send(@method, hash_b) - hash_a.default.should == hash_b.default - end - - it "raises a #{frozen_error_class} if called on a frozen instance that would not be modified" do - lambda do - HashSpecs.frozen_hash.send(@method, HashSpecs.frozen_hash) - end.should raise_error(frozen_error_class) - end - - it "raises a #{frozen_error_class} if called on a frozen instance that is modified" do - lambda do - HashSpecs.frozen_hash.send(@method, HashSpecs.empty_frozen_hash) - end.should raise_error(frozen_error_class) - end -end diff --git a/spec/ruby/core/hash/shared/select.rb b/spec/ruby/core/hash/shared/select.rb index cc1a54da25..fbeff07330 100644 --- a/spec/ruby/core/hash/shared/select.rb +++ b/spec/ruby/core/hash/shared/select.rb @@ -40,6 +40,27 @@ describe :hash_select, shared: true do @empty.send(@method).should be_an_instance_of(Enumerator) end + it "does not retain the default value" do + h = Hash.new(1) + h.send(@method) { true }.default.should be_nil + h[:a] = 1 + h.send(@method) { true }.default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.send(@method) { true }.default_proc.should be_nil + h[:a] = 1 + h.send(@method) { true }.default_proc.should be_nil + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.send(@method) { |k, _| k == :a } + h2.compare_by_identity?.should == true + end + it_should_behave_like :hash_iteration_no_block before :each do @@ -74,12 +95,12 @@ describe :hash_select!, shared: true do { a: 1 }.send(@method) { |k,v| v <= 1 }.should == nil end - it "raises a #{frozen_error_class} if called on an empty frozen instance" do - lambda { HashSpecs.empty_frozen_hash.send(@method) { false } }.should raise_error(frozen_error_class) + it "raises a FrozenError if called on an empty frozen instance" do + -> { HashSpecs.empty_frozen_hash.send(@method) { false } }.should raise_error(FrozenError) end - it "raises a #{frozen_error_class} if called on a frozen instance that would not be modified" do - lambda { HashSpecs.frozen_hash.send(@method) { true } }.should raise_error(frozen_error_class) + it "raises a FrozenError if called on a frozen instance that would not be modified" do + -> { HashSpecs.frozen_hash.send(@method) { true } }.should raise_error(FrozenError) end it_should_behave_like :hash_iteration_no_block diff --git a/spec/ruby/core/hash/shared/store.rb b/spec/ruby/core/hash/shared/store.rb index de61ed1ad1..72a462a42f 100644 --- a/spec/ruby/core/hash/shared/store.rb +++ b/spec/ruby/core/hash/shared/store.rb @@ -9,7 +9,7 @@ describe :hash_store, shared: true do it "duplicates string keys using dup semantics" do # dup doesn't copy singleton methods - key = "foo" + key = +"foo" def key.reverse() "bar" end h = {} h.send(@method, key, 0) @@ -36,7 +36,7 @@ describe :hash_store, shared: true do h[key].should == "foo" end - it " accepts keys with a Bignum hash" do + it " accepts keys with an Integer hash" do o = mock(hash: 1 << 100) h = {} h[o] = 1 @@ -44,13 +44,13 @@ describe :hash_store, shared: true do end it "duplicates and freezes string keys" do - key = "foo" + key = +"foo" h = {} h.send(@method, key, 0) key << "bar" h.should == { "foo" => 0 } - h.keys[0].frozen?.should == true + h.keys[0].should.frozen? end it "doesn't duplicate and freeze already frozen string keys" do @@ -75,8 +75,8 @@ describe :hash_store, shared: true do it "keeps the existing String key in the hash if there is a matching one" do h = { "a" => 1, "b" => 2, "c" => 3, "d" => 4 } - key1 = "foo" - key2 = "foo" + key1 = "foo".dup + key2 = "foo".dup key1.should_not equal(key2) h[key1] = 41 frozen_key = h.keys.last @@ -86,13 +86,30 @@ describe :hash_store, shared: true do h.keys.last.should_not equal(key2) end - it "raises a #{frozen_error_class} if called on a frozen instance" do - lambda { HashSpecs.frozen_hash.send(@method, 1, 2) }.should raise_error(frozen_error_class) + it "raises a FrozenError if called on a frozen instance" do + -> { HashSpecs.frozen_hash.send(@method, 1, 2) }.should raise_error(FrozenError) end it "does not raise an exception if changing the value of an existing key during iteration" do - hash = {1 => 2, 3 => 4, 5 => 6} - hash.each { hash.send(@method, 1, :foo) } - hash.should == {1 => :foo, 3 => 4, 5 => 6} + hash = {1 => 2, 3 => 4, 5 => 6} + hash.each { hash.send(@method, 1, :foo) } + hash.should == {1 => :foo, 3 => 4, 5 => 6} + end + + it "does not dispatch to hash for Boolean, Integer, Float, String, or Symbol" do + code = <<-EOC + load '#{fixture __FILE__, "name.rb"}' + hash = {} + [true, false, 1, 2.0, "hello", :ok].each do |value| + hash[value] = 42 + raise "incorrect value" unless hash[value] == 42 + hash[value] = 43 + raise "incorrect value" unless hash[value] == 43 + end + puts "OK" + puts hash.size + EOC + result = ruby_exe(code, args: "2>&1") + result.should == "OK\n6\n" end end diff --git a/spec/ruby/core/hash/shared/to_s.rb b/spec/ruby/core/hash/shared/to_s.rb index 88333e0f42..e116b8878b 100644 --- a/spec/ruby/core/hash/shared/to_s.rb +++ b/spec/ruby/core/hash/shared/to_s.rb @@ -2,17 +2,10 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' describe :hash_to_s, shared: true do - it "returns a string representation with same order as each()" do h = { a: [1, 2], b: -2, d: -6, nil => nil } - - pairs = [] - h.each do |key, value| - pairs << key.inspect + '=>' + value.inspect - end - - str = '{' + pairs.join(', ') + '}' - h.send(@method).should == str + expected = ruby_version_is("3.4") ? "{a: [1, 2], b: -2, d: -6, nil => nil}" : "{:a=>[1, 2], :b=>-2, :d=>-6, nil=>nil}" + h.send(@method).should == expected end it "calls #inspect on keys and values" do @@ -20,31 +13,31 @@ describe :hash_to_s, shared: true do val = mock('val') key.should_receive(:inspect).and_return('key') val.should_receive(:inspect).and_return('val') - - { key => val }.send(@method).should == '{key=>val}' + expected = ruby_version_is("3.4") ? "{key => val}" : "{key=>val}" + { key => val }.send(@method).should == expected end it "does not call #to_s on a String returned from #inspect" do - str = "abc" + str = +"abc" str.should_not_receive(:to_s) - - { a: str }.send(@method).should == '{:a=>"abc"}' + expected = ruby_version_is("3.4") ? '{a: "abc"}' : '{:a=>"abc"}' + { a: str }.send(@method).should == expected end it "calls #to_s on the object returned from #inspect if the Object isn't a String" do obj = mock("Hash#inspect/to_s calls #to_s") obj.should_receive(:inspect).and_return(obj) obj.should_receive(:to_s).and_return("abc") - - { a: obj }.send(@method).should == "{:a=>abc}" + expected = ruby_version_is("3.4") ? "{a: abc}" : "{:a=>abc}" + { a: obj }.send(@method).should == expected end it "does not call #to_str on the object returned from #inspect when it is not a String" do obj = mock("Hash#inspect/to_s does not call #to_str") obj.should_receive(:inspect).and_return(obj) obj.should_not_receive(:to_str) - - { a: obj }.send(@method).should =~ /^\{:a=>#<MockObject:0x[0-9a-f]+>\}$/ + expected_pattern = ruby_version_is("3.4") ? /^\{a: #<MockObject:0x[0-9a-f]+>\}$/ : /^\{:a=>#<MockObject:0x[0-9a-f]+>\}$/ + { a: obj }.send(@method).should =~ expected_pattern end it "does not call #to_str on the object returned from #to_s when it is not a String" do @@ -52,8 +45,8 @@ describe :hash_to_s, shared: true do obj.should_receive(:inspect).and_return(obj) obj.should_receive(:to_s).and_return(obj) obj.should_not_receive(:to_str) - - { a: obj }.send(@method).should =~ /^\{:a=>#<MockObject:0x[0-9a-f]+>\}$/ + expected_pattern = ruby_version_is("3.4") ? /^\{a: #<MockObject:0x[0-9a-f]+>\}$/ : /^\{:a=>#<MockObject:0x[0-9a-f]+>\}$/ + { a: obj }.send(@method).should =~ expected_pattern end it "does not swallow exceptions raised by #to_s" do @@ -61,36 +54,40 @@ describe :hash_to_s, shared: true do obj.should_receive(:inspect).and_return(obj) obj.should_receive(:to_s).and_raise(Exception) - lambda { { a: obj }.send(@method) }.should raise_error(Exception) + -> { { a: obj }.send(@method) }.should raise_error(Exception) end it "handles hashes with recursive values" do x = {} x[0] = x - x.send(@method).should == '{0=>{...}}' + expected = ruby_version_is("3.4") ? '{0 => {...}}' : '{0=>{...}}' + x.send(@method).should == expected x = {} y = {} x[0] = y y[1] = x - x.send(@method).should == "{0=>{1=>{...}}}" - y.send(@method).should == "{1=>{0=>{...}}}" + expected_x = ruby_version_is("3.4") ? '{0 => {1 => {...}}}' : '{0=>{1=>{...}}}' + expected_y = ruby_version_is("3.4") ? '{1 => {0 => {...}}}' : '{1=>{0=>{...}}}' + x.send(@method).should == expected_x + y.send(@method).should == expected_y end - it "returns a tainted string if self is tainted and not empty" do - {}.taint.send(@method).tainted?.should be_false - { nil => nil }.taint.send(@method).tainted?.should be_true + it "does not raise if inspected result is not default external encoding" do + utf_16be = mock("utf_16be") + utf_16be.should_receive(:inspect).and_return(%<"utf_16be \u3042">.encode(Encoding::UTF_16BE)) + expected = ruby_version_is("3.4") ? '{a: "utf_16be \u3042"}' : '{:a=>"utf_16be \u3042"}' + {a: utf_16be}.send(@method).should == expected end - it "returns an untrusted string if self is untrusted and not empty" do - {}.untrust.send(@method).untrusted?.should be_false - { nil => nil }.untrust.send(@method).untrusted?.should be_true + it "works for keys and values whose #inspect return a frozen String" do + expected = ruby_version_is("3.4") ? "{true => false}" : "{true=>false}" + { true => false }.to_s.should == expected end - it "does not raise if inspected result is not default external encoding" do - utf_16be = mock("utf_16be") - utf_16be.should_receive(:inspect).and_return(%<"utf_16be \u3042">.encode!(Encoding::UTF_16BE)) - - {a: utf_16be}.send(@method).should == '{:a=>"utf_16be \u3042"}' + ruby_version_is "3.4" do + it "adds quotes to symbol keys that are not valid symbol literals" do + { "needs-quotes": 1 }.send(@method).should == '{"needs-quotes": 1}' + end end end diff --git a/spec/ruby/core/hash/shared/update.rb b/spec/ruby/core/hash/shared/update.rb index e808add5c0..1b0eb809bf 100644 --- a/spec/ruby/core/hash/shared/update.rb +++ b/spec/ruby/core/hash/shared/update.rb @@ -34,10 +34,10 @@ describe :hash_update, shared: true do merge_bang_pairs.should == merge_pairs end - it "raises a #{frozen_error_class} on a frozen instance that is modified" do - lambda do + it "raises a FrozenError on a frozen instance that is modified" do + -> do HashSpecs.frozen_hash.send(@method, 1 => 2) - end.should raise_error(frozen_error_class) + end.should raise_error(FrozenError) end it "checks frozen status before coercing an object with #to_hash" do @@ -47,14 +47,14 @@ describe :hash_update, shared: true do def obj.to_hash() raise Exception, "should not receive #to_hash" end obj.freeze - lambda { HashSpecs.frozen_hash.send(@method, obj) }.should raise_error(frozen_error_class) + -> { HashSpecs.frozen_hash.send(@method, obj) }.should raise_error(FrozenError) end # see redmine #1571 - it "raises a #{frozen_error_class} on a frozen instance that would not be modified" do - lambda do + it "raises a FrozenError on a frozen instance that would not be modified" do + -> do HashSpecs.frozen_hash.send(@method, HashSpecs.empty_frozen_hash) - end.should raise_error(frozen_error_class) + end.should raise_error(FrozenError) end it "does not raise an exception if changing the value of an existing key during iteration" do @@ -64,15 +64,13 @@ describe :hash_update, shared: true do hash.should == {1 => :foo, 3 => :bar, 5 => 6} end - ruby_version_is "2.6" do - it "accepts multiple hashes" do - result = { a: 1 }.send(@method, { b: 2 }, { c: 3 }, { d: 4 }) - result.should == { a: 1, b: 2, c: 3, d: 4 } - end + it "accepts multiple hashes" do + result = { a: 1 }.send(@method, { b: 2 }, { c: 3 }, { d: 4 }) + result.should == { a: 1, b: 2, c: 3, d: 4 } + end - it "accepts zero arguments" do - hash = { a: 1 } - hash.send(@method).should eql(hash) - end + it "accepts zero arguments" do + hash = { a: 1 } + hash.send(@method).should eql(hash) end end diff --git a/spec/ruby/core/hash/shift_spec.rb b/spec/ruby/core/hash/shift_spec.rb index 47c9ac1821..3f31b9864c 100644 --- a/spec/ruby/core/hash/shift_spec.rb +++ b/spec/ruby/core/hash/shift_spec.rb @@ -30,23 +30,22 @@ describe "Hash#shift" do h.should == {} end - it "calls #default with nil if the Hash is empty" do + it "returns nil if the Hash is empty" do h = {} def h.default(key) - key.should == nil - :foo + raise end - h.shift.should == :foo + h.shift.should == nil end it "returns nil from an empty hash" do {}.shift.should == nil end - it "returns (computed) default for empty hashes" do - Hash.new(5).shift.should == 5 + it "returns nil for empty hashes with defaults and default procs" do + Hash.new(5).shift.should == nil h = Hash.new { |*args| args } - h.shift.should == [h, nil] + h.shift.should == nil end it "preserves Hash invariants when removing the last item" do @@ -57,9 +56,9 @@ describe "Hash#shift" do h.should == {:c => 3} end - it "raises a #{frozen_error_class} if called on a frozen instance" do - lambda { HashSpecs.frozen_hash.shift }.should raise_error(frozen_error_class) - lambda { HashSpecs.empty_frozen_hash.shift }.should raise_error(frozen_error_class) + it "raises a FrozenError if called on a frozen instance" do + -> { HashSpecs.frozen_hash.shift }.should raise_error(FrozenError) + -> { HashSpecs.empty_frozen_hash.shift }.should raise_error(FrozenError) end it "works when the hash is at capacity" do diff --git a/spec/ruby/core/hash/slice_spec.rb b/spec/ruby/core/hash/slice_spec.rb index f7717c9404..4fcc01f9a6 100644 --- a/spec/ruby/core/hash/slice_spec.rb +++ b/spec/ruby/core/hash/slice_spec.rb @@ -1,55 +1,74 @@ require_relative '../../spec_helper' -ruby_version_is "2.5" do - describe "Hash#slice" do - before :each do - @hash = { a: 1, b: 2, c: 3 } - end +describe "Hash#slice" do + before :each do + @hash = { a: 1, b: 2, c: 3 } + end - it "returns a new empty hash without arguments" do - ret = @hash.slice - ret.should_not equal(@hash) - ret.should be_an_instance_of(Hash) - ret.should == {} - end + it "returns a new empty hash without arguments" do + ret = @hash.slice + ret.should_not equal(@hash) + ret.should be_an_instance_of(Hash) + ret.should == {} + end - it "returns the requested subset" do - @hash.slice(:c, :a).should == { c: 3, a: 1 } - end + it "returns the requested subset" do + @hash.slice(:c, :a).should == { c: 3, a: 1 } + end - it "returns a hash ordered in the order of the requested keys" do - @hash.slice(:c, :a).keys.should == [:c, :a] - end + it "returns a hash ordered in the order of the requested keys" do + @hash.slice(:c, :a).keys.should == [:c, :a] + end - it "returns only the keys of the original hash" do - @hash.slice(:a, :chunky_bacon).should == { a: 1 } - end + it "returns only the keys of the original hash" do + @hash.slice(:a, :chunky_bacon).should == { a: 1 } + end - it "returns a Hash instance, even on subclasses" do - klass = Class.new(Hash) - h = klass.new - h[:bar] = 12 - h[:foo] = 42 - r = h.slice(:foo) - r.should == {foo: 42} - r.class.should == Hash - end + it "returns a Hash instance, even on subclasses" do + klass = Class.new(Hash) + h = klass.new + h[:bar] = 12 + h[:foo] = 42 + r = h.slice(:foo) + r.should == {foo: 42} + r.class.should == Hash + end - it "uses the regular Hash#[] method, even on subclasses that override it" do - ScratchPad.record [] - klass = Class.new(Hash) do - def [](value) - ScratchPad << :used_subclassed_operator - super - end + it "uses the regular Hash#[] method, even on subclasses that override it" do + ScratchPad.record [] + klass = Class.new(Hash) do + def [](value) + ScratchPad << :used_subclassed_operator + super end + end - h = klass.new - h[:bar] = 12 - h[:foo] = 42 - h.slice(:foo) + h = klass.new + h[:bar] = 12 + h[:foo] = 42 + h.slice(:foo) - ScratchPad.recorded.should == [] - end + ScratchPad.recorded.should == [] + end + + it "does not retain the default value" do + h = Hash.new(1) + h.slice(:a).default.should be_nil + h[:a] = 1 + h.slice(:a).default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.slice(:a).default_proc.should be_nil + h[:a] = 1 + h.slice(:a).default_proc.should be_nil + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.slice(:a) + h2.compare_by_identity?.should == true end end diff --git a/spec/ruby/core/hash/to_a_spec.rb b/spec/ruby/core/hash/to_a_spec.rb index 33ad7cdec9..5baf677929 100644 --- a/spec/ruby/core/hash/to_a_spec.rb +++ b/spec/ruby/core/hash/to_a_spec.rb @@ -26,12 +26,4 @@ describe "Hash#to_a" do ent.should be_kind_of(Array) ent.should == pairs end - - it "returns a tainted array if self is tainted" do - {}.taint.to_a.tainted?.should be_true - end - - it "returns an untrusted array if self is untrusted" do - {}.untrust.to_a.untrusted?.should be_true - end end diff --git a/spec/ruby/core/hash/to_h_spec.rb b/spec/ruby/core/hash/to_h_spec.rb index d6eaac9f33..f84fd7b503 100644 --- a/spec/ruby/core/hash/to_h_spec.rb +++ b/spec/ruby/core/hash/to_h_spec.rb @@ -19,56 +19,88 @@ describe "Hash#to_h" do @h[:foo].should == :bar end - it "copies the default" do + it "retains the default" do @h.default = 42 @h.to_h.default.should == 42 @h[:hello].should == 42 end - it "copies the default_proc" do + it "retains the default_proc" do @h.default_proc = prc = Proc.new{ |h, k| h[k] = 2 * k } @h.to_h.default_proc.should == prc @h[42].should == 84 end + + it "retains compare_by_identity flag" do + @h.compare_by_identity + @h.to_h.compare_by_identity?.should == true + end end - ruby_version_is "2.6" do - context "with block" do - it "converts [key, value] pairs returned by the block to a hash" do - { a: 1, b: 2 }.to_h { |k, v| [k.to_s, v*v]}.should == { "a" => 1, "b" => 4 } - end - - it "raises ArgumentError if block returns longer or shorter array" do - -> do - { a: 1, b: 2 }.to_h { |k, v| [k.to_s, v*v, 1] } - end.should raise_error(ArgumentError, /element has wrong array length/) - - -> do - { a: 1, b: 2 }.to_h { |k, v| [k] } - end.should raise_error(ArgumentError, /element has wrong array length/) - end - - it "raises TypeError if block returns something other than Array" do - -> do - { a: 1, b: 2 }.to_h { |k, v| "not-array" } - end.should raise_error(TypeError, /wrong element type String/) - end - - it "coerces returned pair to Array with #to_ary" do - x = mock('x') - x.stub!(:to_ary).and_return([:b, 'b']) - - { a: 1 }.to_h { |k| x }.should == { :b => 'b' } - end - - it "does not coerce returned pair to Array with #to_a" do - x = mock('x') - x.stub!(:to_a).and_return([:b, 'b']) - - -> do - { a: 1 }.to_h { |k| x } - end.should raise_error(TypeError, /wrong element type MockObject/) - end + context "with block" do + it "converts [key, value] pairs returned by the block to a hash" do + { a: 1, b: 2 }.to_h { |k, v| [k.to_s, v*v]}.should == { "a" => 1, "b" => 4 } + end + + it "passes to a block each pair's key and value as separate arguments" do + ScratchPad.record [] + { a: 1, b: 2 }.to_h { |k, v| ScratchPad << [k, v]; [k, v] } + ScratchPad.recorded.sort.should == [[:a, 1], [:b, 2]] + + ScratchPad.record [] + { a: 1, b: 2 }.to_h { |*args| ScratchPad << args; [args[0], args[1]] } + ScratchPad.recorded.sort.should == [[:a, 1], [:b, 2]] + end + + it "raises ArgumentError if block returns longer or shorter array" do + -> do + { a: 1, b: 2 }.to_h { |k, v| [k.to_s, v*v, 1] } + end.should raise_error(ArgumentError, /element has wrong array length/) + + -> do + { a: 1, b: 2 }.to_h { |k, v| [k] } + end.should raise_error(ArgumentError, /element has wrong array length/) + end + + it "raises TypeError if block returns something other than Array" do + -> do + { a: 1, b: 2 }.to_h { |k, v| "not-array" } + end.should raise_error(TypeError, /wrong element type String/) + end + + it "coerces returned pair to Array with #to_ary" do + x = mock('x') + x.stub!(:to_ary).and_return([:b, 'b']) + + { a: 1 }.to_h { |k| x }.should == { :b => 'b' } + end + + it "does not coerce returned pair to Array with #to_a" do + x = mock('x') + x.stub!(:to_a).and_return([:b, 'b']) + + -> do + { a: 1 }.to_h { |k| x } + end.should raise_error(TypeError, /wrong element type MockObject/) + end + + it "does not retain the default value" do + h = Hash.new(1) + h2 = h.to_h { |k, v| [k.to_s, v*v]} + h2.default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h2 = h.to_h { |k, v| [k.to_s, v*v]} + h2.default_proc.should be_nil + end + + it "does not retain compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.to_h { |k, v| [k.to_s, v*v]} + h2.compare_by_identity?.should == false end end end diff --git a/spec/ruby/core/hash/to_proc_spec.rb b/spec/ruby/core/hash/to_proc_spec.rb index ca55604043..9dbc79e5eb 100644 --- a/spec/ruby/core/hash/to_proc_spec.rb +++ b/spec/ruby/core/hash/to_proc_spec.rb @@ -19,16 +19,20 @@ describe "Hash#to_proc" do @proc = @hash.to_proc end - it "is not a lambda" do - @proc.lambda?.should == false + it "is a lambda" do + @proc.should.lambda? + end + + it "has an arity of 1" do + @proc.arity.should == 1 end it "raises ArgumentError if not passed exactly one argument" do - lambda { + -> { @proc.call }.should raise_error(ArgumentError) - lambda { + -> { @proc.call 1, 2 }.should raise_error(ArgumentError) end @@ -81,7 +85,7 @@ describe "Hash#to_proc" do end it "raises an ArgumentError when calling #call on the Proc with no arguments" do - lambda { @hash.to_proc.call }.should raise_error(ArgumentError) + -> { @hash.to_proc.call }.should raise_error(ArgumentError) end end end diff --git a/spec/ruby/core/hash/transform_keys_spec.rb b/spec/ruby/core/hash/transform_keys_spec.rb index 32ac89b765..e2eeab1813 100644 --- a/spec/ruby/core/hash/transform_keys_spec.rb +++ b/spec/ruby/core/hash/transform_keys_spec.rb @@ -1,131 +1,149 @@ require_relative '../../spec_helper' -ruby_version_is "2.5" do - describe "Hash#transform_keys" do - before :each do - @hash = { a: 1, b: 2, c: 3 } - end +describe "Hash#transform_keys" do + before :each do + @hash = { a: 1, b: 2, c: 3 } + end - it "returns new hash" do - ret = @hash.transform_keys(&:succ) - ret.should_not equal(@hash) - ret.should be_an_instance_of(Hash) - end + it "returns new hash" do + ret = @hash.transform_keys(&:succ) + ret.should_not equal(@hash) + ret.should be_an_instance_of(Hash) + end - it "sets the result as transformed keys with the given block" do - @hash.transform_keys(&:succ).should == { b: 1, c: 2, d: 3 } - end + it "sets the result as transformed keys with the given block" do + @hash.transform_keys(&:succ).should == { b: 1, c: 2, d: 3 } + end - it "keeps last pair if new keys conflict" do - @hash.transform_keys { |_| :a }.should == { a: 3 } - end + it "keeps last pair if new keys conflict" do + @hash.transform_keys { |_| :a }.should == { a: 3 } + end - it "makes both hashes to share values" do - value = [1, 2, 3] - new_hash = { a: value }.transform_keys(&:upcase) - new_hash[:A].should equal(value) - end + it "makes both hashes to share values" do + value = [1, 2, 3] + new_hash = { a: value }.transform_keys(&:upcase) + new_hash[:A].should equal(value) + end - context "when no block is given" do - it "returns a sized Enumerator" do - enumerator = @hash.transform_keys - enumerator.should be_an_instance_of(Enumerator) - enumerator.size.should == @hash.size - enumerator.each(&:succ).should == { b: 1, c: 2, d: 3 } - end + context "when no block is given" do + it "returns a sized Enumerator" do + enumerator = @hash.transform_keys + enumerator.should be_an_instance_of(Enumerator) + enumerator.size.should == @hash.size + enumerator.each(&:succ).should == { b: 1, c: 2, d: 3 } end + end - it "returns a Hash instance, even on subclasses" do - klass = Class.new(Hash) - h = klass.new - h[:foo] = 42 - r = h.transform_keys{|v| :"x#{v}"} - r.keys.should == [:xfoo] - r.class.should == Hash - end + it "returns a Hash instance, even on subclasses" do + klass = Class.new(Hash) + h = klass.new + h[:foo] = 42 + r = h.transform_keys{|v| :"x#{v}"} + r.keys.should == [:xfoo] + r.class.should == Hash end - describe "Hash#transform_keys!" do - before :each do - @hash = { a: 1, b: 2, c: 3, d: 4 } - @initial_pairs = @hash.dup - end + it "allows a hash argument" do + @hash.transform_keys({ a: :A, b: :B, c: :C }).should == { A: 1, B: 2, C: 3 } + end - it "returns self" do - @hash.transform_keys!(&:succ).should equal(@hash) - end + it "allows a partial transformation of keys when using a hash argument" do + @hash.transform_keys({ a: :A, c: :C }).should == { A: 1, b: 2, C: 3 } + end - it "updates self as transformed values with the given block" do - @hash.transform_keys!(&:to_s) - @hash.should == { 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4 } - end + it "allows a combination of hash and block argument" do + @hash.transform_keys({ a: :A }, &:to_s).should == { A: 1, 'b' => 2, 'c' => 3 } + end - # https://bugs.ruby-lang.org/issues/14380 - ruby_version_is ""..."2.5.1" do - it "does not prevent conflicts between new keys and old ones" do - @hash.transform_keys!(&:succ) - @hash.should == { e: 1 } - end - end + it "does not retain the default value" do + h = Hash.new(1) + h.transform_keys(&:succ).default.should be_nil + h[:a] = 1 + h.transform_keys(&:succ).default.should be_nil + end - ruby_version_is "2.5.1" do - it "prevents conflicts between new keys and old ones" do - @hash.transform_keys!(&:succ) - @hash.should == { b: 1, c: 2, d: 3, e: 4 } - end - end + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.transform_values(&:succ).default_proc.should be_nil + h[:a] = 1 + h.transform_values(&:succ).default_proc.should be_nil + end - ruby_version_is ""..."2.5.1" do - it "partially modifies the contents if we broke from the block" do - @hash.transform_keys! do |v| - break if v == :c - v.succ - end - @hash.should == { c: 1, d: 4 } - end - end + it "does not retain compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.transform_keys(&:succ) + h2.compare_by_identity?.should == false + end +end - ruby_version_is "2.5.1" do - it "returns the processed keys if we broke from the block" do - @hash.transform_keys! do |v| - break if v == :c - v.succ - end - @hash.should == { b: 1, c: 2 } - end +describe "Hash#transform_keys!" do + before :each do + @hash = { a: 1, b: 2, c: 3, d: 4 } + @initial_pairs = @hash.dup + end + + it "returns self" do + @hash.transform_keys!(&:succ).should equal(@hash) + end + + it "updates self as transformed values with the given block" do + @hash.transform_keys!(&:to_s) + @hash.should == { 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4 } + end + + it "prevents conflicts between new keys and old ones" do + @hash.transform_keys!(&:succ) + @hash.should == { b: 1, c: 2, d: 3, e: 4 } + end + + it "returns the processed keys and non evaluated keys if we break from the block" do + @hash.transform_keys! do |v| + break if v == :c + v.succ end + @hash.should == { b: 1, c: 2, d: 4 } + end + + it "keeps later pair if new keys conflict" do + @hash.transform_keys! { |_| :a }.should == { a: 4 } + end - it "keeps later pair if new keys conflict" do - @hash.transform_keys! { |_| :a }.should == { a: 4 } + context "when no block is given" do + it "returns a sized Enumerator" do + enumerator = @hash.transform_keys! + enumerator.should be_an_instance_of(Enumerator) + enumerator.size.should == @hash.size + enumerator.each(&:upcase).should == { A: 1, B: 2, C: 3, D: 4 } end + end - context "when no block is given" do - it "returns a sized Enumerator" do - enumerator = @hash.transform_keys! - enumerator.should be_an_instance_of(Enumerator) - enumerator.size.should == @hash.size - enumerator.each(&:upcase).should == { A: 1, B: 2, C: 3, D: 4 } - end + it "allows a hash argument" do + @hash.transform_keys!({ a: :A, b: :B, c: :C, d: :D }) + @hash.should == { A: 1, B: 2, C: 3, D: 4 } + end + + describe "on frozen instance" do + before :each do + @hash.freeze end - describe "on frozen instance" do - before :each do - @hash.freeze - end + it "raises a FrozenError on an empty hash" do + ->{ {}.freeze.transform_keys!(&:upcase) }.should raise_error(FrozenError) + end - it "raises a #{frozen_error_class} on an empty hash" do - ->{ {}.freeze.transform_keys!(&:upcase) }.should raise_error(frozen_error_class) - end + it "keeps pairs and raises a FrozenError" do + ->{ @hash.transform_keys!(&:upcase) }.should raise_error(FrozenError) + @hash.should == @initial_pairs + end - it "keeps pairs and raises a #{frozen_error_class}" do - ->{ @hash.transform_keys!(&:upcase) }.should raise_error(frozen_error_class) - @hash.should == @initial_pairs - end + it "raises a FrozenError on hash argument" do + ->{ @hash.transform_keys!({ a: :A, b: :B, c: :C }) }.should raise_error(FrozenError) + end - context "when no block is given" do - it "does not raise an exception" do - @hash.transform_keys!.should be_an_instance_of(Enumerator) - end + context "when no block is given" do + it "does not raise an exception" do + @hash.transform_keys!.should be_an_instance_of(Enumerator) end end end diff --git a/spec/ruby/core/hash/transform_values_spec.rb b/spec/ruby/core/hash/transform_values_spec.rb index 8b53b7a522..4a0ae8a5a5 100644 --- a/spec/ruby/core/hash/transform_values_spec.rb +++ b/spec/ruby/core/hash/transform_values_spec.rb @@ -39,6 +39,27 @@ describe "Hash#transform_values" do r[:foo].should == 84 r.class.should == Hash end + + it "does not retain the default value" do + h = Hash.new(1) + h.transform_values(&:succ).default.should be_nil + h[:a] = 1 + h.transform_values(&:succ).default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.transform_values(&:succ).default_proc.should be_nil + h[:a] = 1 + h.transform_values(&:succ).default_proc.should be_nil + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.transform_values(&:succ) + h2.compare_by_identity?.should == true + end end describe "Hash#transform_values!" do @@ -79,12 +100,12 @@ describe "Hash#transform_values!" do @hash.freeze end - it "raises a #{frozen_error_class} on an empty hash" do - ->{ {}.freeze.transform_values!(&:succ) }.should raise_error(frozen_error_class) + it "raises a FrozenError on an empty hash" do + ->{ {}.freeze.transform_values!(&:succ) }.should raise_error(FrozenError) end - it "keeps pairs and raises a #{frozen_error_class}" do - ->{ @hash.transform_values!(&:succ) }.should raise_error(frozen_error_class) + it "keeps pairs and raises a FrozenError" do + ->{ @hash.transform_values!(&:succ) }.should raise_error(FrozenError) @hash.should == @initial_pairs end diff --git a/spec/ruby/core/hash/try_convert_spec.rb b/spec/ruby/core/hash/try_convert_spec.rb index 50bb59816c..d359ae49d8 100644 --- a/spec/ruby/core/hash/try_convert_spec.rb +++ b/spec/ruby/core/hash/try_convert_spec.rb @@ -39,12 +39,12 @@ describe "Hash.try_convert" do it "sends #to_hash to the argument and raises TypeError if it's not a kind of Hash" do obj = mock("to_hash") obj.should_receive(:to_hash).and_return(Object.new) - lambda { Hash.try_convert obj }.should raise_error(TypeError) + -> { Hash.try_convert obj }.should raise_error(TypeError, "can't convert MockObject to Hash (MockObject#to_hash gives Object)") end it "does not rescue exceptions raised by #to_hash" do obj = mock("to_hash") obj.should_receive(:to_hash).and_raise(RuntimeError) - lambda { Hash.try_convert obj }.should raise_error(RuntimeError) + -> { Hash.try_convert obj }.should raise_error(RuntimeError) end end |
