summaryrefslogtreecommitdiff
path: root/spec/ruby/core/hash/shared
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/core/hash/shared')
-rw-r--r--spec/ruby/core/hash/shared/comparison.rb15
-rw-r--r--spec/ruby/core/hash/shared/each.rb68
-rw-r--r--spec/ruby/core/hash/shared/eql.rb216
-rw-r--r--spec/ruby/core/hash/shared/equal.rb90
-rw-r--r--spec/ruby/core/hash/shared/greater_than.rb23
-rw-r--r--spec/ruby/core/hash/shared/index.rb27
-rw-r--r--spec/ruby/core/hash/shared/iteration.rb19
-rw-r--r--spec/ruby/core/hash/shared/key.rb38
-rw-r--r--spec/ruby/core/hash/shared/length.rb12
-rw-r--r--spec/ruby/core/hash/shared/less_than.rb23
-rw-r--r--spec/ruby/core/hash/shared/replace.rb51
-rw-r--r--spec/ruby/core/hash/shared/store.rb98
-rw-r--r--spec/ruby/core/hash/shared/to_s.rb109
-rw-r--r--spec/ruby/core/hash/shared/update.rb59
-rw-r--r--spec/ruby/core/hash/shared/value.rb14
-rw-r--r--spec/ruby/core/hash/shared/values_at.rb9
16 files changed, 871 insertions, 0 deletions
diff --git a/spec/ruby/core/hash/shared/comparison.rb b/spec/ruby/core/hash/shared/comparison.rb
new file mode 100644
index 0000000000..bbb9bfd6ad
--- /dev/null
+++ b/spec/ruby/core/hash/shared/comparison.rb
@@ -0,0 +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)
+ 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
+ end
+end
diff --git a/spec/ruby/core/hash/shared/each.rb b/spec/ruby/core/hash/shared/each.rb
new file mode 100644
index 0000000000..bf4c569cfc
--- /dev/null
+++ b/spec/ruby/core/hash/shared/each.rb
@@ -0,0 +1,68 @@
+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
new file mode 100644
index 0000000000..1aed5f51fb
--- /dev/null
+++ b/spec/ruby/core/hash/shared/eql.rb
@@ -0,0 +1,216 @@
+describe :hash_eql, 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).any_number_of_times.and_return(0)
+ y = mock('y')
+ y.should_receive(:hash).any_number_of_times.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).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
+ 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 "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
+ 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
+
+describe :hash_eql_additional, shared: true do
+ it "compares values when keys match" do
+ x = mock('x')
+ y = mock('y')
+ def x.==(o) false end
+ 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
+
+ x = mock('x')
+ y = mock('y')
+ def x.==(o) true end
+ 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
+ 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
+ end
+
+ it "returns true iff 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
+
+ b[:a] = 5
+ a.send(@method, b).should be_true
+
+ not_supported_on :opal do
+ c = { "a" => 5 }
+ a.send(@method, c).should be_false
+ end
+
+ c = { "A" => 5 }
+ a.send(@method, c).should be_false
+
+ c = { a: 6 }
+ a.send(@method, c).should be_false
+ end
+
+ it "does not call to_hash on hash subclasses" do
+ { 5 => 6 }.send(@method, HashSpecs::ToHashHash[5 => 6]).should be_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
+ end
+
+ # Why isn't this true of eql? too ?
+ it "compares keys with matching hash codes via eql?" do
+ a = Array.new(2) 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 = 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
+ end
+
+ it "compares the values in self to values in other hash" do
+ l_val = mock("left")
+ r_val = mock("right")
+
+ l_val.should_receive(:eql?).with(r_val).and_return(true)
+
+ { 1 => l_val }.eql?({ 1 => r_val }).should be_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
+
+ d = Hash.new {|h, k| 1}
+ e = Hash.new {}
+ d[1] = 2
+ e[1] = 2
+ d.send(@method, e).should be_true
+ end
+end
diff --git a/spec/ruby/core/hash/shared/equal.rb b/spec/ruby/core/hash/shared/equal.rb
new file mode 100644
index 0000000000..43606437fe
--- /dev/null
+++ b/spec/ruby/core/hash/shared/equal.rb
@@ -0,0 +1,90 @@
+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
new file mode 100644
index 0000000000..1f8b9fcfb7
--- /dev/null
+++ b/spec/ruby/core/hash/shared/greater_than.rb
@@ -0,0 +1,23 @@
+describe :hash_greater_than, shared: true do
+ before do
+ @h1 = { a: 1, b: 2, c: 3 }
+ @h2 = { a: 1, b: 2 }
+ end
+
+ it "returns true if the other hash is a subset of self" do
+ @h1.send(@method, @h2).should be_true
+ end
+
+ it "returns false if the other hash is not a subset of self" do
+ @h2.send(@method, @h1).should be_false
+ end
+
+ it "converts the right operand to a hash before comparing" do
+ o = Object.new
+ def o.to_hash
+ { a: 1, b: 2 }
+ end
+
+ @h1.send(@method, o).should be_true
+ end
+end
diff --git a/spec/ruby/core/hash/shared/index.rb b/spec/ruby/core/hash/shared/index.rb
new file mode 100644
index 0000000000..9aa1b2a46f
--- /dev/null
+++ b/spec/ruby/core/hash/shared/index.rb
@@ -0,0 +1,27 @@
+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
new file mode 100644
index 0000000000..d27c2443f8
--- /dev/null
+++ b/spec/ruby/core/hash/shared/iteration.rb
@@ -0,0 +1,19 @@
+describe :hash_iteration_no_block, shared: true do
+ before :each do
+ @hsh = { 1 => 2, 3 => 4, 5 => 6 }
+ @empty = {}
+ 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)
+ end
+
+ it "returns an Enumerator if called on an empty hash without a block" do
+ @empty.send(@method).should be_an_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)
+ end
+end
diff --git a/spec/ruby/core/hash/shared/key.rb b/spec/ruby/core/hash/shared/key.rb
new file mode 100644
index 0000000000..17f9f81457
--- /dev/null
+++ b/spec/ruby/core/hash/shared/key.rb
@@ -0,0 +1,38 @@
+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
new file mode 100644
index 0000000000..24f5563759
--- /dev/null
+++ b/spec/ruby/core/hash/shared/length.rb
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000000..cdc6f14546
--- /dev/null
+++ b/spec/ruby/core/hash/shared/less_than.rb
@@ -0,0 +1,23 @@
+describe :hash_less_than, shared: true do
+ before do
+ @h1 = { a: 1, b: 2 }
+ @h2 = { a: 1, b: 2, c: 3 }
+ end
+
+ it "returns true if self is a subset of the other hash" do
+ @h1.send(@method, @h2).should be_true
+ end
+
+ it "returns false if self is not a subset of the other hash" do
+ @h2.send(@method, @h1).should be_false
+ end
+
+ it "converts the right operand to a hash before comparing" do
+ o = Object.new
+ def o.to_hash
+ { a: 1, b: 2, c: 3 }
+ end
+
+ @h1.send(@method, o).should be_true
+ end
+end
diff --git a/spec/ruby/core/hash/shared/replace.rb b/spec/ruby/core/hash/shared/replace.rb
new file mode 100644
index 0000000000..463c861395
--- /dev/null
+++ b/spec/ruby/core/hash/shared/replace.rb
@@ -0,0 +1,51 @@
+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
new file mode 100644
index 0000000000..b43dcbc93e
--- /dev/null
+++ b/spec/ruby/core/hash/shared/store.rb
@@ -0,0 +1,98 @@
+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
new file mode 100644
index 0000000000..0afe605826
--- /dev/null
+++ b/spec/ruby/core/hash/shared/to_s.rb
@@ -0,0 +1,109 @@
+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
new file mode 100644
index 0000000000..b1e3793028
--- /dev/null
+++ b/spec/ruby/core/hash/shared/update.rb
@@ -0,0 +1,59 @@
+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
new file mode 100644
index 0000000000..aac76c253e
--- /dev/null
+++ b/spec/ruby/core/hash/shared/value.rb
@@ -0,0 +1,14 @@
+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
new file mode 100644
index 0000000000..ef3b0e8ba0
--- /dev/null
+++ b/spec/ruby/core/hash/shared/values_at.rb
@@ -0,0 +1,9 @@
+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