From 5b3d09209a0eca6273f87121bc9b5485d00f3583 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Fri, 29 May 2026 10:19:59 +0200 Subject: [PATCH] Move Hash specs to rely less on shared examples This is mostly a mechanical translation. A few things of interest: Two shared specs weren't actually shared. `Hash#index` used to be an alias for `Hash#key` but was removed at some point. `Hash#values_at` had shared specs which never were actually shared. Tests for `Hash#[]=` used `send`. I explicitly added back a test usign `send` to assert the return value. I cleaned up requires, each test file can be run in isolation. For `../mspec/bin/mspec core/hash`: ``` Before: 69 files, 633 examples, 1176 expectations, 0 failures, 0 errors, 0 tagged After: 69 files, 558 examples, 1026 expectations, 0 failures, 0 errors, 0 tagged ``` So 75 fewer specs, 150 fewer assertions --- CONTRIBUTING.md | 24 +++---- core/hash/each_pair_spec.rb | 106 ++++++++++++++++++++++++++++- core/hash/each_spec.rb | 10 +-- core/hash/element_set_spec.rb | 118 +++++++++++++++++++++++++++++++- core/hash/filter_spec.rb | 9 ++- core/hash/has_key_spec.rb | 6 +- core/hash/has_value_spec.rb | 15 +++- core/hash/include_spec.rb | 39 ++++++++++- core/hash/inspect_spec.rb | 122 ++++++++++++++++++++++++++++++++- core/hash/key_spec.rb | 30 ++++++-- core/hash/length_spec.rb | 6 +- core/hash/member_spec.rb | 6 +- core/hash/merge_spec.rb | 6 +- core/hash/select_spec.rb | 108 ++++++++++++++++++++++++++++- core/hash/shared/each.rb | 105 ---------------------------- core/hash/shared/index.rb | 37 ---------- core/hash/shared/key.rb | 38 ----------- core/hash/shared/length.rb | 12 ---- core/hash/shared/select.rb | 112 ------------------------------ core/hash/shared/store.rb | 115 ------------------------------- core/hash/shared/to_s.rb | 124 ---------------------------------- core/hash/shared/update.rb | 76 --------------------- core/hash/shared/value.rb | 14 ---- core/hash/shared/values_at.rb | 9 --- core/hash/size_spec.rb | 13 +++- core/hash/store_spec.rb | 6 +- core/hash/to_s_spec.rb | 6 +- core/hash/update_spec.rb | 76 ++++++++++++++++++++- core/hash/value_spec.rb | 6 +- core/hash/values_at_spec.rb | 10 ++- 30 files changed, 649 insertions(+), 715 deletions(-) delete mode 100644 core/hash/shared/each.rb delete mode 100644 core/hash/shared/index.rb delete mode 100644 core/hash/shared/key.rb delete mode 100644 core/hash/shared/length.rb delete mode 100644 core/hash/shared/select.rb delete mode 100644 core/hash/shared/store.rb delete mode 100644 core/hash/shared/to_s.rb delete mode 100644 core/hash/shared/update.rb delete mode 100644 core/hash/shared/value.rb delete mode 100644 core/hash/shared/values_at.rb diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0b0f251440..d75ab37b9a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -229,7 +229,7 @@ to avoid duplication of specs, we have shared specs that are re-used in other sp bit tricky however, so let's go over it. Commonly, if a shared spec is only reused within its own module, the shared spec will live within a -shared directory inside that module's directory. For example, the `core/hash/shared/key.rb` spec is +shared directory inside that module's directory. For example, the `core/hash/shared/iteration.rb` spec is only used by `Hash` specs, and so it lives inside `core/hash/shared/`. When a shared spec is used across multiple modules or classes, it lives within the `shared/` directory. @@ -243,25 +243,25 @@ variables from the implementor spec: `@method` and `@object`, which the implemen Here's an example of a snippet of a shared spec and two specs which integrates it: ```ruby -# core/hash/shared/key.rb -describe :hash_key_p, shared: true do - it "returns true if the key's matching value was false" do - { xyz: false }.send(@method, :xyz).should == true +# core/hash/shared/iteration.rb +describe :hash_iteration_no_block, shared: true do + it "returns an Enumerator if called on a non-empty hash without a block" do + {}.send(@method).should.instance_of?(Enumerator) end end -# core/hash/key_spec.rb -describe "Hash#key?" do - it_behaves_like :hash_key_p, :key? +# core/hash/select_spec.rb +describe "Hash#select" do + it_behaves_like :hash_iteration_no_block, :select end -# core/hash/include_spec.rb -describe "Hash#include?" do - it_behaves_like :hash_key_p, :include? +# core/hash/reject_spec.rb +describe "Hash#reject?" do + it_behaves_like :hash_iteration_no_block, :reject end ``` -In the example, the first `describe` defines the shared spec `:hash_key_p`, which defines a spec that +In the example, the first `describe` defines the shared spec `:hash_iteration_no_block`, which defines a spec that calls the `@method` method with an expectation. In the implementor spec, we use `it_behaves_like` to integrate the shared spec. `it_behaves_like` takes 3 parameters: the key of the shared spec, a method, and an object. These last two parameters are accessible via `@method` and `@object` in the shared spec. diff --git a/core/hash/each_pair_spec.rb b/core/hash/each_pair_spec.rb index eb6656681d..01810c130c 100644 --- a/core/hash/each_pair_spec.rb +++ b/core/hash/each_pair_spec.rb @@ -1,11 +1,111 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' require_relative 'shared/iteration' -require_relative 'shared/each' 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 } + + # 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/core/hash/each_spec.rb b/core/hash/each_spec.rb index f0de0bdee5..1644b63216 100644 --- a/core/hash/each_spec.rb +++ b/core/hash/each_spec.rb @@ -1,11 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/iteration' -require_relative 'shared/each' -require_relative '../enumerable/shared/enumeratorized' 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/core/hash/element_set_spec.rb b/core/hash/element_set_spec.rb index 67c5a04d73..2c43ef5007 100644 --- a/core/hash/element_set_spec.rb +++ b/core/hash/element_set_spec.rb @@ -1,7 +1,121 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -require_relative 'shared/store' describe "Hash#[]=" do - it_behaves_like :hash_store, :[]= + it "associates the key with the value and return 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/core/hash/filter_spec.rb b/core/hash/filter_spec.rb index 7dabe44984..625a7feb90 100644 --- a/core/hash/filter_spec.rb +++ b/core/hash/filter_spec.rb @@ -1,10 +1,13 @@ require_relative '../../spec_helper' -require_relative 'shared/select' describe "Hash#filter" do - it_behaves_like :hash_select, :filter + it "is an alias of Hash#select" do + Hash.instance_method(:filter).should == Hash.instance_method(:select) + end end describe "Hash#filter!" do - it_behaves_like :hash_select!, :filter! + it "is an alias of Hash#select!" do + Hash.instance_method(:filter!).should == Hash.instance_method(:select!) + end end diff --git a/core/hash/has_key_spec.rb b/core/hash/has_key_spec.rb index 4af53579e5..9a6244c6f6 100644 --- a/core/hash/has_key_spec.rb +++ b/core/hash/has_key_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/key' 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/core/hash/has_value_spec.rb b/core/hash/has_value_spec.rb index 39f1627fd3..d40e52ebfd 100644 --- a/core/hash/has_value_spec.rb +++ b/core/hash/has_value_spec.rb @@ -1,7 +1,16 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/value' describe "Hash#has_value?" do - it_behaves_like :hash_value_p, :has_value? + 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/core/hash/include_spec.rb b/core/hash/include_spec.rb index f3959dc589..1c32e9fb23 100644 --- a/core/hash/include_spec.rb +++ b/core/hash/include_spec.rb @@ -1,7 +1,40 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/key' 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/core/hash/inspect_spec.rb b/core/hash/inspect_spec.rb index f41ebb70a6..e5cd3a5b3f 100644 --- a/core/hash/inspect_spec.rb +++ b/core/hash/inspect_spec.rb @@ -1,7 +1,123 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/to_s' 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: #\}$/ : /^\{:a=>#\}$/ + { 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: #\}$/ : /^\{:a=>#\}$/ + { 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 }.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 }.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/core/hash/key_spec.rb b/core/hash/key_spec.rb index 73eecbc98e..c2d7049aed 100644 --- a/core/hash/key_spec.rb +++ b/core/hash/key_spec.rb @@ -1,12 +1,32 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/key' -require_relative 'shared/index' 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/core/hash/length_spec.rb b/core/hash/length_spec.rb index d0af0945df..325973306f 100644 --- a/core/hash/length_spec.rb +++ b/core/hash/length_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/length' 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/core/hash/member_spec.rb b/core/hash/member_spec.rb index 37c0414559..e7309c3f7d 100644 --- a/core/hash/member_spec.rb +++ b/core/hash/member_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/key' 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/core/hash/merge_spec.rb b/core/hash/merge_spec.rb index 5fb278ad47..9e566fcee9 100644 --- a/core/hash/merge_spec.rb +++ b/core/hash/merge_spec.rb @@ -1,7 +1,5 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -require_relative 'shared/iteration' -require_relative 'shared/update' describe "Hash#merge" do it "returns a new hash by combining self with the contents of other" do @@ -119,5 +117,7 @@ end describe "Hash#merge!" do - it_behaves_like :hash_update, :merge! + it "is an alias of Hash#update" do + Hash.instance_method(:merge!).should == Hash.instance_method(:update) + end end diff --git a/core/hash/select_spec.rb b/core/hash/select_spec.rb index 38b0180b0e..60b2ce67c1 100644 --- a/core/hash/select_spec.rb +++ b/core/hash/select_spec.rb @@ -1,10 +1,112 @@ require_relative '../../spec_helper' -require_relative 'shared/select' +require_relative '../enumerable/shared/enumeratorized' +require_relative 'fixtures/classes' +require_relative 'shared/iteration' describe "Hash#select" do - it_behaves_like :hash_select, :select + 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 }.select { |*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 }.select { |k,v| v % 2 == 0 } + a_pairs.should.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.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.instance_of?(Enumerator) + end + + it "returns an Enumerator when called on an empty hash without a block" do + @empty.select.should.instance_of?(Enumerator) + end + + 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 - it_behaves_like :hash_select!, :select! + 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.select! { |k,v| v <= 1 }.should.equal? h + + h = { 1 => 2, 3 => 4 } + all_args_select = [] + h.dup.select! { |*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.select! { |k,v| false }.should.equal?(h) + h.should == {} + end + + it "returns nil if no changes were made" do + { a: 1 }.select! { |k,v| v <= 1 }.should == nil + end + + 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 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! + + before :each do + @object = { 1 => 2, 3 => 4, 5 => 6 } + end + it_behaves_like :enumeratorized_with_origin_size, :select! end diff --git a/core/hash/shared/each.rb b/core/hash/shared/each.rb deleted file mode 100644 index 657c5d2c52..0000000000 --- a/core/hash/shared/each.rb +++ /dev/null @@ -1,105 +0,0 @@ -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(ArgumentError) - - -> { - { "a" => 1 }.send(@method, &-> 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 }.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(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/core/hash/shared/index.rb b/core/hash/shared/index.rb deleted file mode 100644 index dd4e89a9b4..0000000000 --- a/core/hash/shared/index.rb +++ /dev/null @@ -1,37 +0,0 @@ -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 == 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 == 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/core/hash/shared/key.rb b/core/hash/shared/key.rb deleted file mode 100644 index 17f9f81457..0000000000 --- a/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/core/hash/shared/length.rb b/core/hash/shared/length.rb deleted file mode 100644 index 24f5563759..0000000000 --- a/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/core/hash/shared/select.rb b/core/hash/shared/select.rb deleted file mode 100644 index b4f6d1b3ac..0000000000 --- a/core/hash/shared/select.rb +++ /dev/null @@ -1,112 +0,0 @@ -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.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.instance_of?(Enumerator) - end - - it "returns an Enumerator when called on an empty hash without a block" do - @empty.send(@method).should.instance_of?(Enumerator) - end - - it "does not retain the default value" do - h = Hash.new(1) - h.send(@method) { true }.default.should == nil - h[:a] = 1 - h.send(@method) { true }.default.should == 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 == nil - h[:a] = 1 - h.send(@method) { true }.default_proc.should == 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(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(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/core/hash/shared/store.rb b/core/hash/shared/store.rb deleted file mode 100644 index 05ef63face..0000000000 --- a/core/hash/shared/store.rb +++ /dev/null @@ -1,115 +0,0 @@ -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(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/core/hash/shared/to_s.rb b/core/hash/shared/to_s.rb deleted file mode 100644 index f88ca738a5..0000000000 --- a/core/hash/shared/to_s.rb +++ /dev/null @@ -1,124 +0,0 @@ -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: #\}$/ : /^\{:a=>#\}$/ - { 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: #\}$/ : /^\{:a=>#\}$/ - { 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(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 - - 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/core/hash/shared/update.rb b/core/hash/shared/update.rb deleted file mode 100644 index 6dbad1d6d0..0000000000 --- a/core/hash/shared/update.rb +++ /dev/null @@ -1,76 +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 FrozenError on a frozen instance that is modified" do - -> do - HashSpecs.frozen_hash.send(@method, 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.send(@method, 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.send(@method, 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.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/core/hash/shared/value.rb b/core/hash/shared/value.rb deleted file mode 100644 index aac76c253e..0000000000 --- a/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/core/hash/shared/values_at.rb b/core/hash/shared/values_at.rb deleted file mode 100644 index 4e4e60e7d6..0000000000 --- a/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.is_a?(Array) - h.send(@method).should == [] - h.send(@method, :a, :d, :b).should.is_a?(Array) - h.send(@method, :a, :d, :b).should == [9, nil, 'a'] - end -end diff --git a/core/hash/size_spec.rb b/core/hash/size_spec.rb index 1e8abd8d97..5e5008a5dc 100644 --- a/core/hash/size_spec.rb +++ b/core/hash/size_spec.rb @@ -1,7 +1,14 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/length' 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/core/hash/store_spec.rb b/core/hash/store_spec.rb index 7e975380ec..7017d8ba2b 100644 --- a/core/hash/store_spec.rb +++ b/core/hash/store_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/store' 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/core/hash/to_s_spec.rb b/core/hash/to_s_spec.rb index e52b09962e..2915db6ef8 100644 --- a/core/hash/to_s_spec.rb +++ b/core/hash/to_s_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/to_s' 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/core/hash/update_spec.rb b/core/hash/update_spec.rb index 0975045ad1..f3a3e6b4db 100644 --- a/core/hash/update_spec.rb +++ b/core/hash/update_spec.rb @@ -1,7 +1,79 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -require_relative 'shared/update' 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/core/hash/value_spec.rb b/core/hash/value_spec.rb index 0ab16a5d1b..9cfbe576d2 100644 --- a/core/hash/value_spec.rb +++ b/core/hash/value_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/value' 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/core/hash/values_at_spec.rb b/core/hash/values_at_spec.rb index b620a279ba..78dcd8df6a 100644 --- a/core/hash/values_at_spec.rb +++ b/core/hash/values_at_spec.rb @@ -1,7 +1,11 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/values_at' 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