diff options
Diffstat (limited to 'spec/ruby/core/hash')
88 files changed, 2206 insertions, 1566 deletions
diff --git a/spec/ruby/core/hash/allocate_spec.rb b/spec/ruby/core/hash/allocate_spec.rb index d607f235bd..a92accc161 100644 --- a/spec/ruby/core/hash/allocate_spec.rb +++ b/spec/ruby/core/hash/allocate_spec.rb @@ -1,9 +1,9 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Hash.allocate" do it "returns an instance of Hash" do hsh = Hash.allocate - hsh.should be_an_instance_of(Hash) + hsh.should.instance_of?(Hash) end it "returns a fully-formed instance of Hash" do diff --git a/spec/ruby/core/hash/any_spec.rb b/spec/ruby/core/hash/any_spec.rb index ab8d320c18..c26dfabde6 100644 --- a/spec/ruby/core/hash/any_spec.rb +++ b/spec/ruby/core/hash/any_spec.rb @@ -1,13 +1,13 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' 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 0b10720b6f..51af51a20f 100644 --- a/spec/ruby/core/hash/assoc_spec.rb +++ b/spec/ruby/core/hash/assoc_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Hash#assoc" do before :each do @@ -6,7 +6,7 @@ describe "Hash#assoc" do end it "returns an Array if the argument is == to a key of the Hash" do - @h.assoc(:apple).should be_an_instance_of(Array) + @h.assoc(:apple).should.instance_of?(Array) end it "returns a 2-element Array if the argument is == to a key of the Hash" do @@ -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 @@ -40,11 +40,11 @@ describe "Hash#assoc" do end it "returns nil if the argument is not a key of the Hash" do - @h.assoc(:green).should be_nil + @h.assoc(:green).should == nil end it "returns nil if the argument is not a key of the Hash even when there is a default" do - Hash.new(42).merge!( foo: :bar ).assoc(42).should be_nil - Hash.new{|h, k| h[k] = 42}.merge!( foo: :bar ).assoc(42).should be_nil + Hash.new(42).merge!( foo: :bar ).assoc(42).should == nil + Hash.new{|h, k| h[k] = 42}.merge!( foo: :bar ).assoc(42).should == nil end end diff --git a/spec/ruby/core/hash/clear_spec.rb b/spec/ruby/core/hash/clear_spec.rb index ea8235451a..beff925a63 100644 --- a/spec/ruby/core/hash/clear_spec.rb +++ b/spec/ruby/core/hash/clear_spec.rb @@ -1,10 +1,10 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash#clear" do it "removes all key, value pairs" do h = { 1 => 2, 3 => 4 } - h.clear.should equal(h) + h.clear.should.equal?(h) h.should == {} end @@ -25,8 +25,8 @@ describe "Hash#clear" do h.default_proc.should_not == nil end - it "raises a RuntimeError if called on a frozen instance" do - lambda { HashSpecs.frozen_hash.clear }.should raise_error(RuntimeError) - lambda { HashSpecs.empty_frozen_hash.clear }.should raise_error(RuntimeError) + it "raises a FrozenError if called on a frozen instance" do + -> { HashSpecs.frozen_hash.clear }.should.raise(FrozenError) + -> { HashSpecs.empty_frozen_hash.clear }.should.raise(FrozenError) end end diff --git a/spec/ruby/core/hash/clone_spec.rb b/spec/ruby/core/hash/clone_spec.rb index 188ae1c807..d135a84453 100644 --- a/spec/ruby/core/hash/clone_spec.rb +++ b/spec/ruby/core/hash/clone_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Hash#clone" do it "copies instance variable but not the objects they refer to" do @@ -7,7 +7,6 @@ describe "Hash#clone" do clone = hash.clone clone.should == hash - clone.object_id.should_not == hash.object_id + clone.should_not.equal? hash end end - diff --git a/spec/ruby/core/hash/compact_spec.rb b/spec/ruby/core/hash/compact_spec.rb index d9ef8a2987..de83dabed3 100644 --- a/spec/ruby/core/hash/compact_spec.rb +++ b/spec/ruby/core/hash/compact_spec.rb @@ -1,61 +1,81 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' -ruby_version_is "2.4" do - describe "Hash#compact" do - before :each do - @hash = { truthy: true, false: false, nil: nil, nil => true } - @initial_pairs = @hash.dup - @compact = { truthy: true, false: false, nil => true } - end +describe "Hash#compact" do + before :each do + @hash = { truthy: true, false: false, nil: nil, nil => true } + @initial_pairs = @hash.dup + @compact = { truthy: true, false: false, nil => true } + end - it "returns new object that rejects pair has nil value" do - ret = @hash.compact - ret.should_not equal(@hash) - ret.should == @compact - end + it "returns new object that rejects pair has nil value" do + ret = @hash.compact + ret.should_not.equal?(@hash) + ret.should == @compact + end - it "keeps own pairs" do - @hash.compact - @hash.should == @initial_pairs - end + it "keeps own pairs" do + @hash.compact + @hash.should == @initial_pairs end - describe "Hash#compact!" do - before :each do - @hash = { truthy: true, false: false, nil: nil, nil => true } - @initial_pairs = @hash.dup - @compact = { truthy: true, false: false, nil => true } - end + 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 "returns self" do - @hash.compact!.should equal(@hash) - 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 "rejects own pair has nil value" do + 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 + +describe "Hash#compact!" do + before :each do + @hash = { truthy: true, false: false, nil: nil, nil => true } + @initial_pairs = @hash.dup + @compact = { truthy: true, false: false, nil => true } + end + + it "returns self" do + @hash.compact!.should.equal?(@hash) + end + + it "rejects own pair has nil value" do + @hash.compact! + @hash.should == @compact + end + + context "when each pair does not have nil value" do + before :each do @hash.compact! - @hash.should == @compact end - context "when each pair does not have nil value" do - before :each do - @hash.compact! - end - - it "returns nil" do - @hash.compact!.should be_nil - end + it "returns nil" do + @hash.compact!.should == nil end + end - describe "on frozen instance" do - before :each do - @hash.freeze - end + describe "on frozen instance" do + before :each do + @hash.freeze + end - it "keeps pairs and raises a RuntimeError" do - ->{ @hash.compact! }.should raise_error(RuntimeError) - @hash.should == @initial_pairs - end + it "keeps pairs and raises a FrozenError" do + ->{ @hash.compact! }.should.raise(FrozenError) + @hash.should == @initial_pairs end 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 a518e0cdb3..1abf9d5666 100644 --- a/spec/ruby/core/hash/compare_by_identity_spec.rb +++ b/spec/ruby/core/hash/compare_by_identity_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash#compare_by_identity" do before :each do @@ -11,7 +11,7 @@ describe "Hash#compare_by_identity" do @h[[1]] = :a @h[[1]].should == :a @h.compare_by_identity - @h[[1].dup].should be_nil + @h[[1].dup].should == nil end it "rehashes internally so that old keys can be looked up" do @@ -27,21 +27,21 @@ describe "Hash#compare_by_identity" do it "returns self" do h = {} h[:foo] = :bar - h.compare_by_identity.should equal h + h.compare_by_identity.should.equal? h end 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.compare_by_identity.should.equal? @idh + @idh.should.compare_by_identity? @idh[:foo].should == :bar end it "uses the semantics of BasicObject#equal? to determine key identity" do - [1].should_not equal([1]) + [1].should_not.equal?([1]) @idh[[1]] = :c @idh[[1]] = :d - :bar.should equal(:bar) + :bar.should.equal?(:bar) @idh[:bar] = :e @idh[:bar] = :f @idh.values.should == [:c, :d, :f] @@ -64,13 +64,13 @@ describe "Hash#compare_by_identity" do it "regards #dup'd objects as having different identities" do key = ['foo'] @idh[key.dup] = :str - @idh[key].should be_nil + @idh[key].should == nil end it "regards #clone'd objects as having different identities" do key = ['foo'] @idh[key.clone] = :str - @idh[key].should be_nil + @idh[key].should == nil end it "regards references to the same object as having the same identity" do @@ -80,24 +80,26 @@ describe "Hash#compare_by_identity" do @h[o].should == :o end - it "raises a RuntimeError on frozen hashes" do + it "raises a FrozenError on frozen hashes" do @h = @h.freeze - lambda { @h.compare_by_identity }.should raise_error(RuntimeError) + -> { @h.compare_by_identity }.should.raise(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 @@ -105,36 +107,41 @@ describe "Hash#compare_by_identity" do @idh[foo] = true @idh[foo] = true @idh.size.should == 1 - @idh.keys.first.object_id.should == foo.object_id - end - - ruby_bug "#12855", "2.2.0"..."2.4.1" do - it "gives different identity for string literals" do - @idh['foo'] = 1 - @idh['foo'] = 2 - @idh.values.should == [1, 2] - @idh.size.should == 2 - end + @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 end describe "Hash#compare_by_identity?" do it "returns false by default" do h = {} - h.compare_by_identity?.should be_false + h.compare_by_identity?.should == false end it "returns true once #compare_by_identity has been invoked on self" do h = {} h.compare_by_identity - h.compare_by_identity?.should be_true + h.compare_by_identity?.should == true end it "returns true when called multiple times on the same ident hash" do h = {} h.compare_by_identity - h.compare_by_identity?.should be_true - h.compare_by_identity?.should be_true - h.compare_by_identity?.should be_true + h.compare_by_identity?.should == true + h.compare_by_identity?.should == true + h.compare_by_identity?.should == true end end diff --git a/spec/ruby/core/hash/constructor_spec.rb b/spec/ruby/core/hash/constructor_spec.rb index 0f582d91de..c16f373160 100644 --- a/spec/ruby/core/hash/constructor_spec.rb +++ b/spec/ruby/core/hash/constructor_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash.[]" do describe "passed zero arguments" do @@ -42,25 +42,25 @@ describe "Hash.[]" do Hash[ary].should == { a: :b } end - it "ignores elements that are not arrays" do + it "raises for elements that are not arrays" do -> { - Hash[[:a]].should == {} - }.should complain(/ignoring wrong elements/) + Hash[[:a]] + }.should.raise(ArgumentError, "wrong element type Symbol at 0 (expected array)") -> { - Hash[[:nil]].should == {} - }.should complain(/ignoring wrong elements/) + Hash[[nil]] + }.should.raise(ArgumentError, "wrong element type nil at 0 (expected array)") 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.raise(ArgumentError, "invalid number of elements (3 for 1..2)") end it "raises an ArgumentError when passed a list of value-invalid-pairs in an array" do -> { - -> { - Hash[[[:a, 1], [:b], 42, [:d, 2], [:e, 2, 3], []]] - }.should complain(/ignoring wrong elements/) - }.should raise_error(ArgumentError) + Hash[[[:a, 1], [:b], 42, [:d, 2], [:e, 2, 3], []]] + }.should.raise(ArgumentError, "wrong element type Integer at 2 (expected array)") end describe "passed a single argument which responds to #to_hash" do @@ -71,13 +71,13 @@ describe "Hash.[]" do result = Hash[to_hash] result.should == h - result.should_not equal(h) + result.should_not.equal?(h) end 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(ArgumentError) + -> { Hash[1, 2, { 3 => 4 }] }.should.raise(ArgumentError) end it "calls to_hash" do @@ -87,24 +87,41 @@ describe "Hash.[]" do end it "returns an instance of a subclass when passed an Array" do - HashSpecs::MyHash[1,2,3,4].should be_an_instance_of(HashSpecs::MyHash) + HashSpecs::MyHash[1,2,3,4].should.instance_of?(HashSpecs::MyHash) end it "returns instances of subclasses" do - HashSpecs::MyHash[].should be_an_instance_of(HashSpecs::MyHash) + HashSpecs::MyHash[].should.instance_of?(HashSpecs::MyHash) end it "returns an instance of the class it's called on" do Hash[HashSpecs::MyHash[1, 2]].class.should == Hash - HashSpecs::MyHash[Hash[1, 2]].should be_an_instance_of(HashSpecs::MyHash) + HashSpecs::MyHash[Hash[1, 2]].should.instance_of?(HashSpecs::MyHash) end it "does not call #initialize on the subclass instance" do - HashSpecs::MyInitializerHash[Hash[1, 2]].should be_an_instance_of(HashSpecs::MyInitializerHash) + HashSpecs::MyInitializerHash[Hash[1, 2]].should.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 == nil + hash[:a] = 1 + Hash[hash].default.should == 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[hash].default_proc.should == nil + hash[:a] = 1 + Hash[hash].default_proc.should == nil + end + + 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 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..f6ff3a2818 --- /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(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 0bd20d43af..4830e02acd 100644 --- a/spec/ruby/core/hash/default_proc_spec.rb +++ b/spec/ruby/core/hash/default_proc_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash#default_proc" do it "returns the block passed to Hash.new" do @@ -25,24 +25,24 @@ describe "Hash#default_proc=" do h = Hash.new { 'Paris' } obj = mock('to_proc') obj.should_receive(:to_proc).and_return(Proc.new { 'Montreal' }) - (h.default_proc = obj).should equal(obj) + (h.default_proc = obj).should.equal?(obj) h[:cool_city].should == 'Montreal' end it "overrides the static default" do h = Hash.new(42) h.default_proc = Proc.new { 6 } - h.default.should be_nil + h.default.should == nil h.default_proc.call.should == 6 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(TypeError) end it "returns the passed Proc" do new_proc = Proc.new {} - ({}.default_proc = new_proc).should equal(new_proc) + ({}.default_proc = new_proc).should.equal?(new_proc) end it "clears the default proc if passed nil" do @@ -53,28 +53,28 @@ describe "Hash#default_proc=" do end it "returns nil if passed nil" do - ({}.default_proc = nil).should be_nil + ({}.default_proc = nil).should == nil end it "accepts a lambda with an arity of 2" do h = {} - lambda do - h.default_proc = lambda {|a,b| } - end.should_not raise_error(TypeError) + -> do + h.default_proc = -> a, b { } + end.should_not.raise(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| } - end.should raise_error(TypeError) - lambda do - h.default_proc = lambda {|a,b,c| } - end.should raise_error(TypeError) + -> do + h.default_proc = -> a { } + end.should.raise(TypeError) + -> do + h.default_proc = -> a, b, c { } + end.should.raise(TypeError) end - it "raises a RuntimeError if self is frozen" do - lambda { {}.freeze.default_proc = Proc.new {} }.should raise_error(RuntimeError) - lambda { {}.freeze.default_proc = nil }.should raise_error(RuntimeError) + it "raises a FrozenError if self is frozen" do + -> { {}.freeze.default_proc = Proc.new {} }.should.raise(FrozenError) + -> { {}.freeze.default_proc = nil }.should.raise(FrozenError) end end diff --git a/spec/ruby/core/hash/default_spec.rb b/spec/ruby/core/hash/default_spec.rb index 6c1c7377b7..17ee032e2b 100644 --- a/spec/ruby/core/hash/default_spec.rb +++ b/spec/ruby/core/hash/default_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash#default" do it "returns the default value" do @@ -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 RuntimeError if called on a frozen instance" do - lambda { HashSpecs.frozen_hash.default = nil }.should raise_error(RuntimeError) - lambda { HashSpecs.empty_frozen_hash.default = nil }.should raise_error(RuntimeError) + it "raises a FrozenError if called on a frozen instance" do + -> { HashSpecs.frozen_hash.default = nil }.should.raise(FrozenError) + -> { HashSpecs.empty_frozen_hash.default = nil }.should.raise(FrozenError) end end diff --git a/spec/ruby/core/hash/delete_if_spec.rb b/spec/ruby/core/hash/delete_if_spec.rb index d739e4fbab..3090633d88 100644 --- a/spec/ruby/core/hash/delete_if_spec.rb +++ b/spec/ruby/core/hash/delete_if_spec.rb @@ -1,7 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/iteration', __FILE__) -require File.expand_path('../../enumerable/shared/enumeratorized', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/iteration' +require_relative '../enumerable/shared/enumeratorized' describe "Hash#delete_if" do it "yields two arguments: key and value" do @@ -12,13 +12,13 @@ describe "Hash#delete_if" do it "removes every entry for which block is true and returns self" do h = { a: 1, b: 2, c: 3, d: 4 } - h.delete_if { |k,v| v % 2 == 1 }.should equal(h) + h.delete_if { |k,v| v % 2 == 1 }.should.equal?(h) h.should == { b: 2, d: 4 } end it "removes all entries if the block is true" do h = { a: 1, b: 2, c: 3 } - h.delete_if { |k,v| true }.should equal(h) + h.delete_if { |k,v| true }.should.equal?(h) h.should == {} end @@ -34,11 +34,11 @@ describe "Hash#delete_if" do each_pairs.should == delete_pairs end - it "raises a RuntimeError if called on a frozen instance" do - lambda { HashSpecs.frozen_hash.delete_if { false } }.should raise_error(RuntimeError) - lambda { HashSpecs.empty_frozen_hash.delete_if { true } }.should raise_error(RuntimeError) + it "raises a FrozenError if called on a frozen instance" do + -> { HashSpecs.frozen_hash.delete_if { false } }.should.raise(FrozenError) + -> { HashSpecs.empty_frozen_hash.delete_if { true } }.should.raise(FrozenError) end - it_behaves_like(:hash_iteration_no_block, :delete_if) - it_behaves_like(:enumeratorized_with_origin_size, :delete_if, { 1 => 2, 3 => 4, 5 => 6 }) + it_behaves_like :hash_iteration_no_block, :delete_if + it_behaves_like :enumeratorized_with_origin_size, :delete_if, { 1 => 2, 3 => 4, 5 => 6 } end diff --git a/spec/ruby/core/hash/delete_spec.rb b/spec/ruby/core/hash/delete_spec.rb index a41fad3586..33ff01bf36 100644 --- a/spec/ruby/core/hash/delete_spec.rb +++ b/spec/ruby/core/hash/delete_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash#delete" do it "removes the entry and returns the deleted value" do @@ -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 RuntimeError if called on a frozen instance" do - lambda { HashSpecs.frozen_hash.delete("foo") }.should raise_error(RuntimeError) - lambda { HashSpecs.empty_frozen_hash.delete("foo") }.should raise_error(RuntimeError) + it "raises a FrozenError if called on a frozen instance" do + -> { HashSpecs.frozen_hash.delete("foo") }.should.raise(FrozenError) + -> { HashSpecs.empty_frozen_hash.delete("foo") }.should.raise(FrozenError) end end diff --git a/spec/ruby/core/hash/dig_spec.rb b/spec/ruby/core/hash/dig_spec.rb index 237c407e91..94ac94e850 100644 --- a/spec/ruby/core/hash/dig_spec.rb +++ b/spec/ruby/core/hash/dig_spec.rb @@ -1,68 +1,66 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' -ruby_version_is '2.3' do - describe "Hash#dig" do +describe "Hash#dig" do - it "returns #[] with one arg" do - h = { 0 => false, a: 1 } - h.dig(:a).should == 1 - h.dig(0).should be_false - h.dig(1).should be_nil - end + it "returns #[] with one arg" do + h = { 0 => false, a: 1 } + h.dig(:a).should == 1 + h.dig(0).should == false + h.dig(1).should == nil + end - it "returns the nested value specified by the sequence of keys" do - h = { foo: { bar: { baz: 1 } } } - h.dig(:foo, :bar, :baz).should == 1 - h.dig(:foo, :bar, :nope).should be_nil - h.dig(:foo, :baz).should be_nil - h.dig(:bar, :baz, :foo).should be_nil - end + it "returns the nested value specified by the sequence of keys" do + h = { foo: { bar: { baz: 1 } } } + h.dig(:foo, :bar, :baz).should == 1 + h.dig(:foo, :bar, :nope).should == nil + h.dig(:foo, :baz).should == nil + h.dig(:bar, :baz, :foo).should == nil + end - it "returns the nested value specified if the sequence includes an index" do - h = { foo: [1, 2, 3] } - h.dig(:foo, 2).should == 3 - end + it "returns the nested value specified if the sequence includes an index" do + h = { foo: [1, 2, 3] } + h.dig(:foo, 2).should == 3 + end - it "returns nil if any intermediate step is nil" do - h = { foo: { bar: { baz: 1 } } } - h.dig(:foo, :zot, :xyz).should == nil - end + it "returns nil if any intermediate step is nil" do + h = { foo: { bar: { baz: 1 } } } + h.dig(:foo, :zot, :xyz).should == nil + end - it "raises an ArgumentError if no arguments provided" do - lambda { { the: 'borg' }.dig() }.should raise_error(ArgumentError) - end + it "raises an ArgumentError if no arguments provided" do + -> { { the: 'borg' }.dig() }.should.raise(ArgumentError) + end - it "handles type-mixed deep digging" do - h = {} - h[:foo] = [ { bar: [ 1 ] }, [ obj = Object.new, 'str' ] ] - def obj.dig(*args); [ 42 ] end + it "handles type-mixed deep digging" do + h = {} + h[:foo] = [ { bar: [ 1 ] }, [ obj = Object.new, 'str' ] ] + def obj.dig(*args); [ 42 ] end - h.dig(:foo, 0, :bar).should == [ 1 ] - h.dig(:foo, 0, :bar, 0).should == 1 - h.dig(:foo, 1, 1).should == 'str' - # MRI does not recurse values returned from `obj.dig` - h.dig(:foo, 1, 0, 0).should == [ 42 ] - h.dig(:foo, 1, 0, 0, 10).should == [ 42 ] - end + h.dig(:foo, 0, :bar).should == [ 1 ] + h.dig(:foo, 0, :bar, 0).should == 1 + h.dig(:foo, 1, 1).should == 'str' + # MRI does not recurse values returned from `obj.dig` + h.dig(:foo, 1, 0, 0).should == [ 42 ] + h.dig(:foo, 1, 0, 0, 10).should == [ 42 ] + end - 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) - end + it "raises TypeError if an intermediate element does not respond to #dig" do + h = {} + h[:foo] = [ { bar: [ 1 ] }, [ nil, 'str' ] ] + -> { h.dig(:foo, 0, :bar, 0, 0) }.should.raise(TypeError) + -> { h.dig(:foo, 1, 1, 0) }.should.raise(TypeError) + end - it "calls #dig on the result of #[] with the remaining arguments" do - h = { foo: { bar: { baz: 42 } } } - h[:foo].should_receive(:dig).with(:bar, :baz).and_return(42) - h.dig(:foo, :bar, :baz).should == 42 - end + it "calls #dig on the result of #[] with the remaining arguments" do + h = { foo: { bar: { baz: 42 } } } + h[:foo].should_receive(:dig).with(:bar, :baz).and_return(42) + h.dig(:foo, :bar, :baz).should == 42 + end - it "respects Hash's default" do - default = {bar: 42} - h = Hash.new(default) - h.dig(:foo).should equal default - h.dig(:foo, :bar).should == 42 - end + it "respects Hash's default" do + default = {bar: 42} + h = Hash.new(default) + h.dig(:foo).should.equal? default + h.dig(:foo, :bar).should == 42 end end diff --git a/spec/ruby/core/hash/each_key_spec.rb b/spec/ruby/core/hash/each_key_spec.rb index 4a4078a594..5b2e9deb63 100644 --- a/spec/ruby/core/hash/each_key_spec.rb +++ b/spec/ruby/core/hash/each_key_spec.rb @@ -1,13 +1,13 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/iteration', __FILE__) -require File.expand_path('../../enumerable/shared/enumeratorized', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/iteration' +require_relative '../enumerable/shared/enumeratorized' describe "Hash#each_key" do it "calls block once for each key, passing key" do r = {} h = { 1 => -1, 2 => -2, 3 => -3, 4 => -4 } - h.each_key { |k| r[k] = k }.should equal(h) + h.each_key { |k| r[k] = k }.should.equal?(h) r.should == { 1 => 1, 2 => 2, 3 => 3, 4 => 4 } end @@ -18,6 +18,6 @@ describe "Hash#each_key" do keys.should == h.keys end - it_behaves_like(:hash_iteration_no_block, :each_key) - it_behaves_like(:enumeratorized_with_origin_size, :each_key, { 1 => 2, 3 => 4, 5 => 6 }) + it_behaves_like :hash_iteration_no_block, :each_key + it_behaves_like :enumeratorized_with_origin_size, :each_key, { 1 => 2, 3 => 4, 5 => 6 } end diff --git a/spec/ruby/core/hash/each_pair_spec.rb b/spec/ruby/core/hash/each_pair_spec.rb index 285ca01b26..01810c130c 100644 --- a/spec/ruby/core/hash/each_pair_spec.rb +++ b/spec/ruby/core/hash/each_pair_spec.rb @@ -1,11 +1,111 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/iteration', __FILE__) -require File.expand_path('../shared/each', __FILE__) -require File.expand_path('../../enumerable/shared/enumeratorized', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/iteration' +require_relative '../enumerable/shared/enumeratorized' describe "Hash#each_pair" do - it_behaves_like(:hash_each, :each_pair) - it_behaves_like(:hash_iteration_no_block, :each_pair) - it_behaves_like(:enumeratorized_with_origin_size, :each_pair, { 1 => 2, 3 => 4, 5 => 6 }) + it_behaves_like :hash_iteration_no_block, :each_pair + it_behaves_like :enumeratorized_with_origin_size, :each_pair, { 1 => 2, 3 => 4, 5 => 6 } + + # This is inconsistent with below, MRI checks the block arity in rb_hash_each_pair() + it "yields a [[key, value]] Array for each pair to a block expecting |*args|" do + all_args = [] + { 1 => 2, 3 => 4 }.each_pair { |*args| all_args << args } + all_args.sort.should == [[[1, 2]], [[3, 4]]] + end + + it "yields the key and value of each pair to a block expecting |key, value|" do + r = {} + h = { a: 1, b: 2, c: 3, d: 5 } + h.each_pair { |k,v| r[k.to_s] = v.to_s }.should.equal?(h) + r.should == { "a" => "1", "b" => "2", "c" => "3", "d" => "5" } + end + + it "yields the key only to a block expecting |key,|" do + ary = [] + h = { "a" => 1, "b" => 2, "c" => 3 } + h.each_pair { |k,| ary << k } + ary.sort.should == ["a", "b", "c"] + end + + 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) + end + + -> { + { "a" => 1 }.each_pair(&obj.method(:foo)) + }.should.raise(ArgumentError) + + -> { + { "a" => 1 }.each_pair(&-> key, value { }) + }.should.raise(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 }.each_pair(&obj.method(:foo)) + ScratchPad.recorded.should == [["a", 1]] + end + + 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 }.each_pair(&obj.method(:foo)) + }.should.raise(ArgumentError) + end + + it "uses the same order as keys() and values()" do + h = { a: 1, b: 2, c: 3, d: 5 } + keys = [] + values = [] + + h.each_pair do |k, v| + keys << k + values << v + end + + keys.should == h.keys + values.should == h.values + end + + # Confirming the argument-splatting works from child class for both k, v and [k, v] + it "properly expands (or not) child class's 'each'-yielded args" do + cls1 = Class.new(Hash) do + attr_accessor :k_v + def each + super do |k, v| + @k_v = [k, v] + yield k, v + end + end + end + + cls2 = Class.new(Hash) do + attr_accessor :k_v + def each + super do |k, v| + @k_v = [k, v] + yield([k, v]) + end + end + end + + obj1 = cls1.new + obj1['a'] = 'b' + obj1.map {|k, v| [k, v]}.should == [['a', 'b']] + obj1.k_v.should == ['a', 'b'] + + obj2 = cls2.new + obj2['a'] = 'b' + obj2.map {|k, v| [k, v]}.should == [['a', 'b']] + obj2.k_v.should == ['a', 'b'] + end end diff --git a/spec/ruby/core/hash/each_spec.rb b/spec/ruby/core/hash/each_spec.rb index 676fd284b9..1644b63216 100644 --- a/spec/ruby/core/hash/each_spec.rb +++ b/spec/ruby/core/hash/each_spec.rb @@ -1,11 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/iteration', __FILE__) -require File.expand_path('../shared/each', __FILE__) -require File.expand_path('../../enumerable/shared/enumeratorized', __FILE__) +require_relative '../../spec_helper' describe "Hash#each" do - it_behaves_like(:hash_each, :each) - it_behaves_like(:hash_iteration_no_block, :each) - it_behaves_like(:enumeratorized_with_origin_size, :each, { 1 => 2, 3 => 4, 5 => 6 }) + it "is an alias of Hash#each_pair" do + Hash.instance_method(:each).should == Hash.instance_method(:each_pair) + end end diff --git a/spec/ruby/core/hash/each_value_spec.rb b/spec/ruby/core/hash/each_value_spec.rb index d3b2b8692e..2df95a8789 100644 --- a/spec/ruby/core/hash/each_value_spec.rb +++ b/spec/ruby/core/hash/each_value_spec.rb @@ -1,13 +1,13 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/iteration', __FILE__) -require File.expand_path('../../enumerable/shared/enumeratorized', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/iteration' +require_relative '../enumerable/shared/enumeratorized' describe "Hash#each_value" do it "calls block once for each key, passing value" do r = [] h = { a: -5, b: -3, c: -2, d: -1, e: -1 } - h.each_value { |v| r << v }.should equal(h) + h.each_value { |v| r << v }.should.equal?(h) r.sort.should == [-5, -3, -2, -1, -1] end @@ -18,6 +18,6 @@ describe "Hash#each_value" do values.should == h.values end - it_behaves_like(:hash_iteration_no_block, :each_value) - it_behaves_like(:enumeratorized_with_origin_size, :each_value, { 1 => 2, 3 => 4, 5 => 6 }) + it_behaves_like :hash_iteration_no_block, :each_value + it_behaves_like :enumeratorized_with_origin_size, :each_value, { 1 => 2, 3 => 4, 5 => 6 } end diff --git a/spec/ruby/core/hash/element_reference_spec.rb b/spec/ruby/core/hash/element_reference_spec.rb index b5ca56e84b..3d074b346d 100644 --- a/spec/ruby/core/hash/element_reference_spec.rb +++ b/spec/ruby/core/hash/element_reference_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash#[]" do it "returns the value for key" do @@ -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,13 +30,13 @@ 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] a << "bar" - a.should equal(b) + a.should.equal?(b) a.should == "foobar" b.should == "foobar" end @@ -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/element_set_spec.rb b/spec/ruby/core/hash/element_set_spec.rb index a2d67c7f22..bb805477ca 100644 --- a/spec/ruby/core/hash/element_set_spec.rb +++ b/spec/ruby/core/hash/element_set_spec.rb @@ -1,7 +1,121 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/store', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash#[]=" do - it_behaves_like(:hash_store, :[]=) + it "associates the key with the value" do + h = { a: 1 } + h[:b] = 2 + h.should == { b:2, a:1 } + end + + it "returns the assigned value" do + h = {} + h.send(:[]=, 1, 2).should == 2 + end + + it "duplicates string keys using dup semantics" do + # dup doesn't copy singleton methods + key = +"foo" + def key.reverse() "bar" end + h = {} + h[key] = 0 + h.keys[0].reverse.should == "oof" + end + + it "stores unequal keys that hash to the same value" do + h = {} + k1 = ["x"] + k2 = ["y"] + # So they end up in the same bucket + k1.should_receive(:hash).and_return(0) + k2.should_receive(:hash).and_return(0) + + h[k1] = 1 + h[k2] = 2 + h.size.should == 2 + end + + it "accepts keys with private #hash method" do + key = HashSpecs::KeyWithPrivateHash.new + h = {} + h[key] = "foo" + h[key].should == "foo" + end + + it " accepts keys with an Integer hash" do + o = mock(hash: 1 << 100) + h = {} + h[o] = 1 + h[o].should == 1 + end + + it "duplicates and freezes string keys" do + key = +"foo" + h = {} + h[key] = 0 + key << "bar" + + h.should == { "foo" => 0 } + h.keys[0].should.frozen? + end + + it "doesn't duplicate and freeze already frozen string keys" do + key = "foo".freeze + h = {} + h[key] = 0 + h.keys[0].should.equal?(key) + end + + it "keeps the existing key in the hash if there is a matching one" do + h = { "a" => 1, "b" => 2, "c" => 3, "d" => 4 } + key1 = HashSpecs::ByValueKey.new(13) + key2 = HashSpecs::ByValueKey.new(13) + h[key1] = 41 + key_in_hash = h.keys.last + key_in_hash.should.equal?(key1) + h[key2] = 42 + last_key = h.keys.last + last_key.should.equal?(key_in_hash) + last_key.should_not.equal?(key2) + end + + 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".dup + key2 = "foo".dup + key1.should_not.equal?(key2) + h[key1] = 41 + frozen_key = h.keys.last + frozen_key.should_not.equal?(key1) + h[key2] = 42 + h.keys.last.should.equal?(frozen_key) + h.keys.last.should_not.equal?(key2) + end + + it "raises a FrozenError if called on a frozen instance" do + -> { HashSpecs.frozen_hash[1] = 2 }.should.raise(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[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/empty_spec.rb b/spec/ruby/core/hash/empty_spec.rb index 84464f34e1..881e1cc34b 100644 --- a/spec/ruby/core/hash/empty_spec.rb +++ b/spec/ruby/core/hash/empty_spec.rb @@ -1,15 +1,15 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +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/eql_spec.rb b/spec/ruby/core/hash/eql_spec.rb index 3e22bf1c67..9013e12ffd 100644 --- a/spec/ruby/core/hash/eql_spec.rb +++ b/spec/ruby/core/hash/eql_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/eql', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/eql' describe "Hash#eql?" do it_behaves_like :hash_eql, :eql? diff --git a/spec/ruby/core/hash/equal_value_spec.rb b/spec/ruby/core/hash/equal_value_spec.rb index 76e4d796aa..ab14f81489 100644 --- a/spec/ruby/core/hash/equal_value_spec.rb +++ b/spec/ruby/core/hash/equal_value_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/eql', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/eql' describe "Hash#==" do it_behaves_like :hash_eql, :== @@ -13,6 +13,6 @@ describe "Hash#==" do l_val.should_receive(:==).with(r_val).and_return(true) - ({ 1 => l_val } == { 1 => r_val }).should be_true + ({ 1 => l_val } == { 1 => r_val }).should == true 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..77a020e9b7 --- /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 == nil + h[:a] = 1 + h.except(:a).default.should == 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 == nil + h[:a] = 1 + h.except(:a).default_proc.should == 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 5e701b1162..f9b80b6c49 100644 --- a/spec/ruby/core/hash/fetch_spec.rb +++ b/spec/ruby/core/hash/fetch_spec.rb @@ -1,15 +1,23 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../shared/hash/key_error' describe "Hash#fetch" do - it "returns the value for key" do - { a: 1, b: -1 }.fetch(:b).should == -1 + 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 "formats the object with #inspect in the KeyError message" do + -> { + {}.fetch('foo') + }.should.raise(KeyError, 'key not found: "foo"') + end end - it "raises a KeyError if key is not found" do - lambda { {}.fetch(:a) }.should raise_error(KeyError) - lambda { Hash.new(5).fetch(:a) }.should raise_error(KeyError) - lambda { Hash.new { 5 }.fetch(:a) }.should raise_error(KeyError) + it "returns the value for key" do + { a: 1, b: -1 }.fetch(:b).should == -1 end it "returns default if key is not found when passed a default" do @@ -23,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(ArgumentError) + -> { {}.fetch(1, 2, 3) }.should.raise(ArgumentError) end end diff --git a/spec/ruby/core/hash/fetch_values_spec.rb b/spec/ruby/core/hash/fetch_values_spec.rb index d6e47d5885..0cd48565af 100644 --- a/spec/ruby/core/hash/fetch_values_spec.rb +++ b/spec/ruby/core/hash/fetch_values_spec.rb @@ -1,35 +1,35 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../shared/hash/key_error' -ruby_version_is "2.3" do - describe "Hash#fetch_values" do - before :each do - @hash = { a: 1, b: 2, c: 3 } +describe "Hash#fetch_values" do + before :each do + @hash = { a: 1, b: 2, c: 3 } + end + + describe "with matched keys" do + it "returns the values for keys" do + @hash.fetch_values(:a).should == [1] + @hash.fetch_values(:a, :c).should == [1, 3] end - describe "with matched keys" do - it "returns the values for keys" 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 "raises a KeyError" do - ->{ @hash.fetch_values :z }.should raise_error(KeyError) - ->{ @hash.fetch_values :a, :z }.should raise_error(KeyError) - end + describe "with unmatched keys" do + 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"] - @hash.fetch_values(:a, :z) { |key| "`#{key}' is not found" }.should == [1, "`z' is not found"] - end + it "returns the default value from block" do + @hash.fetch_values(:z) { |key| "`#{key}' is not found" }.should == ["`z' is not found"] + @hash.fetch_values(:a, :z) { |key| "`#{key}' is not found" }.should == [1, "`z' is not found"] end + end - describe "without keys" do - it "returns an empty Array" do - @hash.fetch_values.should == [] - end + describe "without keys" do + it "returns an empty Array" do + @hash.fetch_values.should == [] end end end diff --git a/spec/ruby/core/hash/filter_spec.rb b/spec/ruby/core/hash/filter_spec.rb new file mode 100644 index 0000000000..625a7feb90 --- /dev/null +++ b/spec/ruby/core/hash/filter_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' + +describe "Hash#filter" do + it "is an alias of Hash#select" do + Hash.instance_method(:filter).should == Hash.instance_method(:select) + end +end + +describe "Hash#filter!" do + it "is an alias of Hash#select!" do + Hash.instance_method(:filter!).should == Hash.instance_method(:select!) + end +end diff --git a/spec/ruby/core/hash/fixtures/classes.rb b/spec/ruby/core/hash/fixtures/classes.rb index 3d3df576cf..ae907aaff6 100644 --- a/spec/ruby/core/hash/fixtures/classes.rb +++ b/spec/ruby/core/hash/fixtures/classes.rb @@ -17,6 +17,13 @@ module HashSpecs end end + class SubHashSettingInInitialize < Hash + def initialize(*args, &block) + self[:foo] = :bar + super(*args, &block) + end + end + class DefaultHash < Hash def default(key) 100 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 60762f864d..18e9a7b56a 100644 --- a/spec/ruby/core/hash/flatten_spec.rb +++ b/spec/ruby/core/hash/flatten_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Hash#flatten" do @@ -9,7 +9,7 @@ describe "Hash#flatten" do end it "returns an Array" do - {}.flatten.should be_an_instance_of(Array) + {}.flatten.should.instance_of?(Array) end it "returns an empty Array for an empty Hash" do @@ -55,8 +55,8 @@ 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.should.raise(TypeError) end end diff --git a/spec/ruby/core/hash/gt_spec.rb b/spec/ruby/core/hash/gt_spec.rb index 35bf52bf64..69d23b7d29 100644 --- a/spec/ruby/core/hash/gt_spec.rb +++ b/spec/ruby/core/hash/gt_spec.rb @@ -1,44 +1,42 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/comparison', __FILE__) -require File.expand_path('../shared/greater_than', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/comparison' +require_relative 'shared/greater_than' -ruby_version_is "2.3" do - describe "Hash#>" do - it_behaves_like :hash_comparison, :> - it_behaves_like :hash_greater_than, :> +describe "Hash#>" do + it_behaves_like :hash_comparison, :> + it_behaves_like :hash_greater_than, :> - it "returns false if both hashes are identical" do - h = { a: 1, b: 2 } - (h > h).should be_false - end + it "returns false if both hashes are identical" do + h = { a: 1, b: 2 } + (h > h).should == false end +end - describe "Hash#>" do - before :each do - @hash = {a:1, b:2} - @bigger = {a:1, b:2, c:3} - @unrelated = {c:3, d:4} - @similar = {a:2, b:3} - end +describe "Hash#>" do + before :each do + @hash = {a:1, b:2} + @bigger = {a:1, b:2, c:3} + @unrelated = {c:3, d:4} + @similar = {a:2, b:3} + end - it "returns false when receiver size is smaller than argument" do - (@hash > @bigger).should == false - (@unrelated > @bigger).should == false - end + it "returns false when receiver size is smaller than argument" do + (@hash > @bigger).should == false + (@unrelated > @bigger).should == false + end - it "returns false when receiver size is the same as argument" do - (@hash > @hash).should == false - (@hash > @unrelated).should == false - (@unrelated > @hash).should == false - end + it "returns false when receiver size is the same as argument" do + (@hash > @hash).should == false + (@hash > @unrelated).should == false + (@unrelated > @hash).should == false + end - it "returns true when argument is a subset of receiver" do - (@bigger > @hash).should == true - end + it "returns true when argument is a subset of receiver" do + (@bigger > @hash).should == true + end - it "returns false when keys match but values don't" do - (@hash > @similar).should == false - (@similar > @hash).should == false - end + it "returns false when keys match but values don't" do + (@hash > @similar).should == false + (@similar > @hash).should == false end end diff --git a/spec/ruby/core/hash/gte_spec.rb b/spec/ruby/core/hash/gte_spec.rb index 5453440ed6..be5060ea4f 100644 --- a/spec/ruby/core/hash/gte_spec.rb +++ b/spec/ruby/core/hash/gte_spec.rb @@ -1,44 +1,42 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/comparison', __FILE__) -require File.expand_path('../shared/greater_than', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/comparison' +require_relative 'shared/greater_than' -ruby_version_is "2.3" do - describe "Hash#>=" do - it_behaves_like :hash_comparison, :>= - it_behaves_like :hash_greater_than, :>= +describe "Hash#>=" do + it_behaves_like :hash_comparison, :>= + it_behaves_like :hash_greater_than, :>= - it "returns true if both hashes are identical" do - h = { a: 1, b: 2 } - (h >= h).should be_true - end + it "returns true if both hashes are identical" do + h = { a: 1, b: 2 } + (h >= h).should == true end +end - describe "Hash#>=" do - before :each do - @hash = {a:1, b:2} - @bigger = {a:1, b:2, c:3} - @unrelated = {c:3, d:4} - @similar = {a:2, b:3} - end +describe "Hash#>=" do + before :each do + @hash = {a:1, b:2} + @bigger = {a:1, b:2, c:3} + @unrelated = {c:3, d:4} + @similar = {a:2, b:3} + end - it "returns false when receiver size is smaller than argument" do - (@hash >= @bigger).should == false - (@unrelated >= @bigger).should == false - end + it "returns false when receiver size is smaller than argument" do + (@hash >= @bigger).should == false + (@unrelated >= @bigger).should == false + end - it "returns false when argument is not a subset or not equals to receiver" do - (@hash >= @unrelated).should == false - (@unrelated >= @hash).should == false - end + it "returns false when argument is not a subset or not equals to receiver" do + (@hash >= @unrelated).should == false + (@unrelated >= @hash).should == false + end - it "returns true when argument is a subset of receiver or equals to receiver" do - (@bigger >= @hash).should == true - (@hash >= @hash).should == true - end + it "returns true when argument is a subset of receiver or equals to receiver" do + (@bigger >= @hash).should == true + (@hash >= @hash).should == true + end - it "returns false when keys match but values don't" do - (@hash >= @similar).should == false - (@similar >= @hash).should == false - end + it "returns false when keys match but values don't" do + (@hash >= @similar).should == false + (@similar >= @hash).should == false end end diff --git a/spec/ruby/core/hash/has_key_spec.rb b/spec/ruby/core/hash/has_key_spec.rb index 1d2aa279f1..9a6244c6f6 100644 --- a/spec/ruby/core/hash/has_key_spec.rb +++ b/spec/ruby/core/hash/has_key_spec.rb @@ -1,8 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/key', __FILE__) +require_relative '../../spec_helper' describe "Hash#has_key?" do - it_behaves_like(:hash_key_p, :has_key?) + it "is an alias of Hash#include?" do + Hash.instance_method(:has_key?).should == Hash.instance_method(:include?) + end end - diff --git a/spec/ruby/core/hash/has_value_spec.rb b/spec/ruby/core/hash/has_value_spec.rb index dc8fdf9b69..d40e52ebfd 100644 --- a/spec/ruby/core/hash/has_value_spec.rb +++ b/spec/ruby/core/hash/has_value_spec.rb @@ -1,8 +1,16 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/value', __FILE__) +require_relative '../../spec_helper' describe "Hash#has_value?" do - it_behaves_like(:hash_value_p, :has_value?) -end + it "returns true if the value exists in the hash" do + { a: :b }.has_value?(:a).should == false + { 1 => 2 }.has_value?(2).should == true + h = Hash.new(5) + h.has_value?(5).should == false + h = Hash.new { 5 } + h.has_value?(5).should == false + end + it "uses == semantics for comparing values" do + { 5 => 2.0 }.has_value?(2).should == true + end +end diff --git a/spec/ruby/core/hash/hash_spec.rb b/spec/ruby/core/hash/hash_spec.rb index 9d1e984c60..cd67f7a652 100644 --- a/spec/ruby/core/hash/hash_spec.rb +++ b/spec/ruby/core/hash/hash_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Hash" do it "includes Enumerable" do @@ -11,6 +11,14 @@ describe "Hash#hash" do { 0=>2, 11=>1 }.hash.should == { 11=>1, 0=>2 }.hash end + it "returns a value in which element values do not cancel each other out" do + { a: 2, b: 2 }.hash.should_not == { a: 7, b: 7 }.hash + end + + it "returns a value in which element keys and values do not cancel each other out" do + { :a => :a }.hash.should_not == { :b => :b }.hash + end + it "generates a hash for recursive hash structures" do h = {} h[:a] = h @@ -33,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/include_spec.rb b/spec/ruby/core/hash/include_spec.rb index 8731673c19..1c32e9fb23 100644 --- a/spec/ruby/core/hash/include_spec.rb +++ b/spec/ruby/core/hash/include_spec.rb @@ -1,7 +1,40 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/key', __FILE__) +require_relative '../../spec_helper' describe "Hash#include?" do - it_behaves_like(:hash_key_p, :include?) + it "returns true if argument is a key" do + h = { a: 1, b: 2, c: 3, 4 => 0 } + h.include?(:a).should == true + h.include?(:b).should == true + h.include?(2).should == false + h.include?(4).should == true + + not_supported_on :opal do + h.include?('b').should == false + h.include?(4.0).should == false + end + end + + it "returns true if the key's matching value was nil" do + { xyz: nil }.include?(:xyz).should == true + end + + it "returns true if the key's matching value was false" do + { xyz: false }.include?(:xyz).should == true + end + + it "returns true if the key is nil" do + { nil => 'b' }.include?(nil).should == true + { nil => nil }.include?(nil).should == true + end + + it "compares keys with the same #hash value via #eql?" do + x = mock('x') + x.stub!(:hash).and_return(42) + + y = mock('y') + y.stub!(:hash).and_return(42) + y.should_receive(:eql?).and_return(false) + + { x => nil }.include?(y).should == false + 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 28e64f4eb8..0000000000 --- a/spec/ruby/core/hash/index_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/index', __FILE__) - -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 aa943d333d..cdba598813 100644 --- a/spec/ruby/core/hash/initialize_spec.rb +++ b/spec/ruby/core/hash/initialize_spec.rb @@ -1,40 +1,61 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash#initialize" do it "is private" do - Hash.should have_private_instance_method("initialize") + Hash.private_instance_methods(false).should.include?(:initialize) end it "can be used to reset default_proc" do h = { "foo" => 1, "bar" => 2 } h.default_proc.should == nil - h.instance_eval { initialize { |_, k| k * 2 } } + h.send(:initialize) { |_, k| k * 2 } h.default_proc.should_not == nil h["a"].should == "aa" end + it "can be used to reset the default value" do + h = {} + h.default = 42 + h.default.should == 42 + h.send(:initialize, 1) + h.default.should == 1 + h.send(:initialize) + h.default.should == nil + end + it "receives the arguments passed to Hash#new" do HashSpecs::NewHash.new(:one, :two)[0].should == :one HashSpecs::NewHash.new(:one, :two)[1].should == :two end + it "does not change the storage, only the default value or proc" do + h = HashSpecs::SubHashSettingInInitialize.new + h.to_a.should == [[:foo, :bar]] + + h = HashSpecs::SubHashSettingInInitialize.new(:default) + h.to_a.should == [[:foo, :bar]] + + h = HashSpecs::SubHashSettingInInitialize.new { :default_block } + h.to_a.should == [[:foo, :bar]] + end + it "returns self" do h = Hash.new - h.send(:initialize).should equal(h) + h.send(:initialize).should.equal?(h) end - it "raises a RuntimeError if called on a frozen instance" do - block = lambda { HashSpecs.frozen_hash.instance_eval { initialize() }} - block.should raise_error(RuntimeError) + it "raises a FrozenError if called on a frozen instance" do + block = -> { HashSpecs.frozen_hash.instance_eval { initialize() }} + block.should.raise(FrozenError) - block = lambda { HashSpecs.frozen_hash.instance_eval { initialize(nil) } } - block.should raise_error(RuntimeError) + block = -> { HashSpecs.frozen_hash.instance_eval { initialize(nil) } } + block.should.raise(FrozenError) - block = lambda { HashSpecs.frozen_hash.instance_eval { initialize(5) } } - block.should raise_error(RuntimeError) + block = -> { HashSpecs.frozen_hash.instance_eval { initialize(5) } } + block.should.raise(FrozenError) - block = lambda { HashSpecs.frozen_hash.instance_eval { initialize { 5 } } } - block.should raise_error(RuntimeError) + block = -> { HashSpecs.frozen_hash.instance_eval { initialize { 5 } } } + block.should.raise(FrozenError) end end diff --git a/spec/ruby/core/hash/inspect_spec.rb b/spec/ruby/core/hash/inspect_spec.rb index b8c42cf269..359b13360f 100644 --- a/spec/ruby/core/hash/inspect_spec.rb +++ b/spec/ruby/core/hash/inspect_spec.rb @@ -1,7 +1,123 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/to_s', __FILE__) +require_relative '../../spec_helper' describe "Hash#inspect" do - it_behaves_like :hash_to_s, :inspect + it "returns a string representation with same order as each()" do + h = { a: [1, 2], b: -2, d: -6, nil => nil } + 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.inspect.should == expected + end + + it "calls #inspect on keys and values" do + key = mock('key') + val = mock('val') + key.should_receive(:inspect).and_return('key') + val.should_receive(:inspect).and_return('val') + expected = ruby_version_is("3.4") ? "{key => val}" : "{key=>val}" + { key => val }.inspect.should == expected + end + + it "does not call #to_s on a String returned from #inspect" do + str = +"abc" + str.should_not_receive(:to_s) + expected = ruby_version_is("3.4") ? '{a: "abc"}' : '{:a=>"abc"}' + { a: str }.inspect.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") + expected = ruby_version_is("3.4") ? "{a: abc}" : "{:a=>abc}" + { a: obj }.inspect.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) + expected_pattern = ruby_version_is("3.4") ? /^\{a: #<MockObject:0x[0-9a-f]+>\}$/ : /^\{:a=>#<MockObject:0x[0-9a-f]+>\}$/ + { a: obj }.inspect.should =~ expected_pattern + end + + it "does not call #to_str on the object returned from #to_s when it is not a String" do + obj = mock("Hash#inspect/to_s does not call #to_str on #to_s result") + obj.should_receive(:inspect).and_return(obj) + obj.should_receive(:to_s).and_return(obj) + obj.should_not_receive(:to_str) + expected_pattern = ruby_version_is("3.4") ? /^\{a: #<MockObject:0x[0-9a-f]+>\}$/ : /^\{:a=>#<MockObject:0x[0-9a-f]+>\}$/ + { a: obj }.inspect.should =~ expected_pattern + end + + it "does not swallow exceptions raised by #to_s" do + obj = mock("Hash#inspect/to_s does not swallow #to_s exceptions") + obj.should_receive(:inspect).and_return(obj) + obj.should_receive(:to_s).and_raise(Exception) + + -> { { a: obj }.inspect }.should.raise(Exception) + end + + it "handles hashes with recursive values" do + x = {} + x[0] = x + expected = ruby_version_is("3.4") ? '{0 => {...}}' : '{0=>{...}}' + x.inspect.should == expected + + x = {} + y = {} + x[0] = y + y[1] = x + expected_x = ruby_version_is("3.4") ? '{0 => {1 => {...}}}' : '{0=>{1=>{...}}}' + expected_y = ruby_version_is("3.4") ? '{1 => {0 => {...}}}' : '{1=>{0=>{...}}}' + x.inspect.should == expected_x + y.inspect.should == expected_y + 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)) + expected = ruby_version_is("3.4") ? '{a: "utf_16be \u3042"}' : '{:a=>"utf_16be \u3042"}' + {a: utf_16be}.inspect.should == expected + end + + 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 }.inspect.should == expected + end + + ruby_version_is "3.4" do + it "adds quotes to symbol keys that are not valid symbol literals" do + { "needs-quotes": 1 }.inspect.should == '{"needs-quotes": 1}' + end + + it "can be evaled" do + no_quote = '{a: 1, a!: 1, a?: 1}' + eval(no_quote).inspect.should == no_quote + [ + '{"": 1}', + '{"0": 1, "!": 1, "%": 1, "&": 1, "*": 1, "+": 1, "-": 1, "/": 1, "<": 1, ">": 1, "^": 1, "`": 1, "|": 1, "~": 1}', + '{"@a": 1, "$a": 1, "+@": 1, "a=": 1, "[]": 1}', + '{"a\"b": 1, "@@a": 1, "<=>": 1, "===": 1, "[]=": 1}', + ].each do |quote| + eval(quote).inspect.should == quote + end + end + + it "can be evaled when Encoding.default_external is changed" do + external = Encoding.default_external + + Encoding.default_external = Encoding::ASCII + utf8_ascii_hash = '{"\\u3042": 1}' + eval(utf8_ascii_hash).inspect.should == utf8_ascii_hash + + Encoding.default_external = Encoding::UTF_8 + utf8_hash = "{\u3042: 1}" + eval(utf8_hash).inspect.should == utf8_hash + + Encoding.default_external = Encoding::Windows_31J + sjis_hash = "{\x87]: 1}".dup.force_encoding('sjis') + eval(sjis_hash).inspect.should == sjis_hash + ensure + Encoding.default_external = external + end + end end diff --git a/spec/ruby/core/hash/invert_spec.rb b/spec/ruby/core/hash/invert_spec.rb index bc2bf711f7..ea441751f2 100644 --- a/spec/ruby/core/hash/invert_spec.rb +++ b/spec/ruby/core/hash/invert_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash#invert" do it "returns a new hash where keys are values and vice versa" do @@ -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 == nil + h[:a] = 1 + h.invert.default.should == 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 == nil + h[:a] = 1 + h.invert.default_proc.should == 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 6b3a1925d3..c975efc5f8 100644 --- a/spec/ruby/core/hash/keep_if_spec.rb +++ b/spec/ruby/core/hash/keep_if_spec.rb @@ -1,7 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/iteration', __FILE__) -require File.expand_path('../../enumerable/shared/enumeratorized', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/iteration' +require_relative '../enumerable/shared/enumeratorized' describe "Hash#keep_if" do it "yields two arguments: key and value" do @@ -12,26 +12,26 @@ describe "Hash#keep_if" do it "keeps every entry for which block is true and returns self" do h = { a: 1, b: 2, c: 3, d: 4 } - h.keep_if { |k,v| v % 2 == 0 }.should equal(h) + h.keep_if { |k,v| v % 2 == 0 }.should.equal?(h) h.should == { b: 2, d: 4 } end it "removes all entries if the block is false" do h = { a: 1, b: 2, c: 3 } - h.keep_if { |k,v| false }.should equal(h) + h.keep_if { |k,v| false }.should.equal?(h) h.should == {} end it "returns self even if unmodified" do h = { 1 => 2, 3 => 4 } - h.keep_if { true }.should equal(h) + h.keep_if { true }.should.equal?(h) end - it "raises a RuntimeError if called on a frozen instance" do - lambda { HashSpecs.frozen_hash.keep_if { true } }.should raise_error(RuntimeError) - lambda { HashSpecs.empty_frozen_hash.keep_if { false } }.should raise_error(RuntimeError) + it "raises a FrozenError if called on a frozen instance" do + -> { HashSpecs.frozen_hash.keep_if { true } }.should.raise(FrozenError) + -> { HashSpecs.empty_frozen_hash.keep_if { false } }.should.raise(FrozenError) end - it_behaves_like(:hash_iteration_no_block, :keep_if) - it_behaves_like(:enumeratorized_with_origin_size, :keep_if, { 1 => 2, 3 => 4, 5 => 6 }) + it_behaves_like :hash_iteration_no_block, :keep_if + it_behaves_like :enumeratorized_with_origin_size, :keep_if, { 1 => 2, 3 => 4, 5 => 6 } end diff --git a/spec/ruby/core/hash/key_spec.rb b/spec/ruby/core/hash/key_spec.rb index dc78174641..c2d7049aed 100644 --- a/spec/ruby/core/hash/key_spec.rb +++ b/spec/ruby/core/hash/key_spec.rb @@ -1,12 +1,32 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/key', __FILE__) -require File.expand_path('../shared/index', __FILE__) +require_relative '../../spec_helper' describe "Hash#key?" do - it_behaves_like(:hash_key_p, :key?) + it "is an alias of Hash#include?" do + Hash.instance_method(:key?).should == Hash.instance_method(:include?) + end end describe "Hash#key" do - it_behaves_like(:hash_index, :key) + it "returns the corresponding key for value" do + { 2 => 'a', 1 => 'b' }.key('b').should == 1 + end + + it "returns nil if the value is not found" do + { a: -1, b: 3.14, c: 2.718 }.key(1).should == nil + end + + it "doesn't return default value if the value is not found" do + Hash.new(5).key(5).should == nil + end + + it "compares values using ==" do + { 1 => 0 }.key(0.0).should == 1 + { 1 => 0.0 }.key(0).should == 1 + + needle = mock('needle') + inhash = mock('inhash') + inhash.should_receive(:==).with(needle).and_return(true) + + { 1 => inhash }.key(needle).should == 1 + end end diff --git a/spec/ruby/core/hash/keys_spec.rb b/spec/ruby/core/hash/keys_spec.rb index bc7855f73b..789c413bee 100644 --- a/spec/ruby/core/hash/keys_spec.rb +++ b/spec/ruby/core/hash/keys_spec.rb @@ -1,15 +1,15 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash#keys" do it "returns an array with the keys in the order they were inserted" do {}.keys.should == [] - {}.keys.should be_kind_of(Array) + {}.keys.should.is_a?(Array) Hash.new(5).keys.should == [] Hash.new { 5 }.keys.should == [] { 1 => 2, 4 => 8, 2 => 4 }.keys.should == [1, 4, 2] - { 1 => 2, 2 => 4, 4 => 8 }.keys.should be_kind_of(Array) + { 1 => 2, 2 => 4, 4 => 8 }.keys.should.is_a?(Array) { nil => nil }.keys.should == [nil] end diff --git a/spec/ruby/core/hash/length_spec.rb b/spec/ruby/core/hash/length_spec.rb index 90751402eb..325973306f 100644 --- a/spec/ruby/core/hash/length_spec.rb +++ b/spec/ruby/core/hash/length_spec.rb @@ -1,7 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/length', __FILE__) +require_relative '../../spec_helper' describe "Hash#length" do - it_behaves_like(:hash_length, :length) + it "is an alias of Hash#size" do + Hash.instance_method(:size).should == Hash.instance_method(:length) + end end diff --git a/spec/ruby/core/hash/lt_spec.rb b/spec/ruby/core/hash/lt_spec.rb index c32af32017..4ca326d077 100644 --- a/spec/ruby/core/hash/lt_spec.rb +++ b/spec/ruby/core/hash/lt_spec.rb @@ -1,44 +1,42 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/comparison', __FILE__) -require File.expand_path('../shared/less_than', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/comparison' +require_relative 'shared/less_than' -ruby_version_is "2.3" do - describe "Hash#<" do - it_behaves_like :hash_comparison, :< - it_behaves_like :hash_less_than, :< +describe "Hash#<" do + it_behaves_like :hash_comparison, :< + it_behaves_like :hash_less_than, :< - it "returns false if both hashes are identical" do - h = { a: 1, b: 2 } - (h < h).should be_false - end + it "returns false if both hashes are identical" do + h = { a: 1, b: 2 } + (h < h).should == false end +end - describe "Hash#<" do - before :each do - @hash = {a:1, b:2} - @bigger = {a:1, b:2, c:3} - @unrelated = {c:3, d:4} - @similar = {a:2, b:3} - end +describe "Hash#<" do + before :each do + @hash = {a:1, b:2} + @bigger = {a:1, b:2, c:3} + @unrelated = {c:3, d:4} + @similar = {a:2, b:3} + end - it "returns false when receiver size is larger than argument" do - (@bigger < @hash).should == false - (@bigger < @unrelated).should == false - end + it "returns false when receiver size is larger than argument" do + (@bigger < @hash).should == false + (@bigger < @unrelated).should == false + end - it "returns false when receiver size is the same as argument" do - (@hash < @hash).should == false - (@hash < @unrelated).should == false - (@unrelated < @hash).should == false - end + it "returns false when receiver size is the same as argument" do + (@hash < @hash).should == false + (@hash < @unrelated).should == false + (@unrelated < @hash).should == false + end - it "returns true when receiver is a subset of argument" do - (@hash < @bigger).should == true - end + it "returns true when receiver is a subset of argument" do + (@hash < @bigger).should == true + end - it "returns false when keys match but values don't" do - (@hash < @similar).should == false - (@similar < @hash).should == false - end + it "returns false when keys match but values don't" do + (@hash < @similar).should == false + (@similar < @hash).should == false end end diff --git a/spec/ruby/core/hash/lte_spec.rb b/spec/ruby/core/hash/lte_spec.rb index 1a6926a44a..29e60273d1 100644 --- a/spec/ruby/core/hash/lte_spec.rb +++ b/spec/ruby/core/hash/lte_spec.rb @@ -1,44 +1,42 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/comparison', __FILE__) -require File.expand_path('../shared/less_than', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/comparison' +require_relative 'shared/less_than' -ruby_version_is "2.3" do - describe "Hash#<=" do - it_behaves_like :hash_comparison, :<= - it_behaves_like :hash_less_than, :<= +describe "Hash#<=" do + it_behaves_like :hash_comparison, :<= + it_behaves_like :hash_less_than, :<= - it "returns true if both hashes are identical" do - h = { a: 1, b: 2 } - (h <= h).should be_true - end + it "returns true if both hashes are identical" do + h = { a: 1, b: 2 } + (h <= h).should == true end +end - describe "Hash#<=" do - before :each do - @hash = {a:1, b:2} - @bigger = {a:1, b:2, c:3} - @unrelated = {c:3, d:4} - @similar = {a:2, b:3} - end +describe "Hash#<=" do + before :each do + @hash = {a:1, b:2} + @bigger = {a:1, b:2, c:3} + @unrelated = {c:3, d:4} + @similar = {a:2, b:3} + end - it "returns false when receiver size is larger than argument" do - (@bigger <= @hash).should == false - (@bigger <= @unrelated).should == false - end + it "returns false when receiver size is larger than argument" do + (@bigger <= @hash).should == false + (@bigger <= @unrelated).should == false + end - it "returns false when receiver size is the same as argument" do - (@hash <= @unrelated).should == false - (@unrelated <= @hash).should == false - end + it "returns false when receiver size is the same as argument" do + (@hash <= @unrelated).should == false + (@unrelated <= @hash).should == false + end - it "returns true when receiver is a subset of argument or equals to argument" do - (@hash <= @bigger).should == true - (@hash <= @hash).should == true - end + it "returns true when receiver is a subset of argument or equals to argument" do + (@hash <= @bigger).should == true + (@hash <= @hash).should == true + end - it "returns false when keys match but values don't" do - (@hash <= @similar).should == false - (@similar <= @hash).should == false - end + it "returns false when keys match but values don't" do + (@hash <= @similar).should == false + (@similar <= @hash).should == false end end diff --git a/spec/ruby/core/hash/member_spec.rb b/spec/ruby/core/hash/member_spec.rb index 376bb4c006..e7309c3f7d 100644 --- a/spec/ruby/core/hash/member_spec.rb +++ b/spec/ruby/core/hash/member_spec.rb @@ -1,7 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/key', __FILE__) +require_relative '../../spec_helper' describe "Hash#member?" do - it_behaves_like(:hash_key_p, :member?) + it "is an alias of Hash#include?" do + Hash.instance_method(:member?).should == Hash.instance_method(:include?) + end end diff --git a/spec/ruby/core/hash/merge_spec.rb b/spec/ruby/core/hash/merge_spec.rb index 21401ffd08..9e566fcee9 100644 --- a/spec/ruby/core/hash/merge_spec.rb +++ b/spec/ruby/core/hash/merge_spec.rb @@ -1,7 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/iteration', __FILE__) -require File.expand_path('../shared/update', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash#merge" do it "returns a new hash by combining self with the contents of other" do @@ -28,9 +26,9 @@ 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) + }.should.raise(IndexError) r = h1.merge(h1) { |k,x,y| :x } r.should == { a: :x, b: :x, d: :x } @@ -47,8 +45,8 @@ describe "Hash#merge" do end it "returns subclass instance for subclasses" do - HashSpecs::MyHash[1 => 2, 3 => 4].merge({ 1 => 2 }).should be_an_instance_of(HashSpecs::MyHash) - HashSpecs::MyHash[].merge({ 1 => 2 }).should be_an_instance_of(HashSpecs::MyHash) + HashSpecs::MyHash[1 => 2, 3 => 4].merge({ 1 => 2 }).should.instance_of?(HashSpecs::MyHash) + HashSpecs::MyHash[].merge({ 1 => 2 }).should.instance_of?(HashSpecs::MyHash) { 1 => 2, 3 => 4 }.merge(HashSpecs::MyHash[1 => 2]).class.should == Hash {}.merge(HashSpecs::MyHash[1 => 2]).class.should == Hash @@ -63,15 +61,63 @@ describe "Hash#merge" do merge_pairs.should == each_pairs end + it "preserves the order of merged elements" do + h1 = { 1 => 2, 3 => 4, 5 => 6 } + h2 = { 1 => 7 } + merge_pairs = [] + h1.merge(h2).each_pair { |k, v| merge_pairs << [k, v] } + merge_pairs.should == [[1,7], [3, 4], [5, 6]] + end + + it "preserves the order of merged elements for large hashes" do + h1 = {} + h2 = {} + merge_pairs = [] + expected_pairs = [] + (1..100).each { |x| h1[x] = x; h2[101 - x] = x; expected_pairs << [x, 101 - x] } + h1.merge(h2).each_pair { |k, v| merge_pairs << [k, v] } + merge_pairs.should == expected_pairs + 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 + + 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 + + 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 describe "Hash#merge!" do - it_behaves_like(:hash_update, :merge!) - - it "does not raise an exception if changing the value of an existing key during iteration" do - hash = {1 => 2, 3 => 4, 5 => 6} - hash2 = {1 => :foo, 3 => :bar} - hash.each { hash.merge!(hash2) } - hash.should == {1 => :foo, 3 => :bar, 5 => 6} + it "is an alias of Hash#update" do + Hash.instance_method(:merge!).should == Hash.instance_method(:update) end end diff --git a/spec/ruby/core/hash/new_spec.rb b/spec/ruby/core/hash/new_spec.rb index c31cc2abf4..207fc2931f 100644 --- a/spec/ruby/core/hash/new_spec.rb +++ b/spec/ruby/core/hash/new_spec.rb @@ -1,6 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +# Actually these are specs of Hash#initialize, there is no Hash.new it's just Class.new. describe "Hash.new" do it "creates an empty Hash if passed no arguments" do Hash.new.should == {} @@ -14,7 +15,7 @@ describe "Hash.new" do it "does not create a copy of the default argument" do str = "foo" - Hash.new(str).default.should equal(str) + Hash.new(str).default.should.equal?(str) end it "creates a Hash with a default_proc if passed a block" do @@ -26,11 +27,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(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(ArgumentError) + -> { Hash.new(nil) { 0 } }.should.raise(ArgumentError) + end + + ruby_version_is ""..."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(ArgumentError) + -> { Hash.new(unknown: true) { 0 } }.should.raise(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 + end + + it "raises an error if unknown keyword arguments are passed" do + -> { Hash.new(unknown: true) }.should.raise(ArgumentError) + -> { Hash.new(1, unknown: true) }.should.raise(ArgumentError) + -> { Hash.new(unknown: true) { 0 } }.should.raise(ArgumentError) + end end end diff --git a/spec/ruby/core/hash/rassoc_spec.rb b/spec/ruby/core/hash/rassoc_spec.rb index 79c633f95e..73379f6867 100644 --- a/spec/ruby/core/hash/rassoc_spec.rb +++ b/spec/ruby/core/hash/rassoc_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Hash#rassoc" do before :each do @@ -6,7 +6,7 @@ describe "Hash#rassoc" do end it "returns an Array if the argument is a value of the Hash" do - @h.rassoc(:green).should be_an_instance_of(Array) + @h.rassoc(:green).should.instance_of?(Array) end it "returns a 2-element Array if the argument is a value of the Hash" do @@ -28,15 +28,15 @@ describe "Hash#rassoc" do it "uses #== to compare the argument to the values" do @h[:key] = 1.0 1.should == 1.0 - @h.rassoc(1).should eql [:key, 1.0] + @h.rassoc(1).should.eql? [:key, 1.0] end it "returns nil if the argument is not a value of the Hash" do - @h.rassoc(:banana).should be_nil + @h.rassoc(:banana).should == nil end it "returns nil if the argument is not a value of the Hash even when there is a default" do - Hash.new(42).merge!( foo: :bar ).rassoc(42).should be_nil - Hash.new{|h, k| h[k] = 42}.merge!( foo: :bar ).rassoc(42).should be_nil + Hash.new(42).merge!( foo: :bar ).rassoc(42).should == nil + Hash.new{|h, k| h[k] = 42}.merge!( foo: :bar ).rassoc(42).should == nil end end diff --git a/spec/ruby/core/hash/rehash_spec.rb b/spec/ruby/core/hash/rehash_spec.rb index 09315737b6..fcd5a037bd 100644 --- a/spec/ruby/core/hash/rehash_spec.rb +++ b/spec/ruby/core/hash/rehash_spec.rb @@ -1,22 +1,31 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +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.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') @@ -35,8 +44,71 @@ describe "Hash#rehash" do h[k2].should == v2 end - it "raises a RuntimeError if called on a frozen instance" do - lambda { HashSpecs.frozen_hash.rehash }.should raise_error(RuntimeError) - lambda { HashSpecs.empty_frozen_hash.rehash }.should raise_error(RuntimeError) + it "removes duplicate keys" do + a = [1,2] + b = [1] + + h = {} + h[a] = true + h[b] = true + b << 2 + h.size.should == 2 + h.keys.should == [a, b] + h.rehash + h.size.should == 1 + h.keys.should == [a] + end + + 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(FrozenError) + -> { HashSpecs.empty_frozen_hash.rehash }.should.raise(FrozenError) end end diff --git a/spec/ruby/core/hash/reject_spec.rb b/spec/ruby/core/hash/reject_spec.rb index 21dd7425aa..aa1a074897 100644 --- a/spec/ruby/core/hash/reject_spec.rb +++ b/spec/ruby/core/hash/reject_spec.rb @@ -1,7 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/iteration', __FILE__) -require File.expand_path('../../enumerable/shared/enumeratorized', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/iteration' +require_relative '../enumerable/shared/enumeratorized' describe "Hash#reject" do it "returns a new hash removing keys for which the block yields true" do @@ -28,13 +28,8 @@ describe "Hash#reject" do context "with extra state" do it "returns Hash instance for subclasses" 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 + HashSpecs::MyHash[1 => 2, 3 => 4].reject { false }.should.is_a?(Hash) + HashSpecs::MyHash[1 => 2, 3 => 4].reject { true }.should.is_a?(Hash) end end @@ -49,8 +44,29 @@ describe "Hash#reject" do reject_pairs.should == reject_bang_pairs end - it_behaves_like(:hash_iteration_no_block, :reject) - it_behaves_like(:enumeratorized_with_origin_size, :reject, { 1 => 2, 3 => 4, 5 => 6 }) + it "does not retain the default value" do + h = Hash.new(1) + h.reject { false }.default.should == nil + h[:a] = 1 + h.reject { false }.default.should == 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 == nil + h[:a] = 1 + h.reject { false }.default_proc.should == 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 describe "Hash#reject!" do @@ -63,7 +79,7 @@ describe "Hash#reject!" do it "removes all entries if the block is true" do h = { a: 1, b: 2, c: 3 } - h.reject! { |k,v| true }.should equal(h) + h.reject! { |k,v| true }.should.equal?(h) h.should == {} end @@ -87,14 +103,14 @@ describe "Hash#reject!" do reject_bang_pairs.should == delete_if_pairs end - it "raises a RuntimeError if called on a frozen instance that is modified" do - lambda { HashSpecs.empty_frozen_hash.reject! { true } }.should raise_error(RuntimeError) + it "raises a FrozenError if called on a frozen instance that is modified" do + -> { HashSpecs.empty_frozen_hash.reject! { true } }.should.raise(FrozenError) end - it "raises a RuntimeError if called on a frozen instance that would not be modified" do - lambda { HashSpecs.frozen_hash.reject! { false } }.should raise_error(RuntimeError) + it "raises a FrozenError if called on a frozen instance that would not be modified" do + -> { HashSpecs.frozen_hash.reject! { false } }.should.raise(FrozenError) end - it_behaves_like(:hash_iteration_no_block, :reject!) - it_behaves_like(:enumeratorized_with_origin_size, :reject!, { 1 => 2, 3 => 4, 5 => 6 }) + it_behaves_like :hash_iteration_no_block, :reject! + it_behaves_like :enumeratorized_with_origin_size, :reject!, { 1 => 2, 3 => 4, 5 => 6 } end diff --git a/spec/ruby/core/hash/replace_spec.rb b/spec/ruby/core/hash/replace_spec.rb index 61b3164355..4dacbf9779 100644 --- a/spec/ruby/core/hash/replace_spec.rb +++ b/spec/ruby/core/hash/replace_spec.rb @@ -1,7 +1,79 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/replace', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' 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 == 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 == 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(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(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..609a53f81f --- /dev/null +++ b/spec/ruby/core/hash/ruby2_keywords_hash_spec.rb @@ -0,0 +1,81 @@ +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(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(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 + + 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 diff --git a/spec/ruby/core/hash/select_spec.rb b/spec/ruby/core/hash/select_spec.rb index 449607b606..60b2ce67c1 100644 --- a/spec/ruby/core/hash/select_spec.rb +++ b/spec/ruby/core/hash/select_spec.rb @@ -1,7 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/iteration', __FILE__) -require File.expand_path('../../enumerable/shared/enumeratorized', __FILE__) +require_relative '../../spec_helper' +require_relative '../enumerable/shared/enumeratorized' +require_relative 'fixtures/classes' +require_relative 'shared/iteration' describe "Hash#select" do before :each do @@ -17,7 +17,7 @@ describe "Hash#select" do it "returns a Hash of entries for which block is true" do a_pairs = { 'a' => 9, 'c' => 4, 'b' => 5, 'd' => 2 }.select { |k,v| v % 2 == 0 } - a_pairs.should be_an_instance_of(Hash) + a_pairs.should.instance_of?(Hash) a_pairs.sort.should == [['c', 4], ['d', 2]] end @@ -26,22 +26,47 @@ describe "Hash#select" do select_pairs = [] reject_pairs = [] - h.dup.select { |*pair| select_pairs << pair } + h.dup.select{ |*pair| select_pairs << pair } h.reject { |*pair| reject_pairs << pair } select_pairs.should == reject_pairs end it "returns an Enumerator when called on a non-empty hash without a block" do - @hsh.select.should be_an_instance_of(Enumerator) + @hsh.select.should.instance_of?(Enumerator) end it "returns an Enumerator when called on an empty hash without a block" do - @empty.select.should be_an_instance_of(Enumerator) + @empty.select.should.instance_of?(Enumerator) end - it_behaves_like(:hash_iteration_no_block, :select) - it_behaves_like(:enumeratorized_with_origin_size, :select, { 1 => 2, 3 => 4, 5 => 6 }) + it "does not retain the default value" do + h = Hash.new(1) + h.select { true }.default.should == nil + h[:a] = 1 + h.select { true }.default.should == nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.select { true }.default_proc.should == nil + h[:a] = 1 + h.select { true }.default_proc.should == nil + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.select { |k, _| k == :a } + h2.compare_by_identity?.should == true + end + + it_behaves_like :hash_iteration_no_block, :select + + before :each do + @object = { 1 => 2, 3 => 4, 5 => 6 } + end + it_behaves_like :enumeratorized_with_origin_size, :select end describe "Hash#select!" do @@ -52,7 +77,7 @@ describe "Hash#select!" do it "is equivalent to keep_if if changes are made" do h = { a: 2 } - h.select! { |k,v| v <= 1 }.should equal h + h.select! { |k,v| v <= 1 }.should.equal? h h = { 1 => 2, 3 => 4 } all_args_select = [] @@ -62,7 +87,7 @@ describe "Hash#select!" do it "removes all entries if the block is false" do h = { a: 1, b: 2, c: 3 } - h.select! { |k,v| false }.should equal(h) + h.select! { |k,v| false }.should.equal?(h) h.should == {} end @@ -70,14 +95,18 @@ describe "Hash#select!" do { a: 1 }.select! { |k,v| v <= 1 }.should == nil end - it "raises a RuntimeError if called on an empty frozen instance" do - lambda { HashSpecs.empty_frozen_hash.select! { false } }.should raise_error(RuntimeError) + it "raises a FrozenError if called on an empty frozen instance" do + -> { HashSpecs.empty_frozen_hash.select! { false } }.should.raise(FrozenError) end - it "raises a RuntimeError if called on a frozen instance that would not be modified" do - lambda { HashSpecs.frozen_hash.select! { true } }.should raise_error(RuntimeError) + it "raises a FrozenError if called on a frozen instance that would not be modified" do + -> { HashSpecs.frozen_hash.select! { true } }.should.raise(FrozenError) end - it_behaves_like(:hash_iteration_no_block, :select!) - it_behaves_like(:enumeratorized_with_origin_size, :select!, { 1 => 2, 3 => 4, 5 => 6 }) + it_behaves_like :hash_iteration_no_block, :select! + + before :each do + @object = { 1 => 2, 3 => 4, 5 => 6 } + end + it_behaves_like :enumeratorized_with_origin_size, :select! end diff --git a/spec/ruby/core/hash/shared/comparison.rb b/spec/ruby/core/hash/shared/comparison.rb index bbb9bfd6ad..2b62837232 100644 --- a/spec/ruby/core/hash/shared/comparison.rb +++ b/spec/ruby/core/hash/shared/comparison.rb @@ -1,15 +1,15 @@ 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(TypeError) + -> { { a: 1 }.send(@method, nil) }.should.raise(TypeError) + -> { { a: 1 }.send(@method, []) }.should.raise(TypeError) end it "returns false if both hashes have the same keys but different values" do h1 = { a: 1 } h2 = { a: 2 } - h1.send(@method, h2).should be_false - h2.send(@method, h1).should be_false + h1.send(@method, h2).should == false + h2.send(@method, h1).should == false end end diff --git a/spec/ruby/core/hash/shared/each.rb b/spec/ruby/core/hash/shared/each.rb deleted file mode 100644 index bf4c569cfc..0000000000 --- a/spec/ruby/core/hash/shared/each.rb +++ /dev/null @@ -1,68 +0,0 @@ -describe :hash_each, shared: true do - it "yields a [[key, value]] Array for each pair to a block expecting |*args|" do - all_args = [] - { 1 => 2, 3 => 4 }.send(@method) { |*args| all_args << args } - all_args.sort.should == [[[1, 2]], [[3, 4]]] - end - - it "yields the key and value of each pair to a block expecting |key, value|" do - r = {} - h = { a: 1, b: 2, c: 3, d: 5 } - h.send(@method) { |k,v| r[k.to_s] = v.to_s }.should equal(h) - r.should == { "a" => "1", "b" => "2", "c" => "3", "d" => "5" } - end - - it "yields the key only to a block expecting |key,|" do - ary = [] - h = { "a" => 1, "b" => 2, "c" => 3 } - h.send(@method) { |k,| ary << k } - ary.sort.should == ["a", "b", "c"] - end - - it "uses the same order as keys() and values()" do - h = { a: 1, b: 2, c: 3, d: 5 } - keys = [] - values = [] - - h.send(@method) do |k, v| - keys << k - values << v - end - - keys.should == h.keys - values.should == h.values - end - - # Confirming the argument-splatting works from child class for both k, v and [k, v] - it "properly expands (or not) child class's 'each'-yielded args" do - cls1 = Class.new(Hash) do - attr_accessor :k_v - def each - super do |k, v| - @k_v = [k, v] - yield k, v - end - end - end - - cls2 = Class.new(Hash) do - attr_accessor :k_v - def each - super do |k, v| - @k_v = [k, v] - yield([k, v]) - end - end - end - - obj1 = cls1.new - obj1['a'] = 'b' - obj1.map {|k, v| [k, v]}.should == [['a', 'b']] - obj1.k_v.should == ['a', 'b'] - - obj2 = cls2.new - obj2['a'] = 'b' - obj2.map {|k, v| [k, v]}.should == [['a', 'b']] - obj2.k_v.should == ['a', 'b'] - end -end diff --git a/spec/ruby/core/hash/shared/eql.rb b/spec/ruby/core/hash/shared/eql.rb index 1aed5f51fb..512e1ad016 100644 --- a/spec/ruby/core/hash/shared/eql.rb +++ b/spec/ruby/core/hash/shared/eql.rb @@ -3,7 +3,7 @@ describe :hash_eql, shared: true do value = mock('x') value.should_not_receive(:==) value.should_not_receive(:eql?) - { 1 => value }.send(@method, { 2 => value }).should be_false + { 1 => value }.send(@method, { 2 => value }).should == false end it "returns false when the numbers of keys differ without comparing any elements" do @@ -13,8 +13,8 @@ describe :hash_eql, shared: true do obj.should_not_receive(:==) obj.should_not_receive(:eql?) - {}.send(@method, h).should be_false - h.send(@method, {}).should be_false + {}.send(@method, h).should == false + h.send(@method, {}).should == false end it "first compares keys via hash" do @@ -23,7 +23,7 @@ describe :hash_eql, shared: true do y = mock('y') y.should_receive(:hash).any_number_of_times.and_return(0) - { x => 1 }.send(@method, { y => 1 }).should be_false + { x => 1 }.send(@method, { y => 1 }).should == false end it "does not compare keys with different hash codes via eql?" do @@ -35,39 +35,39 @@ describe :hash_eql, shared: true do x.should_receive(:hash).any_number_of_times.and_return(0) y.should_receive(:hash).any_number_of_times.and_return(1) - { x => 1 }.send(@method, { y => 1 }).should be_false + { x => 1 }.send(@method, { y => 1 }).should == 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 + h.send(@method, h[:a]).should == true + (h == h[:a]).should == true end it "doesn't call to_hash on objects" do mock_hash = mock("fake hash") def mock_hash.to_hash() {} end - {}.send(@method, mock_hash).should be_false + {}.send(@method, mock_hash).should == false 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! + a.send(@method, b).should == 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! + c.send(@method, a).should == 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.send(@method, a).should == false # not quite the same structure, as a[:other][:delta] = nil c[:delta] = 42 - c.send(@method, a).should be_false + c.send(@method, a).should == false a[:delta] = 42 - c.send(@method, a).should be_false + c.send(@method, a).should == false b[:delta] = 42 - c.send(@method, a).should be_true + c.send(@method, a).should == true end it "computes equality for recursive hashes & arrays" do @@ -76,19 +76,19 @@ describe :hash_eql, shared: true do 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 + b.send(@method, c).should == true # they clearly have the same structure! + y.send(@method, z).should == true + a.send(@method, b).should == true # subtle, but they both have the same structure! + x.send(@method, y).should == true y << x - y.send(@method, z).should be_false + y.send(@method, z).should == false z << x - y.send(@method, z).should be_true + y.send(@method, z).should == true a[:foo], a[:bar] = a[:bar], a[:foo] - a.send(@method, b).should be_false + a.send(@method, b).should == false b[:bar] = b[:foo] - b.send(@method, c).should be_false + b.send(@method, c).should == false end end @@ -100,7 +100,7 @@ describe :hash_eql_additional, shared: true do def y.==(o) false end def x.eql?(o) false end def y.eql?(o) false end - { 1 => x }.send(@method, { 1 => y }).should be_false + { 1 => x }.send(@method, { 1 => y }).should == false x = mock('x') y = mock('y') @@ -108,45 +108,45 @@ describe :hash_eql_additional, shared: true do def y.==(o) true end def x.eql?(o) true end def y.eql?(o) true end - { 1 => x }.send(@method, { 1 => y }).should be_true + { 1 => x }.send(@method, { 1 => y }).should == true end it "compares keys with eql? semantics" do - { 1.0 => "x" }.send(@method, { 1.0 => "x" }).should be_true - { 1.0 => "x" }.send(@method, { 1.0 => "x" }).should be_true - { 1 => "x" }.send(@method, { 1.0 => "x" }).should be_false - { 1.0 => "x" }.send(@method, { 1 => "x" }).should be_false + { 1.0 => "x" }.send(@method, { 1.0 => "x" }).should == true + { 1.0 => "x" }.send(@method, { 1.0 => "x" }).should == true + { 1 => "x" }.send(@method, { 1.0 => "x" }).should == false + { 1.0 => "x" }.send(@method, { 1 => "x" }).should == 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 + a.send(@method, b).should == false b[:a] = 5 - a.send(@method, b).should be_true + a.send(@method, b).should == true not_supported_on :opal do c = { "a" => 5 } - a.send(@method, c).should be_false + a.send(@method, c).should == false end c = { "A" => 5 } - a.send(@method, c).should be_false + a.send(@method, c).should == false c = { a: 6 } - a.send(@method, c).should be_false + a.send(@method, c).should == false end it "does not call to_hash on hash subclasses" do - { 5 => 6 }.send(@method, HashSpecs::ToHashHash[5 => 6]).should be_true + { 5 => 6 }.send(@method, HashSpecs::ToHashHash[5 => 6]).should == true end it "ignores hash class differences" do h = { 1 => 2, 3 => 4 } - HashSpecs::MyHash[h].send(@method, h).should be_true - HashSpecs::MyHash[h].send(@method, HashSpecs::MyHash[h]).should be_true - h.send(@method, HashSpecs::MyHash[h]).should be_true + HashSpecs::MyHash[h].send(@method, h).should == true + HashSpecs::MyHash[h].send(@method, HashSpecs::MyHash[h]).should == true + h.send(@method, HashSpecs::MyHash[h]).should == true end # Why isn't this true of eql? too ? @@ -155,40 +155,28 @@ 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 obj 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[0] => 1 }.send(@method, { a[1] => 1 }).should == false 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 obj end - { a[0] => 1 }.send(@method, { a[1] => 1 }).should be_true - a[0].tainted?.should be_true - a[1].tainted?.should be_true + { a[0] => 1 }.send(@method, { a[1] => 1 }).should == true end it "compares the values in self to values in other hash" do @@ -197,20 +185,20 @@ describe :hash_eql_additional, shared: true do l_val.should_receive(:eql?).with(r_val).and_return(true) - { 1 => l_val }.eql?({ 1 => r_val }).should be_true + { 1 => l_val }.eql?({ 1 => r_val }).should == true end end describe :hash_eql_additional_more, shared: true do it "returns true if other Hash has the same number of keys and each key-value pair matches, even though the default-value are not same" do - Hash.new(5).send(@method, Hash.new(1)).should be_true - Hash.new {|h, k| 1}.send(@method, Hash.new {}).should be_true - Hash.new {|h, k| 1}.send(@method, Hash.new(2)).should be_true + Hash.new(5).send(@method, Hash.new(1)).should == true + Hash.new {|h, k| 1}.send(@method, Hash.new {}).should == true + Hash.new {|h, k| 1}.send(@method, Hash.new(2)).should == true d = Hash.new {|h, k| 1} e = Hash.new {} d[1] = 2 e[1] = 2 - d.send(@method, e).should be_true + d.send(@method, e).should == true end end 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/greater_than.rb b/spec/ruby/core/hash/shared/greater_than.rb index 1f8b9fcfb7..dfe0b80250 100644 --- a/spec/ruby/core/hash/shared/greater_than.rb +++ b/spec/ruby/core/hash/shared/greater_than.rb @@ -5,11 +5,11 @@ describe :hash_greater_than, shared: true do end it "returns true if the other hash is a subset of self" do - @h1.send(@method, @h2).should be_true + @h1.send(@method, @h2).should == true end it "returns false if the other hash is not a subset of self" do - @h2.send(@method, @h1).should be_false + @h2.send(@method, @h1).should == false end it "converts the right operand to a hash before comparing" do @@ -18,6 +18,6 @@ describe :hash_greater_than, shared: true do { a: 1, b: 2 } end - @h1.send(@method, o).should be_true + @h1.send(@method, o).should == true end end diff --git a/spec/ruby/core/hash/shared/index.rb b/spec/ruby/core/hash/shared/index.rb deleted file mode 100644 index 9aa1b2a46f..0000000000 --- a/spec/ruby/core/hash/shared/index.rb +++ /dev/null @@ -1,27 +0,0 @@ -require File.expand_path('../../../../spec_helper', __FILE__) -require File.expand_path('../../fixtures/classes', __FILE__) - -describe :hash_index, shared: true do - it "returns the corresponding key for value" do - { 2 => 'a', 1 => 'b' }.send(@method, 'b').should == 1 - 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 - end - - it "doesn't return default value if the value is not found" do - Hash.new(5).send(@method, 5).should be_nil - end - - it "compares values using ==" do - { 1 => 0 }.send(@method, 0.0).should == 1 - { 1 => 0.0 }.send(@method, 0).should == 1 - - needle = mock('needle') - inhash = mock('inhash') - inhash.should_receive(:==).with(needle).and_return(true) - - { 1 => inhash }.send(@method, needle).should == 1 - end -end diff --git a/spec/ruby/core/hash/shared/iteration.rb b/spec/ruby/core/hash/shared/iteration.rb index d27c2443f8..322e4b9a2f 100644 --- a/spec/ruby/core/hash/shared/iteration.rb +++ b/spec/ruby/core/hash/shared/iteration.rb @@ -5,15 +5,15 @@ describe :hash_iteration_no_block, shared: true do end it "returns an Enumerator if called on a non-empty hash without a block" do - @hsh.send(@method).should be_an_instance_of(Enumerator) + @hsh.send(@method).should.instance_of?(Enumerator) end it "returns an Enumerator if called on an empty hash without a block" do - @empty.send(@method).should be_an_instance_of(Enumerator) + @empty.send(@method).should.instance_of?(Enumerator) end it "returns an Enumerator if called on a frozen instance" do @hsh.freeze - @hsh.send(@method).should be_an_instance_of(Enumerator) + @hsh.send(@method).should.instance_of?(Enumerator) end end diff --git a/spec/ruby/core/hash/shared/key.rb b/spec/ruby/core/hash/shared/key.rb deleted file mode 100644 index 17f9f81457..0000000000 --- a/spec/ruby/core/hash/shared/key.rb +++ /dev/null @@ -1,38 +0,0 @@ -describe :hash_key_p, shared: true do - it "returns true if argument is a key" do - h = { a: 1, b: 2, c: 3, 4 => 0 } - h.send(@method, :a).should == true - h.send(@method, :b).should == true - h.send(@method, 2).should == false - h.send(@method, 4).should == true - - not_supported_on :opal do - h.send(@method, 'b').should == false - h.send(@method, 4.0).should == false - end - end - - it "returns true if the key's matching value was nil" do - { xyz: nil }.send(@method, :xyz).should == true - end - - it "returns true if the key's matching value was false" do - { xyz: false }.send(@method, :xyz).should == true - end - - it "returns true if the key is nil" do - { nil => 'b' }.send(@method, nil).should == true - { nil => nil }.send(@method, nil).should == true - end - - it "compares keys with the same #hash value via #eql?" do - x = mock('x') - x.stub!(:hash).and_return(42) - - y = mock('y') - y.stub!(:hash).and_return(42) - y.should_receive(:eql?).and_return(false) - - { x => nil }.send(@method, y).should == false - end -end diff --git a/spec/ruby/core/hash/shared/length.rb b/spec/ruby/core/hash/shared/length.rb deleted file mode 100644 index 24f5563759..0000000000 --- a/spec/ruby/core/hash/shared/length.rb +++ /dev/null @@ -1,12 +0,0 @@ -describe :hash_length, shared: true do - it "returns the number of entries" do - { a: 1, b: 'c' }.send(@method).should == 2 - h = { a: 1, b: 2 } - h[:a] = 2 - h.send(@method).should == 2 - { a: 1, b: 1, c: 1 }.send(@method).should == 3 - {}.send(@method).should == 0 - Hash.new(5).send(@method).should == 0 - Hash.new { 5 }.send(@method).should == 0 - end -end diff --git a/spec/ruby/core/hash/shared/less_than.rb b/spec/ruby/core/hash/shared/less_than.rb index cdc6f14546..071b3e97bb 100644 --- a/spec/ruby/core/hash/shared/less_than.rb +++ b/spec/ruby/core/hash/shared/less_than.rb @@ -5,11 +5,11 @@ describe :hash_less_than, shared: true do end it "returns true if self is a subset of the other hash" do - @h1.send(@method, @h2).should be_true + @h1.send(@method, @h2).should == true end it "returns false if self is not a subset of the other hash" do - @h2.send(@method, @h1).should be_false + @h2.send(@method, @h1).should == false end it "converts the right operand to a hash before comparing" do @@ -18,6 +18,6 @@ describe :hash_less_than, shared: true do { a: 1, b: 2, c: 3 } end - @h1.send(@method, o).should be_true + @h1.send(@method, o).should == true 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 463c861395..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 RuntimeError 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(RuntimeError) - end - - it "raises a RuntimeError 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(RuntimeError) - end -end diff --git a/spec/ruby/core/hash/shared/store.rb b/spec/ruby/core/hash/shared/store.rb deleted file mode 100644 index b43dcbc93e..0000000000 --- a/spec/ruby/core/hash/shared/store.rb +++ /dev/null @@ -1,98 +0,0 @@ -require File.expand_path('../../fixtures/classes', __FILE__) - -describe :hash_store, shared: true do - it "associates the key with the value and return the value" do - h = { a: 1 } - h.send(@method, :b, 2).should == 2 - h.should == { b:2, a:1 } - end - - it "duplicates string keys using dup semantics" do - # dup doesn't copy singleton methods - key = "foo" - def key.reverse() "bar" end - h = {} - h.send(@method, key, 0) - h.keys[0].reverse.should == "oof" - end - - it "stores unequal keys that hash to the same value" do - h = {} - k1 = ["x"] - k2 = ["y"] - # So they end up in the same bucket - k1.should_receive(:hash).and_return(0) - k2.should_receive(:hash).and_return(0) - - h.send(@method, k1, 1) - h.send(@method, k2, 2) - h.size.should == 2 - end - - it "accepts keys with private #hash method" do - key = HashSpecs::KeyWithPrivateHash.new - h = {} - h.send(@method, key, "foo") - h[key].should == "foo" - end - - it " accepts keys with a Bignum hash" do - o = mock(hash: 1 << 100) - h = {} - h[o] = 1 - h[o].should == 1 - end - - it "duplicates and freezes string keys" do - key = "foo" - h = {} - h.send(@method, key, 0) - key << "bar" - - h.should == { "foo" => 0 } - h.keys[0].frozen?.should == true - end - - it "doesn't duplicate and freeze already frozen string keys" do - key = "foo".freeze - h = {} - h.send(@method, key, 0) - h.keys[0].should equal(key) - end - - it "keeps the existing key in the hash if there is a matching one" do - h = { "a" => 1, "b" => 2, "c" => 3, "d" => 4 } - key1 = HashSpecs::ByValueKey.new(13) - key2 = HashSpecs::ByValueKey.new(13) - h[key1] = 41 - key_in_hash = h.keys.last - key_in_hash.should equal(key1) - h[key2] = 42 - last_key = h.keys.last - last_key.should equal(key_in_hash) - last_key.should_not equal(key2) - end - - 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.should_not equal(key2) - h[key1] = 41 - frozen_key = h.keys.last - frozen_key.should_not equal(key1) - h[key2] = 42 - h.keys.last.should equal(frozen_key) - h.keys.last.should_not equal(key2) - end - - it "raises a RuntimeError if called on a frozen instance" do - lambda { HashSpecs.frozen_hash.send(@method, 1, 2) }.should raise_error(RuntimeError) - 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} - end -end diff --git a/spec/ruby/core/hash/shared/to_s.rb b/spec/ruby/core/hash/shared/to_s.rb deleted file mode 100644 index 0afe605826..0000000000 --- a/spec/ruby/core/hash/shared/to_s.rb +++ /dev/null @@ -1,109 +0,0 @@ -require File.expand_path('../../../../spec_helper', __FILE__) -require File.expand_path('../../fixtures/classes', __FILE__) - -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 - end - - it "calls #inspect on keys and values" do - key = mock('key') - val = mock('val') - key.should_receive(:inspect).and_return('key') - val.should_receive(:inspect).and_return('val') - - { key => val }.send(@method).should == '{key=>val}' - end - - it "does not call #to_s on a String returned from #inspect" do - str = "abc" - str.should_not_receive(:to_s) - - { a: str }.send(@method).should == '{:a=>"abc"}' - 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}" - 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]+>\}$/ - end - - it "does not call #to_str on the object returned from #to_s when it is not a String" do - obj = mock("Hash#inspect/to_s does not call #to_str on #to_s result") - 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]+>\}$/ - end - - it "does not swallow exceptions raised by #to_s" do - obj = mock("Hash#inspect/to_s does not swallow #to_s exceptions") - obj.should_receive(:inspect).and_return(obj) - obj.should_receive(:to_s).and_raise(Exception) - - lambda { { 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=>{...}}' - - x = {} - y = {} - x[0] = y - y[1] = x - x.send(@method).should == "{0=>{1=>{...}}}" - y.send(@method).should == "{1=>{0=>{...}}}" - 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 - 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 - end - - ruby_version_is ''...'2.3' do - it "raises 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)) - - lambda { - {a: utf_16be}.send(@method) - }.should raise_error(Encoding::CompatibilityError) - end - end - - ruby_version_is '2.3' do - 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"}' - end - end -end diff --git a/spec/ruby/core/hash/shared/update.rb b/spec/ruby/core/hash/shared/update.rb deleted file mode 100644 index b1e3793028..0000000000 --- a/spec/ruby/core/hash/shared/update.rb +++ /dev/null @@ -1,59 +0,0 @@ -describe :hash_update, shared: true do - it "adds the entries from other, overwriting duplicate keys. Returns self" do - h = { _1: 'a', _2: '3' } - h.send(@method, _1: '9', _9: 2).should equal(h) - h.should == { _1: "9", _2: "3", _9: 2 } - end - - it "sets any duplicate key to the value of block if passed a block" do - h1 = { a: 2, b: -1 } - h2 = { a: -2, c: 1 } - h1.send(@method, h2) { |k,x,y| 3.14 }.should equal(h1) - h1.should == { c: 1, b: -1, a: 3.14 } - - h1.send(@method, h1) { nil } - h1.should == { a: nil, b: nil, c: nil } - end - - it "tries to convert the passed argument to a hash using #to_hash" do - obj = mock('{1=>2}') - obj.should_receive(:to_hash).and_return({ 1 => 2 }) - { 3 => 4 }.send(@method, obj).should == { 1 => 2, 3 => 4 } - end - - it "does not call to_hash on hash subclasses" do - { 3 => 4 }.send(@method, HashSpecs::ToHashHash[1 => 2]).should == { 1 => 2, 3 => 4 } - end - - it "processes entries with same order as merge()" do - h = { 1 => 2, 3 => 4, 5 => 6, "x" => nil, nil => 5, [] => [] } - merge_bang_pairs = [] - merge_pairs = [] - h.merge(h) { |*arg| merge_pairs << arg } - h.send(@method, h) { |*arg| merge_bang_pairs << arg } - merge_bang_pairs.should == merge_pairs - end - - it "raises a RuntimeError on a frozen instance that is modified" do - lambda do - HashSpecs.frozen_hash.send(@method, 1 => 2) - end.should raise_error(RuntimeError) - end - - it "checks frozen status before coercing an object with #to_hash" do - obj = mock("to_hash frozen") - # This is necessary because mock cleanup code cannot run on the frozen - # object. - def obj.to_hash() raise Exception, "should not receive #to_hash" end - obj.freeze - - lambda { HashSpecs.frozen_hash.send(@method, obj) }.should raise_error(RuntimeError) - end - - # see redmine #1571 - it "raises a RuntimeError on a frozen instance that would not be modified" do - lambda do - HashSpecs.frozen_hash.send(@method, HashSpecs.empty_frozen_hash) - end.should raise_error(RuntimeError) - end -end diff --git a/spec/ruby/core/hash/shared/value.rb b/spec/ruby/core/hash/shared/value.rb deleted file mode 100644 index aac76c253e..0000000000 --- a/spec/ruby/core/hash/shared/value.rb +++ /dev/null @@ -1,14 +0,0 @@ -describe :hash_value_p, shared: true do - it "returns true if the value exists in the hash" do - { a: :b }.send(@method, :a).should == false - { 1 => 2 }.send(@method, 2).should == true - h = Hash.new(5) - h.send(@method, 5).should == false - h = Hash.new { 5 } - h.send(@method, 5).should == false - end - - it "uses == semantics for comparing values" do - { 5 => 2.0 }.send(@method, 2).should == true - end -end diff --git a/spec/ruby/core/hash/shared/values_at.rb b/spec/ruby/core/hash/shared/values_at.rb deleted file mode 100644 index ef3b0e8ba0..0000000000 --- a/spec/ruby/core/hash/shared/values_at.rb +++ /dev/null @@ -1,9 +0,0 @@ -describe :hash_values_at, shared: true do - it "returns an array of values for the given keys" do - h = { a: 9, b: 'a', c: -10, d: nil } - h.send(@method).should be_kind_of(Array) - h.send(@method).should == [] - h.send(@method, :a, :d, :b).should be_kind_of(Array) - h.send(@method, :a, :d, :b).should == [9, nil, 'a'] - end -end diff --git a/spec/ruby/core/hash/shift_spec.rb b/spec/ruby/core/hash/shift_spec.rb index 3991da9656..6095d2e55f 100644 --- a/spec/ruby/core/hash/shift_spec.rb +++ b/spec/ruby/core/hash/shift_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash#shift" do it "removes a pair from hash and return it" do @@ -8,7 +8,7 @@ describe "Hash#shift" do h.size.times do |i| r = h.shift - r.should be_kind_of(Array) + r.should.is_a?(Array) h2[r.first].should == r.last h.size.should == h2.size - i - 1 end @@ -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,8 +56,23 @@ describe "Hash#shift" do h.should == {:c => 3} end - it "raises a RuntimeError if called on a frozen instance" do - lambda { HashSpecs.frozen_hash.shift }.should raise_error(RuntimeError) - lambda { HashSpecs.empty_frozen_hash.shift }.should raise_error(RuntimeError) + it "raises a FrozenError if called on a frozen instance" do + -> { HashSpecs.frozen_hash.shift }.should.raise(FrozenError) + -> { HashSpecs.empty_frozen_hash.shift }.should.raise(FrozenError) + end + + it "works when the hash is at capacity" do + # We try a wide range of sizes in hopes that this will cover all implementations' base Hash size. + results = [] + 1.upto(100) do |n| + h = {} + n.times do |i| + h[i] = i + end + h.shift + results << h.size + end + + results.should == 0.upto(99).to_a end end diff --git a/spec/ruby/core/hash/size_spec.rb b/spec/ruby/core/hash/size_spec.rb index 71660af038..5e5008a5dc 100644 --- a/spec/ruby/core/hash/size_spec.rb +++ b/spec/ruby/core/hash/size_spec.rb @@ -1,7 +1,14 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/length', __FILE__) +require_relative '../../spec_helper' describe "Hash#size" do - it_behaves_like(:hash_length, :size) + it "returns the number of entries" do + { a: 1, b: 'c' }.size.should == 2 + h = { a: 1, b: 2 } + h[:a] = 2 + h.size.should == 2 + { a: 1, b: 1, c: 1 }.size.should == 3 + {}.size.should == 0 + Hash.new(5).size.should == 0 + Hash.new { 5 }.size.should == 0 + end end diff --git a/spec/ruby/core/hash/slice_spec.rb b/spec/ruby/core/hash/slice_spec.rb index 15c9c815ff..fd93254517 100644 --- a/spec/ruby/core/hash/slice_spec.rb +++ b/spec/ruby/core/hash/slice_spec.rb @@ -1,36 +1,74 @@ -require File.expand_path('../../../spec_helper', __FILE__) +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 new hash" do - ret = @hash.slice - ret.should_not equal(@hash) - ret.should be_an_instance_of(Hash) - end + it "returns a new empty hash without arguments" do + ret = @hash.slice + ret.should_not.equal?(@hash) + ret.should.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[:foo] = 42 - r = h.slice(:foo) - r.should == {foo: 42} - r.class.should == Hash + 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 end + + h = klass.new + h[:bar] = 12 + h[:foo] = 42 + h.slice(:foo) + + ScratchPad.recorded.should == [] + end + + it "does not retain the default value" do + h = Hash.new(1) + h.slice(:a).default.should == nil + h[:a] = 1 + h.slice(:a).default.should == 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 == nil + h[:a] = 1 + h.slice(:a).default_proc.should == 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/sort_spec.rb b/spec/ruby/core/hash/sort_spec.rb index 06235f7609..26058c845e 100644 --- a/spec/ruby/core/hash/sort_spec.rb +++ b/spec/ruby/core/hash/sort_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash#sort" do it "converts self to a nested array of [key, value] arrays and sort with Array#sort" do diff --git a/spec/ruby/core/hash/store_spec.rb b/spec/ruby/core/hash/store_spec.rb index 45ea8da896..7017d8ba2b 100644 --- a/spec/ruby/core/hash/store_spec.rb +++ b/spec/ruby/core/hash/store_spec.rb @@ -1,7 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/store', __FILE__) +require_relative '../../spec_helper' describe "Hash#store" do - it_behaves_like(:hash_store, :store) + it "is an alias of Hash#[]=" do + Hash.instance_method(:store).should == Hash.instance_method(:[]=) + end end diff --git a/spec/ruby/core/hash/to_a_spec.rb b/spec/ruby/core/hash/to_a_spec.rb index 76ca721038..8c638db6c3 100644 --- a/spec/ruby/core/hash/to_a_spec.rb +++ b/spec/ruby/core/hash/to_a_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash#to_a" do it "returns a list of [key, value] pairs with same order as each()" do @@ -10,7 +10,7 @@ describe "Hash#to_a" do pairs << [key, value] end - h.to_a.should be_kind_of(Array) + h.to_a.should.is_a?(Array) h.to_a.should == pairs end @@ -23,15 +23,7 @@ describe "Hash#to_a" do end ent = h.entries - ent.should be_kind_of(Array) + ent.should.is_a?(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 366fd58e8a..5d5a280dac 100644 --- a/spec/ruby/core/hash/to_h_spec.rb +++ b/spec/ruby/core/hash/to_h_spec.rb @@ -1,10 +1,10 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash#to_h" do it "returns self for Hash instances" do h = {} - h.to_h.should equal(h) + h.to_h.should.equal?(h) end describe "when called on a subclass of Hash" do @@ -14,21 +14,93 @@ describe "Hash#to_h" do end it "returns a new Hash instance" do - @h.to_h.should be_an_instance_of(Hash) + @h.to_h.should.instance_of?(Hash) @h.to_h.should == @h @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 + + 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(ArgumentError, /element has wrong array length/) + + -> do + { a: 1, b: 2 }.to_h { |k, v| [k] } + end.should.raise(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(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(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 == 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 == 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_hash_spec.rb b/spec/ruby/core/hash/to_hash_spec.rb index 5a173c2ab5..f5622b3d9c 100644 --- a/spec/ruby/core/hash/to_hash_spec.rb +++ b/spec/ruby/core/hash/to_hash_spec.rb @@ -1,14 +1,14 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash#to_hash" do it "returns self for Hash instances" do h = {} - h.to_hash.should equal(h) + h.to_hash.should.equal?(h) end it "returns self for instances of subclasses of Hash" do h = HashSpecs::MyHash.new - h.to_hash.should equal(h) + h.to_hash.should.equal?(h) end end diff --git a/spec/ruby/core/hash/to_proc_spec.rb b/spec/ruby/core/hash/to_proc_spec.rb index b486d188b7..bc4756600d 100644 --- a/spec/ruby/core/hash/to_proc_spec.rb +++ b/spec/ruby/core/hash/to_proc_spec.rb @@ -1,89 +1,91 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Hash#to_proc" do + before :each do + @key = Object.new + @value = Object.new + @hash = { @key => @value } + @default = Object.new + @unstored = Object.new + end + + it "returns an instance of Proc" do + @hash.to_proc.should.instance_of? Proc + end -ruby_version_is "2.3" do - describe "Hash#to_proc" do + describe "the returned proc" do before :each do - @key = Object.new - @value = Object.new - @hash = { @key => @value } - @default = Object.new - @unstored = Object.new + @proc = @hash.to_proc end - it "returns an instance of Proc" do - @hash.to_proc.should be_an_instance_of Proc + it "is a lambda" do + @proc.should.lambda? end - describe "the returned proc" do - before :each do - @proc = @hash.to_proc - end + it "has an arity of 1" do + @proc.arity.should == 1 + end - it "is not a lambda" do - @proc.lambda?.should == false - end + it "raises ArgumentError if not passed exactly one argument" do + -> { + @proc.call + }.should.raise(ArgumentError) - it "raises ArgumentError if not passed exactly one argument" do - lambda { - @proc.call - }.should raise_error(ArgumentError) + -> { + @proc.call 1, 2 + }.should.raise(ArgumentError) + end - lambda { - @proc.call 1, 2 - }.should raise_error(ArgumentError) + context "with a stored key" do + it "returns the paired value" do + @proc.call(@key).should.equal?(@value) end + end - context "with a stored key" do - it "returns the paired value" do - @proc.call(@key).should equal(@value) - end + context "passed as a block" do + it "retrieves the hash's values" do + [@key].map(&@proc)[0].should.equal?(@value) end - context "passed as a block" do - it "retrieves the hash's values" do - [@key].map(&@proc)[0].should equal(@value) - end - - context "to instance_exec" do - it "always retrieves the original hash's values" do - hash = {foo: 1, bar: 2} - proc = hash.to_proc + context "to instance_exec" do + it "always retrieves the original hash's values" do + hash = {foo: 1, bar: 2} + proc = hash.to_proc - hash.instance_exec(:foo, &proc).should == 1 + hash.instance_exec(:foo, &proc).should == 1 - hash2 = {quux: 1} - hash2.instance_exec(:foo, &proc).should == 1 - end + hash2 = {quux: 1} + hash2.instance_exec(:foo, &proc).should == 1 end end + end - context "with no stored key" do - it "returns nil" do - @proc.call(@unstored).should be_nil - end - - context "when the hash has a default value" do - before :each do - @hash.default = @default - end + context "with no stored key" do + it "returns nil" do + @proc.call(@unstored).should == nil + end - it "returns the default value" do - @proc.call(@unstored).should equal(@default) - end + context "when the hash has a default value" do + before :each do + @hash.default = @default end - context "when the hash has a default proc" do - it "returns an evaluated value from the default proc" do - @hash.default_proc = -> hash, called_with { [hash.keys, called_with] } - @proc.call(@unstored).should == [[@key], @unstored] - end + it "returns the default value" do + @proc.call(@unstored).should.equal?(@default) end end - it "raises an ArgumentError when calling #call on the Proc with no arguments" do - lambda { @hash.to_proc.call }.should raise_error(ArgumentError) + context "when the hash has a default proc" do + it "returns an evaluated value from the default proc" do + @hash.default_proc = -> hash, called_with { [hash.keys, called_with] } + @proc.call(@unstored).should == [[@key], @unstored] + end end end + + it "raises an ArgumentError when calling #call on the Proc with no arguments" do + -> { @hash.to_proc.call }.should.raise(ArgumentError) + end end end diff --git a/spec/ruby/core/hash/to_s_spec.rb b/spec/ruby/core/hash/to_s_spec.rb index 803b1d6236..2915db6ef8 100644 --- a/spec/ruby/core/hash/to_s_spec.rb +++ b/spec/ruby/core/hash/to_s_spec.rb @@ -1,7 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/to_s', __FILE__) +require_relative '../../spec_helper' describe "Hash#to_s" do - it_behaves_like :hash_to_s, :to_s + it "is an alias of Hash#inspect" do + Hash.instance_method(:to_s).should == Hash.instance_method(:inspect) + end end diff --git a/spec/ruby/core/hash/transform_keys_spec.rb b/spec/ruby/core/hash/transform_keys_spec.rb index cf42f17e51..d37a2b8616 100644 --- a/spec/ruby/core/hash/transform_keys_spec.rb +++ b/spec/ruby/core/hash/transform_keys_spec.rb @@ -1,109 +1,149 @@ -require File.expand_path('../../../spec_helper', __FILE__) +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.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.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 - 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 "does not retain the default value" do + h = Hash.new(1) + h.transform_keys(&:succ).default.should == nil + h[:a] = 1 + h.transform_keys(&:succ).default.should == nil + end - 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 == { b: 1, c: 2 } + 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 == nil + h[:a] = 1 + h.transform_values(&:succ).default_proc.should == nil + 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 + +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.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(FrozenError) + end - it "raises a RuntimeError on an empty hash" do - ->{ {}.freeze.transform_keys!(&:upcase) }.should raise_error(RuntimeError) - end + it "keeps pairs and raises a FrozenError" do + ->{ @hash.transform_keys!(&:upcase) }.should.raise(FrozenError) + @hash.should == @initial_pairs + end - it "keeps pairs and raises a RuntimeError" do - ->{ @hash.transform_keys!(&:upcase) }.should raise_error(RuntimeError) - @hash.should == @initial_pairs - end + it "raises a FrozenError on hash argument" do + ->{ @hash.transform_keys!({ a: :A, b: :B, c: :C }) }.should.raise(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.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 a9098a9f2d..b19543a9f4 100644 --- a/spec/ruby/core/hash/transform_values_spec.rb +++ b/spec/ruby/core/hash/transform_values_spec.rb @@ -1,98 +1,117 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' -ruby_version_is "2.4" do - describe "Hash#transform_values" do - before :each do - @hash = { a: 1, b: 2, c: 3 } - end +describe "Hash#transform_values" do + before :each do + @hash = { a: 1, b: 2, c: 3 } + end - it "returns new hash" do - ret = @hash.transform_values(&:succ) - ret.should_not equal(@hash) - ret.should be_an_instance_of(Hash) - end + it "returns new hash" do + ret = @hash.transform_values(&:succ) + ret.should_not.equal?(@hash) + ret.should.instance_of?(Hash) + end - it "sets the result as transformed values with the given block" do - @hash.transform_values(&:succ).should == { a: 2, b: 3, c: 4 } - end + it "sets the result as transformed values with the given block" do + @hash.transform_values(&:succ).should == { a: 2, b: 3, c: 4 } + end - it "makes both hashes to share keys" do - key = [1, 2, 3] - new_hash = { key => 1 }.transform_values(&:succ) - new_hash[key].should == 2 - new_hash.keys[0].should equal(key) - end + it "makes both hashes to share keys" do + key = [1, 2, 3] + new_hash = { key => 1 }.transform_values(&:succ) + new_hash[key].should == 2 + new_hash.keys[0].should.equal?(key) + end - context "when no block is given" do - it "returns a sized Enumerator" do - enumerator = @hash.transform_values - enumerator.should be_an_instance_of(Enumerator) - enumerator.size.should == @hash.size - enumerator.each(&:succ).should == { a: 2, b: 3, c: 4 } - end + context "when no block is given" do + it "returns a sized Enumerator" do + enumerator = @hash.transform_values + enumerator.should.instance_of?(Enumerator) + enumerator.size.should == @hash.size + enumerator.each(&:succ).should == { a: 2, b: 3, c: 4 } end + end - it "returns a Hash instance, even on subclasses" do - klass = Class.new(Hash) - h = klass.new - h[:foo] = 42 - r = h.transform_values{|v| 2 * v} - r[:foo].should == 84 - 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_values{|v| 2 * v} + r[:foo].should == 84 + r.class.should == Hash end - describe "Hash#transform_values!" do - before :each do - @hash = { a: 1, b: 2, c: 3 } - @initial_pairs = @hash.dup - end + it "does not retain the default value" do + h = Hash.new(1) + h.transform_values(&:succ).default.should == nil + h[:a] = 1 + h.transform_values(&:succ).default.should == 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 == nil + h[:a] = 1 + h.transform_values(&:succ).default_proc.should == 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 + before :each do + @hash = { a: 1, b: 2, c: 3 } + @initial_pairs = @hash.dup + end + + it "returns self" do + @hash.transform_values!(&:succ).should.equal?(@hash) + end - it "returns self" do - @hash.transform_values!(&:succ).should equal(@hash) + it "updates self as transformed values with the given block" do + @hash.transform_values!(&:succ) + @hash.should == { a: 2, b: 3, c: 4 } + end + + it "partially modifies the contents if we broke from the block" do + @hash.transform_values! do |v| + break if v == 3 + 100 + v end + @hash.should == { a: 101, b: 102, c: 3} + end - it "updates self as transformed values with the given block" do - @hash.transform_values!(&:succ) + context "when no block is given" do + it "returns a sized Enumerator" do + enumerator = @hash.transform_values! + enumerator.should.instance_of?(Enumerator) + enumerator.size.should == @hash.size + enumerator.each(&:succ) @hash.should == { a: 2, b: 3, c: 4 } end + end - it "partially modifies the contents if we broke from the block" do - @hash.transform_values! do |v| - break if v == 3 - 100 + v - end - @hash.should == { a: 101, b: 102, c: 3} + describe "on frozen instance" do + before :each do + @hash.freeze end - context "when no block is given" do - it "returns a sized Enumerator" do - enumerator = @hash.transform_values! - enumerator.should be_an_instance_of(Enumerator) - enumerator.size.should == @hash.size - enumerator.each(&:succ) - @hash.should == { a: 2, b: 3, c: 4 } - end + it "raises a FrozenError on an empty hash" do + ->{ {}.freeze.transform_values!(&:succ) }.should.raise(FrozenError) end - describe "on frozen instance" do - before :each do - @hash.freeze - end - - it "raises a RuntimeError on an empty hash" do - ->{ {}.freeze.transform_values!(&:succ) }.should raise_error(RuntimeError) - end - - it "keeps pairs and raises a RuntimeError" do - ->{ @hash.transform_values!(&:succ) }.should raise_error(RuntimeError) - @hash.should == @initial_pairs - end + it "keeps pairs and raises a FrozenError" do + ->{ @hash.transform_values!(&:succ) }.should.raise(FrozenError) + @hash.should == @initial_pairs + end - context "when no block is given" do - it "does not raise an exception" do - @hash.transform_values!.should be_an_instance_of(Enumerator) - end + context "when no block is given" do + it "does not raise an exception" do + @hash.transform_values!.should.instance_of?(Enumerator) end end end diff --git a/spec/ruby/core/hash/try_convert_spec.rb b/spec/ruby/core/hash/try_convert_spec.rb index 818b08fb32..c321183356 100644 --- a/spec/ruby/core/hash/try_convert_spec.rb +++ b/spec/ruby/core/hash/try_convert_spec.rb @@ -1,50 +1,50 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash.try_convert" do it "returns the argument if it's a Hash" do x = Hash.new - Hash.try_convert(x).should equal(x) + Hash.try_convert(x).should.equal?(x) end it "returns the argument if it's a kind of Hash" do x = HashSpecs::MyHash.new - Hash.try_convert(x).should equal(x) + Hash.try_convert(x).should.equal?(x) end it "returns nil when the argument does not respond to #to_hash" do - Hash.try_convert(Object.new).should be_nil + Hash.try_convert(Object.new).should == nil end it "sends #to_hash to the argument and returns the result if it's nil" do obj = mock("to_hash") obj.should_receive(:to_hash).and_return(nil) - Hash.try_convert(obj).should be_nil + Hash.try_convert(obj).should == nil end it "sends #to_hash to the argument and returns the result if it's a Hash" do x = Hash.new obj = mock("to_hash") obj.should_receive(:to_hash).and_return(x) - Hash.try_convert(obj).should equal(x) + Hash.try_convert(obj).should.equal?(x) end it "sends #to_hash to the argument and returns the result if it's a kind of Hash" do x = HashSpecs::MyHash.new obj = mock("to_hash") obj.should_receive(:to_hash).and_return(x) - Hash.try_convert(obj).should equal(x) + Hash.try_convert(obj).should.equal?(x) end 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_consistent_error(TypeError, "can't convert MockObject into 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(RuntimeError) end end diff --git a/spec/ruby/core/hash/update_spec.rb b/spec/ruby/core/hash/update_spec.rb index 6cfedea271..f3a3e6b4db 100644 --- a/spec/ruby/core/hash/update_spec.rb +++ b/spec/ruby/core/hash/update_spec.rb @@ -1,7 +1,79 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/update', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash#update" do - it_behaves_like(:hash_update, :update) + it "adds the entries from other, overwriting duplicate keys. Returns self" do + h = { _1: 'a', _2: '3' } + h.update(_1: '9', _9: 2).should.equal?(h) + h.should == { _1: "9", _2: "3", _9: 2 } + end + + it "sets any duplicate key to the value of block if passed a block" do + h1 = { a: 2, b: -1 } + h2 = { a: -2, c: 1 } + h1.update(h2) { |k,x,y| 3.14 }.should.equal?(h1) + h1.should == { c: 1, b: -1, a: 3.14 } + + h1.update(h1) { nil } + h1.should == { a: nil, b: nil, c: nil } + end + + it "tries to convert the passed argument to a hash using #to_hash" do + obj = mock('{1=>2}') + obj.should_receive(:to_hash).and_return({ 1 => 2 }) + { 3 => 4 }.update(obj).should == { 1 => 2, 3 => 4 } + end + + it "does not call to_hash on hash subclasses" do + { 3 => 4 }.update(HashSpecs::ToHashHash[1 => 2]).should == { 1 => 2, 3 => 4 } + end + + it "processes entries with same order as merge()" do + h = { 1 => 2, 3 => 4, 5 => 6, "x" => nil, nil => 5, [] => [] } + merge_bang_pairs = [] + merge_pairs = [] + h.merge(h) { |*arg| merge_pairs << arg } + h.update(h) { |*arg| merge_bang_pairs << arg } + merge_bang_pairs.should == merge_pairs + end + + it "raises a FrozenError on a frozen instance that is modified" do + -> do + HashSpecs.frozen_hash.update(1 => 2) + end.should.raise(FrozenError) + end + + it "checks frozen status before coercing an object with #to_hash" do + obj = mock("to_hash frozen") + # This is necessary because mock cleanup code cannot run on the frozen + # object. + def obj.to_hash() raise Exception, "should not receive #to_hash" end + obj.freeze + + -> { HashSpecs.frozen_hash.update(obj) }.should.raise(FrozenError) + end + + # see redmine #1571 + it "raises a FrozenError on a frozen instance that would not be modified" do + -> do + HashSpecs.frozen_hash.update(HashSpecs.empty_frozen_hash) + end.should.raise(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} + hash2 = {1 => :foo, 3 => :bar} + hash.each { hash.update(hash2) } + hash.should == {1 => :foo, 3 => :bar, 5 => 6} + end + + it "accepts multiple hashes" do + result = { a: 1 }.update({ 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.update.should.eql?(hash) + end end diff --git a/spec/ruby/core/hash/value_spec.rb b/spec/ruby/core/hash/value_spec.rb index acfe1968d5..9cfbe576d2 100644 --- a/spec/ruby/core/hash/value_spec.rb +++ b/spec/ruby/core/hash/value_spec.rb @@ -1,8 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/value', __FILE__) +require_relative '../../spec_helper' describe "Hash#value?" do - it_behaves_like(:hash_value_p, :value?) + it "is an alias of Hash#has_value?" do + Hash.instance_method(:value?).should == Hash.instance_method(:has_value?) + end end - diff --git a/spec/ruby/core/hash/values_at_spec.rb b/spec/ruby/core/hash/values_at_spec.rb index 7c39e9b573..78dcd8df6a 100644 --- a/spec/ruby/core/hash/values_at_spec.rb +++ b/spec/ruby/core/hash/values_at_spec.rb @@ -1,7 +1,11 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/values_at', __FILE__) +require_relative '../../spec_helper' describe "Hash#values_at" do - it_behaves_like(:hash_values_at, :values_at) + it "returns an array of values for the given keys" do + h = { a: 9, b: 'a', c: -10, d: nil } + h.values_at.should.is_a?(Array) + h.values_at.should == [] + h.values_at(:a, :d, :b).should.is_a?(Array) + h.values_at(:a, :d, :b).should == [9, nil, 'a'] + end end diff --git a/spec/ruby/core/hash/values_spec.rb b/spec/ruby/core/hash/values_spec.rb index 4ce2f3f5f0..1fe5f48ad6 100644 --- a/spec/ruby/core/hash/values_spec.rb +++ b/spec/ruby/core/hash/values_spec.rb @@ -1,10 +1,10 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Hash#values" do it "returns an array of values" do h = { 1 => :a, 'a' => :a, 'the' => 'lang' } - h.values.should be_kind_of(Array) + h.values.should.is_a?(Array) h.values.sort {|a, b| a.to_s <=> b.to_s}.should == [:a, :a, 'lang'] end end |
