diff options
Diffstat (limited to 'spec/ruby/core/hash/shared')
-rw-r--r-- | spec/ruby/core/hash/shared/comparison.rb | 15 | ||||
-rw-r--r-- | spec/ruby/core/hash/shared/each.rb | 68 | ||||
-rw-r--r-- | spec/ruby/core/hash/shared/eql.rb | 216 | ||||
-rw-r--r-- | spec/ruby/core/hash/shared/equal.rb | 90 | ||||
-rw-r--r-- | spec/ruby/core/hash/shared/greater_than.rb | 23 | ||||
-rw-r--r-- | spec/ruby/core/hash/shared/index.rb | 27 | ||||
-rw-r--r-- | spec/ruby/core/hash/shared/iteration.rb | 19 | ||||
-rw-r--r-- | spec/ruby/core/hash/shared/key.rb | 38 | ||||
-rw-r--r-- | spec/ruby/core/hash/shared/length.rb | 12 | ||||
-rw-r--r-- | spec/ruby/core/hash/shared/less_than.rb | 23 | ||||
-rw-r--r-- | spec/ruby/core/hash/shared/replace.rb | 51 | ||||
-rw-r--r-- | spec/ruby/core/hash/shared/store.rb | 98 | ||||
-rw-r--r-- | spec/ruby/core/hash/shared/to_s.rb | 109 | ||||
-rw-r--r-- | spec/ruby/core/hash/shared/update.rb | 59 | ||||
-rw-r--r-- | spec/ruby/core/hash/shared/value.rb | 14 | ||||
-rw-r--r-- | spec/ruby/core/hash/shared/values_at.rb | 9 |
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 |