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 | 105 | ||||
| -rw-r--r-- | spec/ruby/core/hash/shared/eql.rb | 204 | ||||
| -rw-r--r-- | spec/ruby/core/hash/shared/greater_than.rb | 23 | ||||
| -rw-r--r-- | spec/ruby/core/hash/shared/index.rb | 37 | ||||
| -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/select.rb | 112 | ||||
| -rw-r--r-- | spec/ruby/core/hash/shared/store.rb | 115 | ||||
| -rw-r--r-- | spec/ruby/core/hash/shared/to_s.rb | 93 | ||||
| -rw-r--r-- | spec/ruby/core/hash/shared/update.rb | 76 | ||||
| -rw-r--r-- | spec/ruby/core/hash/shared/value.rb | 14 | ||||
| -rw-r--r-- | spec/ruby/core/hash/shared/values_at.rb | 9 |
15 files changed, 895 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..07564e4cec --- /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 + -> { { a: 1 }.send(@method, 1) }.should raise_error(TypeError) + -> { { a: 1 }.send(@method, nil) }.should raise_error(TypeError) + -> { { a: 1 }.send(@method, []) }.should raise_error(TypeError) + end + + it "returns false if both hashes have the same keys but different values" do + 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..f9839ff58f --- /dev/null +++ b/spec/ruby/core/hash/shared/each.rb @@ -0,0 +1,105 @@ +describe :hash_each, shared: true do + + # 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 }.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 "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 }.send(@method, &obj.method(:foo)) + }.should raise_error(ArgumentError) + + -> { + { "a" => 1 }.send(@method, &-> key, value { }) + }.should raise_error(ArgumentError) + end + + it "yields an Array of 2 elements when given a callable of arity 1" do + obj = Object.new + def obj.foo(key_value) + ScratchPad << key_value + end + + ScratchPad.record([]) + { "a" => 1 }.send(@method, &obj.method(:foo)) + ScratchPad.recorded.should == [["a", 1]] + 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 }.send(@method, &obj.method(:foo)) + }.should raise_error(ArgumentError) + 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..68db49f76d --- /dev/null +++ b/spec/ruby/core/hash/shared/eql.rb @@ -0,0 +1,204 @@ +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 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 + + 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) + + def obj.eql?(o) + return true if self.equal?(o) + false + end + + obj + end + + { a[0] => 1 }.send(@method, { a[1] => 1 }).should be_false + + a = Array.new(2) do + obj = mock('0') + obj.should_receive(:hash).at_least(1).and_return(0) + + def obj.eql?(o) + true + end + + obj + end + + { a[0] => 1 }.send(@method, { a[1] => 1 }).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/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..7f6a186464 --- /dev/null +++ b/spec/ruby/core/hash/shared/index.rb @@ -0,0 +1,37 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe :hash_index, shared: true do + it "returns the corresponding key for value" do + suppress_warning do # for Hash#index + { 2 => 'a', 1 => 'b' }.send(@method, 'b').should == 1 + end + end + + it "returns nil if the value is not found" do + suppress_warning do # for Hash#index + { a: -1, b: 3.14, c: 2.718 }.send(@method, 1).should be_nil + end + end + + it "doesn't return default value if the value is not found" do + suppress_warning do # for Hash#index + Hash.new(5).send(@method, 5).should be_nil + end + end + + it "compares values using ==" do + suppress_warning do # for Hash#index + { 1 => 0 }.send(@method, 0.0).should == 1 + { 1 => 0.0 }.send(@method, 0).should == 1 + end + + needle = mock('needle') + inhash = mock('inhash') + inhash.should_receive(:==).with(needle).and_return(true) + + suppress_warning do # for Hash#index + { 1 => inhash }.send(@method, needle).should == 1 + end + 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/select.rb b/spec/ruby/core/hash/shared/select.rb new file mode 100644 index 0000000000..fbeff07330 --- /dev/null +++ b/spec/ruby/core/hash/shared/select.rb @@ -0,0 +1,112 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' +require_relative '../shared/iteration' +require_relative '../../enumerable/shared/enumeratorized' + +describe :hash_select, shared: true do + before :each do + @hsh = { 1 => 2, 3 => 4, 5 => 6 } + @empty = {} + end + + it "yields two arguments: key and value" do + all_args = [] + { 1 => 2, 3 => 4 }.send(@method) { |*args| all_args << args } + all_args.sort.should == [[1, 2], [3, 4]] + end + + it "returns a Hash of entries for which block is true" do + a_pairs = { 'a' => 9, 'c' => 4, 'b' => 5, 'd' => 2 }.send(@method) { |k,v| v % 2 == 0 } + a_pairs.should be_an_instance_of(Hash) + a_pairs.sort.should == [['c', 4], ['d', 2]] + end + + it "processes entries with the same order as reject" do + h = { a: 9, c: 4, b: 5, d: 2 } + + select_pairs = [] + reject_pairs = [] + h.dup.send(@method) { |*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.send(@method).should be_an_instance_of(Enumerator) + end + + it "returns an Enumerator when called on an empty hash without a block" do + @empty.send(@method).should be_an_instance_of(Enumerator) + end + + it "does not retain the default value" do + h = Hash.new(1) + h.send(@method) { true }.default.should be_nil + h[:a] = 1 + h.send(@method) { true }.default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.send(@method) { true }.default_proc.should be_nil + h[:a] = 1 + h.send(@method) { true }.default_proc.should be_nil + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.send(@method) { |k, _| k == :a } + h2.compare_by_identity?.should == true + end + + it_should_behave_like :hash_iteration_no_block + + before :each do + @object = { 1 => 2, 3 => 4, 5 => 6 } + end + it_should_behave_like :enumeratorized_with_origin_size +end + +describe :hash_select!, shared: true do + before :each do + @hsh = { 1 => 2, 3 => 4, 5 => 6 } + @empty = {} + end + + it "is equivalent to keep_if if changes are made" do + h = { a: 2 } + h.send(@method) { |k,v| v <= 1 }.should equal h + + h = { 1 => 2, 3 => 4 } + all_args_select = [] + h.dup.send(@method) { |*args| all_args_select << args } + all_args_select.should == [[1, 2], [3, 4]] + end + + it "removes all entries if the block is false" do + h = { a: 1, b: 2, c: 3 } + h.send(@method) { |k,v| false }.should equal(h) + h.should == {} + end + + it "returns nil if no changes were made" do + { a: 1 }.send(@method) { |k,v| v <= 1 }.should == nil + end + + it "raises a FrozenError if called on an empty frozen instance" do + -> { HashSpecs.empty_frozen_hash.send(@method) { false } }.should raise_error(FrozenError) + end + + it "raises a FrozenError if called on a frozen instance that would not be modified" do + -> { HashSpecs.frozen_hash.send(@method) { true } }.should raise_error(FrozenError) + end + + it_should_behave_like :hash_iteration_no_block + + before :each do + @object = { 1 => 2, 3 => 4, 5 => 6 } + end + it_should_behave_like :enumeratorized_with_origin_size +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..72a462a42f --- /dev/null +++ b/spec/ruby/core/hash/shared/store.rb @@ -0,0 +1,115 @@ +require_relative '../fixtures/classes' + +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 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.send(@method, 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.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".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.send(@method, 1, 2) }.should raise_error(FrozenError) + end + + it "does not raise an exception if changing the value of an existing key during iteration" do + hash = {1 => 2, 3 => 4, 5 => 6} + hash.each { hash.send(@method, 1, :foo) } + hash.should == {1 => :foo, 3 => 4, 5 => 6} + end + + it "does not dispatch to hash for Boolean, Integer, Float, String, or Symbol" do + code = <<-EOC + load '#{fixture __FILE__, "name.rb"}' + hash = {} + [true, false, 1, 2.0, "hello", :ok].each do |value| + hash[value] = 42 + raise "incorrect value" unless hash[value] == 42 + hash[value] = 43 + raise "incorrect value" unless hash[value] == 43 + end + puts "OK" + puts hash.size + EOC + result = ruby_exe(code, args: "2>&1") + result.should == "OK\n6\n" + end +end diff --git a/spec/ruby/core/hash/shared/to_s.rb b/spec/ruby/core/hash/shared/to_s.rb new file mode 100644 index 0000000000..e116b8878b --- /dev/null +++ b/spec/ruby/core/hash/shared/to_s.rb @@ -0,0 +1,93 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe :hash_to_s, shared: true do + it "returns a string representation with same order as each()" do + h = { a: [1, 2], b: -2, d: -6, nil => nil } + expected = ruby_version_is("3.4") ? "{a: [1, 2], b: -2, d: -6, nil => nil}" : "{:a=>[1, 2], :b=>-2, :d=>-6, nil=>nil}" + h.send(@method).should == expected + end + + it "calls #inspect on keys and values" do + 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 }.send(@method).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 }.send(@method).should == expected + end + + it "calls #to_s on the object returned from #inspect if the Object isn't a String" do + obj = mock("Hash#inspect/to_s calls #to_s") + obj.should_receive(:inspect).and_return(obj) + obj.should_receive(:to_s).and_return("abc") + expected = ruby_version_is("3.4") ? "{a: abc}" : "{:a=>abc}" + { a: obj }.send(@method).should == expected + end + + it "does not call #to_str on the object returned from #inspect when it is not a String" do + obj = mock("Hash#inspect/to_s does not call #to_str") + obj.should_receive(:inspect).and_return(obj) + obj.should_not_receive(:to_str) + expected_pattern = ruby_version_is("3.4") ? /^\{a: #<MockObject:0x[0-9a-f]+>\}$/ : /^\{:a=>#<MockObject:0x[0-9a-f]+>\}$/ + { a: obj }.send(@method).should =~ expected_pattern + end + + it "does not call #to_str on the object returned from #to_s when it is not a String" do + 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 }.send(@method).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 }.send(@method) }.should raise_error(Exception) + end + + it "handles hashes with recursive values" do + x = {} + x[0] = x + expected = ruby_version_is("3.4") ? '{0 => {...}}' : '{0=>{...}}' + x.send(@method).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.send(@method).should == expected_x + y.send(@method).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}.send(@method).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 }.to_s.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 }.send(@method).should == '{"needs-quotes": 1}' + end + end +end diff --git a/spec/ruby/core/hash/shared/update.rb b/spec/ruby/core/hash/shared/update.rb new file mode 100644 index 0000000000..1b0eb809bf --- /dev/null +++ b/spec/ruby/core/hash/shared/update.rb @@ -0,0 +1,76 @@ +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 FrozenError on a frozen instance that is modified" do + -> do + HashSpecs.frozen_hash.send(@method, 1 => 2) + end.should raise_error(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.send(@method, obj) }.should raise_error(FrozenError) + end + + # see redmine #1571 + it "raises a FrozenError on a frozen instance that would not be modified" do + -> do + HashSpecs.frozen_hash.send(@method, HashSpecs.empty_frozen_hash) + end.should raise_error(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.send(@method, hash2) } + hash.should == {1 => :foo, 3 => :bar, 5 => 6} + end + + it "accepts multiple hashes" do + result = { a: 1 }.send(@method, { b: 2 }, { c: 3 }, { d: 4 }) + result.should == { a: 1, b: 2, c: 3, d: 4 } + end + + it "accepts zero arguments" do + hash = { a: 1 } + hash.send(@method).should eql(hash) + end +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 |
