diff options
Diffstat (limited to 'spec/ruby/core/hash')
26 files changed, 345 insertions, 365 deletions
diff --git a/spec/ruby/core/hash/assoc_spec.rb b/spec/ruby/core/hash/assoc_spec.rb index 64442918d1..62b2a11b30 100644 --- a/spec/ruby/core/hash/assoc_spec.rb +++ b/spec/ruby/core/hash/assoc_spec.rb @@ -22,11 +22,11 @@ describe "Hash#assoc" do end it "only returns the first matching key-value pair for identity hashes" do - # Avoid literal String keys in Hash#[]= due to https://bugs.ruby-lang.org/issues/12855 + # Avoid literal String keys since string literals can be frozen and interned e.g. with --enable-frozen-string-literal h = {}.compare_by_identity - k1 = 'pear' + k1 = 'pear'.dup h[k1] = :red - k2 = 'pear' + k2 = 'pear'.dup h[k2] = :green h.size.should == 2 h.keys.grep(/pear/).size.should == 2 diff --git a/spec/ruby/core/hash/compact_spec.rb b/spec/ruby/core/hash/compact_spec.rb index 2989afc8b7..76aa43949d 100644 --- a/spec/ruby/core/hash/compact_spec.rb +++ b/spec/ruby/core/hash/compact_spec.rb @@ -18,6 +18,30 @@ describe "Hash#compact" do @hash.compact @hash.should == @initial_pairs end + + ruby_version_is '3.3' do + it "retains the default value" do + hash = Hash.new(1) + hash.compact.default.should == 1 + hash[:a] = 1 + hash.compact.default.should == 1 + end + + it "retains the default_proc" do + pr = proc { |h, k| h[k] = [] } + hash = Hash.new(&pr) + hash.compact.default_proc.should == pr + hash[:a] = 1 + hash.compact.default_proc.should == pr + end + + it "retains compare_by_identity_flag" do + hash = {}.compare_by_identity + hash.compact.compare_by_identity?.should == true + hash[:a] = 1 + hash.compact.compare_by_identity?.should == true + end + end end describe "Hash#compact!" do diff --git a/spec/ruby/core/hash/compare_by_identity_spec.rb b/spec/ruby/core/hash/compare_by_identity_spec.rb index 874cd46eb7..2975526a97 100644 --- a/spec/ruby/core/hash/compare_by_identity_spec.rb +++ b/spec/ruby/core/hash/compare_by_identity_spec.rb @@ -85,19 +85,21 @@ describe "Hash#compare_by_identity" do -> { @h.compare_by_identity }.should raise_error(FrozenError) end - # Behaviour confirmed in bug #1871 + # Behaviour confirmed in https://bugs.ruby-lang.org/issues/1871 it "persists over #dups" do - @idh['foo'] = :bar - @idh['foo'] = :glark + @idh['foo'.dup] = :bar + @idh['foo'.dup] = :glark @idh.dup.should == @idh @idh.dup.size.should == @idh.size + @idh.dup.should.compare_by_identity? end it "persists over #clones" do - @idh['foo'] = :bar - @idh['foo'] = :glark + @idh['foo'.dup] = :bar + @idh['foo'.dup] = :glark @idh.clone.should == @idh @idh.clone.size.should == @idh.size + @idh.dup.should.compare_by_identity? end it "does not copy string keys" do @@ -108,9 +110,16 @@ describe "Hash#compare_by_identity" do @idh.keys.first.should equal foo end + # Check `#[]=` call with a String literal. + # Don't use `#+` because with `#+` it's no longer a String literal. + # + # See https://bugs.ruby-lang.org/issues/12855 it "gives different identity for string literals" do + eval <<~RUBY + # frozen_string_literal: false @idh['foo'] = 1 @idh['foo'] = 2 + RUBY @idh.values.should == [1, 2] @idh.size.should == 2 end diff --git a/spec/ruby/core/hash/constructor_spec.rb b/spec/ruby/core/hash/constructor_spec.rb index ad67274802..8d29773909 100644 --- a/spec/ruby/core/hash/constructor_spec.rb +++ b/spec/ruby/core/hash/constructor_spec.rb @@ -42,26 +42,13 @@ describe "Hash.[]" do Hash[ary].should == { a: :b } end - ruby_version_is "" ... "2.7" do - it "ignores elements that are not arrays" do - -> { - Hash[[:a]].should == {} - }.should complain(/ignoring wrong elements/) - -> { - Hash[[:nil]].should == {} - }.should complain(/ignoring wrong elements/) - end - end - - ruby_version_is "2.7" do - it "raises for elements that are not arrays" do - -> { - Hash[[:a]].should == {} - }.should raise_error(ArgumentError) - -> { - Hash[[:nil]].should == {} - }.should raise_error(ArgumentError) - end + it "raises for elements that are not arrays" do + -> { + Hash[[:a]].should == {} + }.should raise_error(ArgumentError) + -> { + Hash[[:nil]].should == {} + }.should raise_error(ArgumentError) end it "raises an ArgumentError for arrays of more than 2 elements" do @@ -116,8 +103,26 @@ describe "Hash.[]" do HashSpecs::MyInitializerHash[Hash[1, 2]].should be_an_instance_of(HashSpecs::MyInitializerHash) end + it "removes the default value" do + hash = Hash.new(1) + Hash[hash].default.should be_nil + hash[:a] = 1 + Hash[hash].default.should be_nil + end + it "removes the default_proc" do hash = Hash.new { |h, k| h[k] = [] } Hash[hash].default_proc.should be_nil + hash[:a] = 1 + Hash[hash].default_proc.should be_nil + end + + ruby_version_is '3.3' do + it "does not retain compare_by_identity_flag" do + hash = {}.compare_by_identity + Hash[hash].compare_by_identity?.should == false + hash[:a] = 1 + Hash[hash].compare_by_identity?.should == false + end end end diff --git a/spec/ruby/core/hash/deconstruct_keys_spec.rb b/spec/ruby/core/hash/deconstruct_keys_spec.rb index b265732616..bbcd8932e5 100644 --- a/spec/ruby/core/hash/deconstruct_keys_spec.rb +++ b/spec/ruby/core/hash/deconstruct_keys_spec.rb @@ -1,25 +1,23 @@ require_relative '../../spec_helper' -ruby_version_is "2.7" do - describe "Hash#deconstruct_keys" do - it "returns self" do - hash = {a: 1, b: 2} +describe "Hash#deconstruct_keys" do + it "returns self" do + hash = {a: 1, b: 2} - hash.deconstruct_keys([:a, :b]).should equal hash - end + hash.deconstruct_keys([:a, :b]).should equal hash + end - it "requires one argument" do - -> { - {a: 1}.deconstruct_keys - }.should raise_error(ArgumentError, /wrong number of arguments \(given 0, expected 1\)/) - end + it "requires one argument" do + -> { + {a: 1}.deconstruct_keys + }.should raise_error(ArgumentError, /wrong number of arguments \(given 0, expected 1\)/) + end - it "ignores argument" do - hash = {a: 1, b: 2} + it "ignores argument" do + hash = {a: 1, b: 2} - hash.deconstruct_keys([:a]).should == {a: 1, b: 2} - hash.deconstruct_keys(0 ).should == {a: 1, b: 2} - hash.deconstruct_keys('' ).should == {a: 1, b: 2} - end + hash.deconstruct_keys([:a]).should == {a: 1, b: 2} + hash.deconstruct_keys(0 ).should == {a: 1, b: 2} + hash.deconstruct_keys('' ).should == {a: 1, b: 2} end end diff --git a/spec/ruby/core/hash/delete_spec.rb b/spec/ruby/core/hash/delete_spec.rb index b262e8846b..3e3479c69c 100644 --- a/spec/ruby/core/hash/delete_spec.rb +++ b/spec/ruby/core/hash/delete_spec.rb @@ -24,7 +24,7 @@ describe "Hash#delete" do it "allows removing a key while iterating" do h = { a: 1, b: 2 } visited = [] - h.each_pair { |k,v| + h.each_pair { |k, v| visited << k h.delete(k) } @@ -32,13 +32,27 @@ describe "Hash#delete" do h.should == {} end + it "allows removing a key while iterating for big hashes" do + h = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10, + k: 11, l: 12, m: 13, n: 14, o: 15, p: 16, q: 17, r: 18, s: 19, t: 20, + u: 21, v: 22, w: 23, x: 24, y: 25, z: 26 } + visited = [] + h.each_pair { |k, v| + visited << k + h.delete(k) + } + visited.should == [:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, + :n, :o, :p, :q, :r, :s, :t, :u, :v, :w, :x, :y, :z] + h.should == {} + end + it "accepts keys with private #hash method" do key = HashSpecs::KeyWithPrivateHash.new { key => 5 }.delete(key).should == 5 end it "raises a FrozenError if called on a frozen instance" do - -> { HashSpecs.frozen_hash.delete("foo") }.should raise_error(FrozenError) + -> { HashSpecs.frozen_hash.delete("foo") }.should raise_error(FrozenError) -> { HashSpecs.empty_frozen_hash.delete("foo") }.should raise_error(FrozenError) end end diff --git a/spec/ruby/core/hash/element_reference_spec.rb b/spec/ruby/core/hash/element_reference_spec.rb index e271f37ea6..94e8237839 100644 --- a/spec/ruby/core/hash/element_reference_spec.rb +++ b/spec/ruby/core/hash/element_reference_spec.rb @@ -30,7 +30,7 @@ describe "Hash#[]" do end it "does not create copies of the immediate default value" do - str = "foo" + str = +"foo" h = Hash.new(str) a = h[:a] b = h[:b] diff --git a/spec/ruby/core/hash/except_spec.rb b/spec/ruby/core/hash/except_spec.rb index 82cfced72f..ac84f9975c 100644 --- a/spec/ruby/core/hash/except_spec.rb +++ b/spec/ruby/core/hash/except_spec.rb @@ -1,34 +1,32 @@ require_relative '../../spec_helper' -ruby_version_is "3.0" do - describe "Hash#except" do - before :each do - @hash = { a: 1, b: 2, c: 3 } - end +describe "Hash#except" do + before :each do + @hash = { a: 1, b: 2, c: 3 } + end - it "returns a new duplicate hash without arguments" do - ret = @hash.except - ret.should_not equal(@hash) - ret.should == @hash - end + it "returns a new duplicate hash without arguments" do + ret = @hash.except + ret.should_not equal(@hash) + ret.should == @hash + end - it "returns a hash without the requested subset" do - @hash.except(:c, :a).should == { b: 2 } - end + it "returns a hash without the requested subset" do + @hash.except(:c, :a).should == { b: 2 } + end - it "ignores keys not present in the original hash" do - @hash.except(:a, :chunky_bacon).should == { b: 2, c: 3 } - end + it "ignores keys not present in the original hash" do + @hash.except(:a, :chunky_bacon).should == { b: 2, c: 3 } + end - it "always returns a Hash without a default" do - klass = Class.new(Hash) - h = klass.new(:default) - h[:bar] = 12 - h[:foo] = 42 - r = h.except(:foo) - r.should == {bar: 12} - r.class.should == Hash - r.default.should == nil - end + it "always returns a Hash without a default" do + klass = Class.new(Hash) + h = klass.new(:default) + h[:bar] = 12 + h[:foo] = 42 + r = h.except(:foo) + r.should == {bar: 12} + r.class.should == Hash + r.default.should == nil end end diff --git a/spec/ruby/core/hash/fetch_spec.rb b/spec/ruby/core/hash/fetch_spec.rb index 753167f709..6e0d207224 100644 --- a/spec/ruby/core/hash/fetch_spec.rb +++ b/spec/ruby/core/hash/fetch_spec.rb @@ -4,7 +4,7 @@ require_relative '../../shared/hash/key_error' describe "Hash#fetch" do context "when the key is not found" do - it_behaves_like :key_error, -> obj, key { obj.fetch(key) }, Hash.new(a: 5) + it_behaves_like :key_error, -> obj, key { obj.fetch(key) }, Hash.new({ a: 5 }) it_behaves_like :key_error, -> obj, key { obj.fetch(key) }, {} it_behaves_like :key_error, -> obj, key { obj.fetch(key) }, Hash.new { 5 } it_behaves_like :key_error, -> obj, key { obj.fetch(key) }, Hash.new(5) diff --git a/spec/ruby/core/hash/fetch_values_spec.rb b/spec/ruby/core/hash/fetch_values_spec.rb index af3673f6ef..0cd48565af 100644 --- a/spec/ruby/core/hash/fetch_values_spec.rb +++ b/spec/ruby/core/hash/fetch_values_spec.rb @@ -19,7 +19,7 @@ describe "Hash#fetch_values" do end describe "with unmatched keys" do - it_behaves_like :key_error, -> obj, key { obj.fetch_values(key) }, Hash.new(a: 5) + it_behaves_like :key_error, -> obj, key { obj.fetch_values(key) }, Hash.new({ a: 5 }) it "returns the default value from block" do @hash.fetch_values(:z) { |key| "`#{key}' is not found" }.should == ["`z' is not found"] diff --git a/spec/ruby/core/hash/hash_spec.rb b/spec/ruby/core/hash/hash_spec.rb index 3649d4d8de..19eb806dc4 100644 --- a/spec/ruby/core/hash/hash_spec.rb +++ b/spec/ruby/core/hash/hash_spec.rb @@ -41,4 +41,13 @@ describe "Hash#hash" do h.hash.should == {x: [h]}.hash # Like above, because h.eql?(x: [h]) end + + ruby_version_is "3.1" do + it "allows omitting values" do + a = 1 + b = 2 + + eval('{a:, b:}.should == { a: 1, b: 2 }') + end + end end diff --git a/spec/ruby/core/hash/index_spec.rb b/spec/ruby/core/hash/index_spec.rb deleted file mode 100644 index be4e2cc6ab..0000000000 --- a/spec/ruby/core/hash/index_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/index' - -ruby_version_is ''...'3.0' do - describe "Hash#index" do - it_behaves_like :hash_index, :index - end -end diff --git a/spec/ruby/core/hash/new_spec.rb b/spec/ruby/core/hash/new_spec.rb index 6054b69bdd..6279815fd6 100644 --- a/spec/ruby/core/hash/new_spec.rb +++ b/spec/ruby/core/hash/new_spec.rb @@ -33,4 +33,17 @@ describe "Hash.new" do -> { Hash.new(5) { 0 } }.should raise_error(ArgumentError) -> { Hash.new(nil) { 0 } }.should raise_error(ArgumentError) end + + ruby_version_is "3.3" do + it "emits a deprecation warning if keyword arguments are passed" do + -> { Hash.new(unknown: true) }.should complain( + Regexp.new(Regexp.escape("Calling Hash.new with keyword arguments is deprecated and will be removed in Ruby 3.4; use Hash.new({ key: value }) instead")) + ) + + -> { Hash.new(1, unknown: true) }.should raise_error(ArgumentError) + -> { Hash.new(unknown: true) { 0 } }.should raise_error(ArgumentError) + + Hash.new({ unknown: true }).default.should == { unknown: true } + end + end end diff --git a/spec/ruby/core/hash/rehash_spec.rb b/spec/ruby/core/hash/rehash_spec.rb index 0049080456..db3e91b166 100644 --- a/spec/ruby/core/hash/rehash_spec.rb +++ b/spec/ruby/core/hash/rehash_spec.rb @@ -77,6 +77,36 @@ describe "Hash#rehash" do h.keys.should_not.include? [1] end + it "iterates keys in insertion order" do + key = Class.new do + attr_reader :name + + def initialize(name) + @name = name + end + + def hash + 123 + end + end + + a, b, c, d = key.new('a'), key.new('b'), key.new('c'), key.new('d') + h = { a => 1, b => 2, c => 3, d => 4 } + h.size.should == 4 + + key.class_exec do + def eql?(other) + true + end + end + + h.rehash + h.size.should == 1 + k, v = h.first + k.name.should == 'a' + v.should == 4 + end + it "raises a FrozenError if called on a frozen instance" do -> { HashSpecs.frozen_hash.rehash }.should raise_error(FrozenError) -> { HashSpecs.empty_frozen_hash.rehash }.should raise_error(FrozenError) diff --git a/spec/ruby/core/hash/reject_spec.rb b/spec/ruby/core/hash/reject_spec.rb index 397000ab67..dd8e817237 100644 --- a/spec/ruby/core/hash/reject_spec.rb +++ b/spec/ruby/core/hash/reject_spec.rb @@ -31,13 +31,6 @@ describe "Hash#reject" do HashSpecs::MyHash[1 => 2, 3 => 4].reject { false }.should be_kind_of(Hash) HashSpecs::MyHash[1 => 2, 3 => 4].reject { true }.should be_kind_of(Hash) end - - ruby_version_is ''...'2.7' do - it "does not taint the resulting hash" do - h = { a: 1 }.taint - h.reject {false}.should_not.tainted? - end - end end it "processes entries with the same order as reject!" do diff --git a/spec/ruby/core/hash/ruby2_keywords_hash_spec.rb b/spec/ruby/core/hash/ruby2_keywords_hash_spec.rb index 005886a482..7dbb9c0a98 100644 --- a/spec/ruby/core/hash/ruby2_keywords_hash_spec.rb +++ b/spec/ruby/core/hash/ruby2_keywords_hash_spec.rb @@ -1,47 +1,83 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -ruby_version_is "2.7" do - describe "Hash.ruby2_keywords_hash?" do - it "returns false if the Hash is not a keywords Hash" do - Hash.ruby2_keywords_hash?({}).should == false - end +describe "Hash.ruby2_keywords_hash?" do + it "returns false if the Hash is not a keywords Hash" do + Hash.ruby2_keywords_hash?({}).should == false + end - it "returns true if the Hash is a keywords Hash marked by Module#ruby2_keywords" do - obj = Class.new { - ruby2_keywords def m(*args) - args.last - end - }.new - Hash.ruby2_keywords_hash?(obj.m(a: 1)).should == true - end + it "returns true if the Hash is a keywords Hash marked by Module#ruby2_keywords" do + obj = Class.new { + ruby2_keywords def m(*args) + args.last + end + }.new + Hash.ruby2_keywords_hash?(obj.m(a: 1)).should == true + end - it "raises TypeError for non-Hash" do - -> { Hash.ruby2_keywords_hash?(nil) }.should raise_error(TypeError) - end + it "raises TypeError for non-Hash" do + -> { Hash.ruby2_keywords_hash?(nil) }.should raise_error(TypeError) end +end - describe "Hash.ruby2_keywords_hash" do - it "returns a copy of a Hash and marks the copy as a keywords Hash" do - h = {a: 1}.freeze - kw = Hash.ruby2_keywords_hash(h) - Hash.ruby2_keywords_hash?(h).should == false - Hash.ruby2_keywords_hash?(kw).should == true - kw.should == h - end +describe "Hash.ruby2_keywords_hash" do + it "returns a copy of a Hash and marks the copy as a keywords Hash" do + h = {a: 1}.freeze + kw = Hash.ruby2_keywords_hash(h) + Hash.ruby2_keywords_hash?(h).should == false + Hash.ruby2_keywords_hash?(kw).should == true + kw.should == h + end - it "returns an instance of the subclass if called on an instance of a subclass of Hash" do - h = HashSpecs::MyHash.new - h[:a] = 1 - kw = Hash.ruby2_keywords_hash(h) - kw.class.should == HashSpecs::MyHash - Hash.ruby2_keywords_hash?(h).should == false - Hash.ruby2_keywords_hash?(kw).should == true - kw.should == h - end + it "returns an instance of the subclass if called on an instance of a subclass of Hash" do + h = HashSpecs::MyHash.new + h[:a] = 1 + kw = Hash.ruby2_keywords_hash(h) + kw.class.should == HashSpecs::MyHash + Hash.ruby2_keywords_hash?(h).should == false + Hash.ruby2_keywords_hash?(kw).should == true + kw.should == h + end + + it "copies instance variables" do + h = {a: 1} + h.instance_variable_set(:@foo, 42) + kw = Hash.ruby2_keywords_hash(h) + kw.instance_variable_get(:@foo).should == 42 + end + + it "copies the hash internals" do + h = {a: 1} + kw = Hash.ruby2_keywords_hash(h) + h[:a] = 2 + kw[:a].should == 1 + end + + it "raises TypeError for non-Hash" do + -> { Hash.ruby2_keywords_hash(nil) }.should raise_error(TypeError) + end + + it "retains the default value" do + hash = Hash.new(1) + Hash.ruby2_keywords_hash(hash).default.should == 1 + hash[:a] = 1 + Hash.ruby2_keywords_hash(hash).default.should == 1 + end + + it "retains the default_proc" do + pr = proc { |h, k| h[k] = [] } + hash = Hash.new(&pr) + Hash.ruby2_keywords_hash(hash).default_proc.should == pr + hash[:a] = 1 + Hash.ruby2_keywords_hash(hash).default_proc.should == pr + end - it "raises TypeError for non-Hash" do - -> { Hash.ruby2_keywords_hash(nil) }.should raise_error(TypeError) + ruby_version_is '3.3' do + it "retains compare_by_identity_flag" do + hash = {}.compare_by_identity + Hash.ruby2_keywords_hash(hash).compare_by_identity?.should == true + hash[:a] = 1 + Hash.ruby2_keywords_hash(hash).compare_by_identity?.should == true end end end diff --git a/spec/ruby/core/hash/shared/each.rb b/spec/ruby/core/hash/shared/each.rb index b2483c8116..f9839ff58f 100644 --- a/spec/ruby/core/hash/shared/each.rb +++ b/spec/ruby/core/hash/shared/each.rb @@ -21,37 +21,18 @@ describe :hash_each, shared: true do ary.sort.should == ["a", "b", "c"] end - ruby_version_is ""..."3.0" do - it "yields 2 values and not an Array of 2 elements when given a callable of arity 2" 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] - - ScratchPad.record([]) - { "a" => 1 }.send(@method, &-> key, value { ScratchPad << key << value }) - ScratchPad.recorded.should == ["a", 1] + 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 - end - - ruby_version_is "3.0" do - 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, &obj.method(:foo)) + }.should raise_error(ArgumentError) - -> { - { "a" => 1 }.send(@method, &-> key, value { }) - }.should raise_error(ArgumentError) - end + -> { + { "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 diff --git a/spec/ruby/core/hash/shared/eql.rb b/spec/ruby/core/hash/shared/eql.rb index e294edd764..68db49f76d 100644 --- a/spec/ruby/core/hash/shared/eql.rb +++ b/spec/ruby/core/hash/shared/eql.rb @@ -149,80 +149,34 @@ describe :hash_eql_additional, shared: true do h.send(@method, HashSpecs::MyHash[h]).should be_true end - ruby_version_is '2.7' do - # 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 + # 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 - { 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 + obj end - end - ruby_version_is ''...'2.7' do - # 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) + { a[0] => 1 }.send(@method, { a[1] => 1 }).should be_false - # 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 + a = Array.new(2) do + obj = mock('0') + obj.should_receive(:hash).at_least(1).and_return(0) - obj + def obj.eql?(o) + true 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 + 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 diff --git a/spec/ruby/core/hash/shared/equal.rb b/spec/ruby/core/hash/shared/equal.rb deleted file mode 100644 index 43606437fe..0000000000 --- a/spec/ruby/core/hash/shared/equal.rb +++ /dev/null @@ -1,90 +0,0 @@ -describe :hash_equal, shared: true do - it "does not compare values when keys don't match" do - value = mock('x') - value.should_not_receive(:==) - value.should_not_receive(:eql?) - { 1 => value }.send(@method, { 2 => value }).should be_false - end - - it "returns false when the numbers of keys differ without comparing any elements" do - obj = mock('x') - h = { obj => obj } - - obj.should_not_receive(:==) - obj.should_not_receive(:eql?) - - {}.send(@method, h).should be_false - h.send(@method, {}).should be_false - end - - it "first compares keys via hash" do - x = mock('x') - x.should_receive(:hash).and_return(0) - y = mock('y') - y.should_receive(:hash).and_return(0) - - { x => 1 }.send(@method, { y => 1 }).should be_false - end - - it "does not compare keys with different hash codes via eql?" do - x = mock('x') - y = mock('y') - x.should_not_receive(:eql?) - y.should_not_receive(:eql?) - - x.should_receive(:hash).and_return(0) - y.should_receive(:hash).and_return(1) - - def x.hash() 0 end - def y.hash() 1 end - - { x => 1 }.send(@method, { y => 1 }).should be_false - end - - it "computes equality for recursive hashes" do - h = {} - h[:a] = h - h.send(@method, h[:a]).should be_true - (h == h[:a]).should be_true - end - - it "computes equality for complex recursive hashes" do - a, b = {}, {} - a.merge! self: a, other: b - b.merge! self: b, other: a - a.send(@method, b).should be_true # they both have the same structure! - - c = {} - c.merge! other: c, self: c - c.send(@method, a).should be_true # subtle, but they both have the same structure! - a[:delta] = c[:delta] = a - c.send(@method, a).should be_false # not quite the same structure, as a[:other][:delta] = nil - c[:delta] = 42 - c.send(@method, a).should be_false - a[:delta] = 42 - c.send(@method, a).should be_false - b[:delta] = 42 - c.send(@method, a).should be_true - end - - it "computes equality for recursive hashes & arrays" do - x, y, z = [], [], [] - a, b, c = {foo: x, bar: 42}, {foo: y, bar: 42}, {foo: z, bar: 42} - x << a - y << c - z << b - b.send(@method, c).should be_true # they clearly have the same structure! - y.send(@method, z).should be_true - a.send(@method, b).should be_true # subtle, but they both have the same structure! - x.send(@method, y).should be_true - y << x - y.send(@method, z).should be_false - z << x - y.send(@method, z).should be_true - - a[:foo], a[:bar] = a[:bar], a[:foo] - a.send(@method, b).should be_false - b[:bar] = b[:foo] - b.send(@method, c).should be_false - end -end diff --git a/spec/ruby/core/hash/shared/store.rb b/spec/ruby/core/hash/shared/store.rb index b823ea45ca..dd1bb52bac 100644 --- a/spec/ruby/core/hash/shared/store.rb +++ b/spec/ruby/core/hash/shared/store.rb @@ -9,7 +9,7 @@ describe :hash_store, shared: true do it "duplicates string keys using dup semantics" do # dup doesn't copy singleton methods - key = "foo" + key = +"foo" def key.reverse() "bar" end h = {} h.send(@method, key, 0) @@ -44,7 +44,7 @@ describe :hash_store, shared: true do end it "duplicates and freezes string keys" do - key = "foo" + key = +"foo" h = {} h.send(@method, key, 0) key << "bar" @@ -75,8 +75,8 @@ describe :hash_store, shared: true do 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 = "foo".dup + key2 = "foo".dup key1.should_not equal(key2) h[key1] = 41 frozen_key = h.keys.last diff --git a/spec/ruby/core/hash/shared/to_s.rb b/spec/ruby/core/hash/shared/to_s.rb index b0e3705d01..7864d7cd4c 100644 --- a/spec/ruby/core/hash/shared/to_s.rb +++ b/spec/ruby/core/hash/shared/to_s.rb @@ -2,7 +2,6 @@ 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 } @@ -25,7 +24,7 @@ describe :hash_to_s, shared: true do end it "does not call #to_s on a String returned from #inspect" do - str = "abc" + str = +"abc" str.should_not_receive(:to_s) { a: str }.send(@method).should == '{:a=>"abc"}' @@ -77,22 +76,14 @@ describe :hash_to_s, shared: true do y.send(@method).should == "{1=>{0=>{...}}}" end - ruby_version_is ''...'2.7' do - 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 - 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)) + 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 + + it "works for keys and values whose #inspect return a frozen String" do + { true => false }.to_s.should == "{true=>false}" + end end diff --git a/spec/ruby/core/hash/shift_spec.rb b/spec/ruby/core/hash/shift_spec.rb index 9d43e640f5..ea36488a04 100644 --- a/spec/ruby/core/hash/shift_spec.rb +++ b/spec/ruby/core/hash/shift_spec.rb @@ -30,23 +30,45 @@ describe "Hash#shift" do h.should == {} end - it "calls #default with nil if the Hash is empty" do - h = {} - def h.default(key) - key.should == nil - :foo + ruby_version_is '3.2' do + it "returns nil if the Hash is empty" do + h = {} + def h.default(key) + raise + end + h.shift.should == nil + end + end + + ruby_version_is ''...'3.2' do + it "calls #default with nil if the Hash is empty" do + h = {} + def h.default(key) + key.should == nil + :foo + end + h.shift.should == :foo end - h.shift.should == :foo end it "returns nil from an empty hash" do {}.shift.should == nil end - it "returns (computed) default for empty hashes" do - Hash.new(5).shift.should == 5 - h = Hash.new { |*args| args } - h.shift.should == [h, nil] + ruby_version_is '3.2' do + it "returns nil for empty hashes with defaults and default procs" do + Hash.new(5).shift.should == nil + h = Hash.new { |*args| args } + h.shift.should == nil + end + end + + ruby_version_is ''...'3.2' do + it "returns (computed) default for empty hashes" do + Hash.new(5).shift.should == 5 + h = Hash.new { |*args| args } + h.shift.should == [h, nil] + end end it "preserves Hash invariants when removing the last item" do diff --git a/spec/ruby/core/hash/to_a_spec.rb b/spec/ruby/core/hash/to_a_spec.rb index 46f871389a..5baf677929 100644 --- a/spec/ruby/core/hash/to_a_spec.rb +++ b/spec/ruby/core/hash/to_a_spec.rb @@ -26,14 +26,4 @@ describe "Hash#to_a" do ent.should be_kind_of(Array) ent.should == pairs end - - ruby_version_is ''...'2.7' do - it "returns a tainted array if self is tainted" do - {}.taint.to_a.tainted?.should be_true - end - - it "returns an untrusted array if self is untrusted" do - {}.untrust.to_a.untrusted?.should be_true - end - end end diff --git a/spec/ruby/core/hash/to_proc_spec.rb b/spec/ruby/core/hash/to_proc_spec.rb index 8f5d21beb5..9dbc79e5eb 100644 --- a/spec/ruby/core/hash/to_proc_spec.rb +++ b/spec/ruby/core/hash/to_proc_spec.rb @@ -19,20 +19,12 @@ describe "Hash#to_proc" do @proc = @hash.to_proc end - ruby_version_is ""..."3.0" do - it "is not a lambda" do - @proc.should_not.lambda? - end + it "is a lambda" do + @proc.should.lambda? end - ruby_version_is "3.0" do - it "is a lambda" do - @proc.should.lambda? - end - - it "has an arity of 1" do - @proc.arity.should == 1 - end + it "has an arity of 1" do + @proc.arity.should == 1 end it "raises ArgumentError if not passed exactly one argument" do diff --git a/spec/ruby/core/hash/transform_keys_spec.rb b/spec/ruby/core/hash/transform_keys_spec.rb index 70a4cdde36..2fbb17a8e2 100644 --- a/spec/ruby/core/hash/transform_keys_spec.rb +++ b/spec/ruby/core/hash/transform_keys_spec.rb @@ -42,6 +42,18 @@ describe "Hash#transform_keys" do r.keys.should == [:xfoo] r.class.should == Hash end + + it "allows a hash argument" do + @hash.transform_keys({ a: :A, b: :B, c: :C }).should == { A: 1, B: 2, C: 3 } + end + + it "allows a partial transformation of keys when using a hash argument" do + @hash.transform_keys({ a: :A, c: :C }).should == { A: 1, b: 2, C: 3 } + end + + it "allows a combination of hash and block argument" do + @hash.transform_keys({ a: :A }, &:to_s).should == { A: 1, 'b' => 2, 'c' => 3 } + end end describe "Hash#transform_keys!" do @@ -59,14 +71,13 @@ describe "Hash#transform_keys!" do @hash.should == { 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4 } end - # https://bugs.ruby-lang.org/issues/14380 it "prevents conflicts between new keys and old ones" do @hash.transform_keys!(&:succ) @hash.should == { b: 1, c: 2, d: 3, e: 4 } end ruby_version_is ""..."3.0.2" do # https://bugs.ruby-lang.org/issues/17735 - it "returns the processed keys if we broke from the block" do + it "returns the processed keys if we break from the block" do @hash.transform_keys! do |v| break if v == :c v.succ @@ -76,7 +87,7 @@ describe "Hash#transform_keys!" do end ruby_version_is "3.0.2" do - it "returns the processed keys and non evaluated keys if we broke from the block" do + it "returns the processed keys and non evaluated keys if we break from the block" do @hash.transform_keys! do |v| break if v == :c v.succ @@ -98,6 +109,11 @@ describe "Hash#transform_keys!" do end end + it "allows a hash argument" do + @hash.transform_keys!({ a: :A, b: :B, c: :C, d: :D }) + @hash.should == { A: 1, B: 2, C: 3, D: 4 } + end + describe "on frozen instance" do before :each do @hash.freeze @@ -112,6 +128,10 @@ describe "Hash#transform_keys!" do @hash.should == @initial_pairs end + it "raises a FrozenError on hash argument" do + ->{ @hash.transform_keys!({ a: :A, b: :B, c: :C }) }.should raise_error(FrozenError) + end + context "when no block is given" do it "does not raise an exception" do @hash.transform_keys!.should be_an_instance_of(Enumerator) diff --git a/spec/ruby/core/hash/try_convert_spec.rb b/spec/ruby/core/hash/try_convert_spec.rb index 44195c5010..d359ae49d8 100644 --- a/spec/ruby/core/hash/try_convert_spec.rb +++ b/spec/ruby/core/hash/try_convert_spec.rb @@ -39,7 +39,7 @@ describe "Hash.try_convert" do it "sends #to_hash to the argument and raises TypeError if it's not a kind of Hash" do obj = mock("to_hash") obj.should_receive(:to_hash).and_return(Object.new) - -> { Hash.try_convert obj }.should raise_error(TypeError) + -> { Hash.try_convert obj }.should raise_error(TypeError, "can't convert MockObject to Hash (MockObject#to_hash gives Object)") end it "does not rescue exceptions raised by #to_hash" do |