##
# Hash ISO Test

class HashKey
  attr_accessor :value, :error, :callback

  self.class.alias_method :[], :new

  def initialize(value, error: nil, callback: nil)
    @value = value
    @error = error
    @callback = callback
  end

  def ==(other)
    @callback.(:==, self, other) if @callback
    return raise_error(:==) if @error == true || @error == :==
    other.kind_of?(self.class) && @value == other.value
  end

  def eql?(other)
    @callback.(:eql?, self, other) if @callback
    return raise_error(:eql?) if @error == true || @error == :eql?
    other.kind_of?(self.class) && @value.eql?(other.value)
  end

  def hash
    @callback.(:hash, self) if @callback
    return raise_error(:hash) if @error == true || @error == :hash
    @value % 3
  end

  def to_s
    "#{self.class}[#{@value}]"
  end
  alias inspect to_s

  def raise_error(name)
    raise "##{self}: #{name} error"
  end
end

class HashEntries < Array
  self.class.alias_method :[], :new

  def initialize(entries) self.replace(entries) end
  def key(index, k=get=true) get ? self[index][0] : (self[index][0] = k) end
  def value(index, v=get=true) get ? self[index][1] : (self[index][1] = v) end
  def keys; map{|k, v| k} end
  def values; map{|k, v| v} end
  def each_key(&block) each{|k, v| block.(k)} end
  def each_value(&block) each{|k, v| block.(v)} end
  def dup2; self.class[*map{|k, v| [k.dup, v.dup]}] end
  def to_s; "#{self.class}#{super}" end
  alias inspect to_s

  def hash_for(hash={}, &block)
    each{|k, v| hash[k] = v}
    block.(hash) if block
    hash
  end
end

def ar_entries
  HashEntries[
    [1, "one"],
    [HashKey[2], :two],
    [nil, :two],
    [:one, 1],
    ["&", "&amp;"],
    [HashKey[6], :six],
    [HashKey[5], :five],  # same hash code as HashKey[2]
  ]
end

def ht_entries
  ar_entries.dup.push(
    ["id", 32],
    [:date, "2020-05-02"],
    [200, "OK"],
    ["modifiers", ["left_shift", "control"]],
    [:banana, :yellow],
    ["JSON", "JavaScript Object Notation"],
    [:size, :large],
    ["key_code", "h"],
    ["h", 0x04],
    [[3, 2, 1], "three, two, one"],
    [:auto, true],
    [HashKey[12], "December"],
    [:path, "/path/to/file"],
    [:name, "Ruby"],
  )
end

def merge_entries!(entries1, entries2)
  entries2.each do |k2, v2|
    entry1 = entries1.find{|k1, _| k1.eql?(k2)}
    entry1 ? (entry1[1] = v2) : (entries1 << [k2, v2])
  end
  entries1
end

def product(*arrays, &block)
  sizes = Array.new(arrays.size+1, 1)
  (arrays.size-1).downto(0){|i| sizes[i] = arrays[i].size * sizes[i+1]}
  size = sizes[0]
  results = Array.new(size){[]}
  arrays.each_with_index do |array, arrays_i|
    results_i = -1
    (size / sizes[arrays_i]).times do
      array.each do |v|
        sizes[arrays_i+1].times{results[results_i+=1] << v}
      end
    end
  end
  results.each{block.(_1)}
end

def assert_iterator(exp, obj, meth)
  params = []
  obj.__send__(meth) {|param| params << param}
  assert_equal(exp, params)
end

def assert_nothing_crashed(&block)
  block.call rescue nil
  pass
end

assert('Hash', '15.2.13') do
  assert_equal(Class, Hash.class)
end

[[:==, '15.2.13.4.1'], [:eql?, '']].each do |meth, iso|
  assert("Hash##{meth}", iso) do
    cls = Class.new(Hash){attr_accessor :foo}
    [ar_entries, ht_entries].each do |entries|
      h1 = entries.hash_for
      h2 = entries.dup.reverse!.hash_for
      assert_operator(h1, meth, h2)
      assert_operator(h1, meth, h1)
      assert_not_operator(h1, meth, true)
      assert_operator({}, meth, Hash.new)

      h1 = entries.hash_for(cls.new(1)) {|h| h.foo = 1}
      h2 = entries.hash_for(cls.new(2)) {|h| h.foo = 2}
      assert_operator(h1, meth, h2)

      h1 = entries.hash_for
      h2 = entries.hash_for(cls.new)
      assert_operator(h1, meth, h2)

      h1 = (entries.dup << [:_k, 1]).hash_for
      h2 = (entries.dup << [:_k, 2]).hash_for
      assert_not_operator(h1, meth, h2)

      h1 = (entries.dup << [:_k1, 0]).hash_for
      h2 = (entries.dup << [:_k2, 0]).hash_for
      assert_not_operator(h1, meth, h2)

      h1 = entries.hash_for
      h2 = (entries.dup << [:_k, 2]).hash_for
      assert_not_operator(h1, meth, h2)

      k1, v1 = HashKey[-1], HashKey[-2]
      k2, v2 = HashKey[-1], HashKey[-2]
      h1 = (entries.dup << [k1, v1]).hash_for
      h2 = (entries.dup << [k2, v2]).hash_for
      product([h1, h2], [k1, k2], %i[eql? hash]) do |h, k, m|
        [k1, k2].each{_1.callback = nil}
        k.callback = ->(name, *){h.clear if name == m}
        assert_nothing_crashed{h1.__send__(meth, h2)}
      end
      product([h1, h2], [v1, v2]) do |h, v|
        [v1, v2].each{_1.callback = nil}
        v.callback = ->(name, *){h.clear if name == meth}
        assert_nothing_crashed{h1.__send__(meth, h2)}
      end

      if Object.const_defined?(:Float)
        h1 = (entries.dup << [-1, true]).hash_for
        h2 = (entries.dup << [-1.0, true]).hash_for
        assert_not_operator(h1, meth, h2)
        h1 = (entries.dup << [-1.0, true]).hash_for
        h2 = (entries.dup << [-1, true]).hash_for
        assert_not_operator(h1, meth, h2)

        h1 = (entries.dup << [:_k, 1]).hash_for
        h2 = (entries.dup << [:_k, 1.0]).hash_for
        if meth == :==
          assert_operator(h1, meth, h2)
        else
          assert_not_operator(h1, meth, h2)
        end
      end
    end
  end
end

assert('Hash#[]', '15.2.13.4.2') do
  [ar_entries, ht_entries].each do |entries|
    h = entries.hash_for
    assert_equal(entries.size, h.size)
    entries.each{|k, v| assert_equal(v, h[k])}
    assert_equal(nil, h["_not_found_"])
    assert_equal(nil, h[:_not_dound_])
    assert_equal(nil, h[-2])

    k = HashKey[-4]
    h[HashKey[-1]] = -1
    h[k] = -4
    h.delete(k)
    assert_equal(nil, h[k])

    if Object.const_defined?(:Float)
      h[-2] = 22
      assert_equal(nil, h[-2.0])
      h[-3.0] = 33
      assert_equal(nil, h[-3])
      assert_equal(33, h[-3.0])
    end

    k = HashKey[-2]
    k.callback = ->(name, *){h.clear if name == :eql?}
    assert_nothing_crashed{h[k]}
    k.callback = ->(name, *){h.clear if name == :hash}
    assert_nothing_crashed{h[k]}
  end

  # Hash#[] should call #default (#3272)
  h = {}
  def h.default(k); self[k] = 1; end
  h[:foo] += 1
  assert_equal(2, h[:foo])
end

[%w[[]= 3], %w[store 26]].each do |meth, no|
  assert("Hash##{meth}", "15.2.13.4.#{no}") do
    [{}, ht_entries.hash_for].each do |h|
      # duplicated key
      k = :_dup_key
      h.__send__(meth, k, 1)
      size = h.size
      h.__send__(meth, k, 2)
      assert_equal(size, h.size)
      assert_equal(2, h[k])

      # freeze string key
      k = "_mutable"
      h.__send__(meth, k, 1)
      h_k = h.keys[-1]
      assert_not_same(k, h_k)
      assert_predicate(h_k, :frozen?)
      assert_not_predicate(k, :frozen?)

      # frozen string key
      k = "_immutable".freeze
      h.__send__(meth, k, 2)
      h_k = h.keys[-1]
      assert_same(k, h_k)
      assert_predicate(h_k, :frozen?)

      # numeric key
      if Object.const_defined?(:Float)
        h.__send__(meth, 3, :fixnum)
        h.__send__(meth, 3.0, :float)
        assert_equal(:fixnum, h[3])
        assert_equal(:float, h[3.0])
        h.__send__(meth, 4.0, :float)
        h.__send__(meth, 4, :fixnum)
        assert_equal(:fixnum, h[4])
        assert_equal(:float, h[4.0])
      end

      # other key
      k = [:_array]
      h.__send__(meth, k, :_array)
      h_k = h.keys[-1]
      assert_same(k, h_k)
      assert_not_predicate(h_k, :frozen?)
      assert_not_predicate(k, :frozen?)

      # deleted key
      k1, k2, k3 = HashKey[-1], HashKey[-4], HashKey[-7]  # same hash code
      h.__send__(meth, k1, 1)
      h.__send__(meth, k2, -4)
      h.__send__(meth, k3, 73)
      size = h.size
      h.delete(k1)
      h.delete(k2)
      h.__send__(meth, k2, 40)
      assert_equal(nil, h[k1])
      assert_equal(40, h[k2])
      assert_equal(73, h[k3])
      assert_equal(size - 1, h.size)

      # frozen
      h.freeze
      assert_raise(FrozenError){h.__send__(meth, -100, 1)}
    end

    [ar_entries.hash_for, ht_entries.hash_for].each do |h|
      k = HashKey[-2]
      k.callback = ->(name, *){h.clear if name == :eql?}
      assert_nothing_crashed{h.__send__(meth, k, 2)}
      k.callback = ->(name, *){h.clear if name == :hash}
      assert_nothing_crashed{h.__send__(meth, k, 2)}
    end
  end
end

assert('Hash#clear', '15.2.13.4.4') do
  [ar_entries, ht_entries].each do |entries|
    h = entries.hash_for
    assert_same(h, h.clear)
    assert_equal(0, h.size)
    assert_nil(h[entries.key(3)])

    h.freeze
    assert_raise(FrozenError){h.clear}
  end

  h = {}.freeze
  assert_raise(FrozenError){h.clear}
end

assert('Hash#dup') do
  cls = Class.new(Hash){attr_accessor :foo}
  [ar_entries, ht_entries].each do |entries|
    h1 = entries.hash_for(cls.new(61)){|h| h.foo = 23}.freeze
    h2 = h1.dup
    assert_not_predicate(h2, :frozen?)
    assert_equal(h1.class, h2.class)
    assert_equal(entries, h2.to_a)
    assert_equal(23, h2.foo)
    assert_equal(61, h2["_not_found_"])
    h2[-10] = 10
    assert_equal(10, h2[-10])
    assert_not_operator(h1, :key?, -10)

    h = entries.hash_for
    k = HashKey[-1]
    h[k] = 1
    k.callback = ->(*){h.clear}
    assert_nothing_crashed{h.dup}
  end
end

assert('Hash#default', '15.2.13.4.5') do
  [ar_entries, ht_entries].each do |entries|
    h = entries.hash_for(Hash.new)
    assert_equal(nil, h.default)
    assert_equal(nil, h.default(-2))

    h = entries.hash_for(Hash.new(-88))
    assert_equal(-88, h.default)
    assert_equal(-88, h.default(-2))
    assert_not_operator(h, :key?, -2)
    assert_raise(ArgumentError){h.default(-2,-2)}

    proc = ->(h, k){h[k] = k * 3}
    h = entries.hash_for(Hash.new(proc))
    assert_equal(proc, h.default(-2))

    h = entries.hash_for(Hash.new(&proc))
    assert_equal(nil, h.default)
    assert_not_operator(h, :key?, -2)
    assert_equal(-6, h.default(-2))
    assert_equal(-6, h[-2])
    h[-2] = -5
    assert_equal(-6, h.default(-2))
    assert_equal(-6, h[-2])
  end
end

assert('Hash#default=', '15.2.13.4.6') do
  [ar_entries, ht_entries].each do |entries|
    h = entries.hash_for(Hash.new)
    h.default = 3
    assert_equal(3, h[-2])
    assert_equal(entries.value(0), h[entries.key(0)])

    h.default = 4
    assert_equal(4, h[-2])

    h.default = nil
    assert_equal(nil, h[-2])

    h.default = [5]
    assert_same(h[-2], h[-3])

    h.freeze
    assert_raise(FrozenError){h.default = 3}
  end
end

assert('Hash#default_proc', '15.2.13.4.7') do
  [ar_entries, ht_entries].each do |entries|
    h = entries.hash_for({})
    assert_nil(h.default_proc)

    h = entries.hash_for(Hash.new(34))
    assert_nil(h.default_proc)

    h = entries.hash_for(Hash.new{|h, k| h[k] = k * 3})
    proc = h.default_proc
    assert_equal(Proc, proc.class)
    assert_equal(6, proc.(h, 2))
    assert_equal([2, 6], h.to_a[-1])
  end
end

assert('Hash#delete', '15.2.13.4.8') do
  [ar_entries, ht_entries].each do |entries|
    h = entries.hash_for
    pairs = entries.dup
    [0, 2, -1].each do |i|
      k, v = pairs.delete_at(i)
      assert_equal(v, h.delete(k))
      assert_equal(nil, h[k])
      assert_equal(false, h.key?(k))
    end
    [entries.key(0), "_not_found_"].each {|k|assert_equal(nil, h.delete(k))}
    assert_equal(pairs.size, h.size)
    assert_equal(pairs, h.to_a)
    pairs.each {|k, v| assert_equal(v, h[k])}

    h = entries.hash_for
    pairs = entries.dup
    [pairs.delete_at(1), ["_not_found_", "_default"]].each do |k, v|
      assert_equal(v, h.delete(k){"_default"})
      assert_equal(nil, h[k])
      assert_equal(false, h.key?(k))
    end
    assert_equal(pairs.size, h.size)
    assert_equal(pairs, h.to_a)
    pairs.each {|k, v| assert_equal(v, h[k])}

    if Object.const_defined?(:Float)
      h = entries.dup.push([-5, 1], [-5.0, 2], [-6.0, 3], [-6, 4]).hash_for
      assert_equal(1, h.delete(-5))
      assert_equal(3, h.delete(-6.0))
    end

    # nil value with block
    h = entries.hash_for
    k = "_nil"
    h[k] = nil
    assert_equal(nil, h.delete(k){"blk"})
    assert_equal(false, h.key?(k))

    k = HashKey[-31, callback: ->(*){h.clear}]
    assert_nothing_crashed{h.delete(k)}
  end

  assert_raise(ArgumentError){{}.delete}
  assert_raise(ArgumentError){{}.delete(1,2)}

  h = {}.freeze
  assert_raise(FrozenError){h.delete(1)}
end

[%w[each 9], %w[each_key 10], %w[each_value 11]].each do |meth, no|
  assert("Hash##{meth}", "15.2.13.4.#{no}") do
    [ar_entries, ht_entries].each do |entries|
      exp = []
      entries.__send__(meth){|param| exp << param}
      assert_iterator(exp, entries.hash_for, meth)

      h = entries.hash_for
      entries.shift
      h.shift
      entry = entries.delete_at(1)
      h.delete(entry[0])
      h.delete(entries.delete_at(-4)[0])
      entries << entry
      h.store(*entry)
      exp = []
      entries.__send__(meth){|param| exp << param}
      assert_iterator(exp, h, meth)
    end

    assert_iterator([], {}, meth)
  end
end

assert('Hash#empty?', '15.2.13.4.12') do
  [ar_entries, ht_entries].each do |entries|
    assert_not_predicate entries.hash_for, :empty?

    h = entries.hash_for
    h.shift
    h.delete(entries.key(-1))
    assert_not_predicate h, :empty?

    h = entries.hash_for
    entries.size.times{h.shift}
    assert_predicate(h, :empty?)

    h = entries.hash_for
    entries.each {|k, v| h.delete(k)}
    assert_predicate(h, :empty?)
  end

  assert_predicate(Hash.new, :empty?)
  assert_predicate(Hash.new(1), :empty?)
  assert_predicate(Hash.new{|h, k| h[k] = 2}, :empty?)
end

[%w[has_key? 13], %w[include? 15], %w[key? 18], %w[member? 21]].each do |meth,no|
  assert("Hash##{meth}", "15.2.13.4.#{no}") do
    [ar_entries, ht_entries].each do |entries|
      pairs = entries.dup.push([HashKey[-3], 3], [nil, "NIL"])
      h = pairs.hash_for
      pairs.each{|k, v| assert_operator(h, meth, k)}
      assert_not_operator(h, meth, HashKey[-6])
      assert_not_operator(h, meth, 3)

      if Object.const_defined?(:Float)
        hh = entries.push([-7, :i], [-8.0, :f]).hash_for
        assert_not_operator(hh, meth, -7.0)
        assert_not_operator(hh, meth, -8)
        assert_operator(hh, meth, -8.0)
      end

      h.shift
      assert_not_operator(h, meth, pairs.key(0))

      h.delete(pairs.key(3))
      assert_not_operator(h, meth, pairs.key(3))

      k = HashKey[-31, callback: ->(*){h.clear}]
      assert_nothing_crashed{h.__send__(meth, k)}
    end
  end

  h = Hash.new{|h, k| h[1] = 1}
  assert_not_operator(h, meth, 1)
end

[%w[has_value? 14], %w[value? 24]].each do |meth, no|
  assert("Hash##{meth}", "15.2.13.4.#{no}") do
    [ar_entries, ht_entries].each do |entries|
      entries.push([HashKey[-5], -8], ["NIL", nil])
      h = entries.hash_for
      entries.each{|k, v| assert_operator(h, meth, v)}
      assert_operator(h, meth, -8.0) if Object.const_defined?(:Float)
      assert_not_operator(h, meth, "-8")

      h.shift
      assert_not_operator(h, meth, entries.value(0))

      h.delete(entries.key(3))
      assert_not_operator(h, meth, entries.value(3))

      v = HashKey[-31, callback: ->(*){h.clear}]
      assert_nothing_crashed{h.__send__(meth, v)}
    end
  end

  h = Hash.new{|h, k| h[1] = 1}
  assert_not_operator(h, meth, 1)
end

assert('Hash#initialize', '15.2.13.4.16') do
  h = Hash.new
  assert_equal(Hash, h.class)
  assert_not_operator(h, :key?, 1)
  assert_equal(nil, h[1])

  h = Hash.new([8])
  assert_not_operator(h, :key?, 1)
  assert_equal([8], h[1])
  assert_same(h[1], h[2])

  k = "key"
  h = Hash.new{|hash, key| [hash, key]}
  assert_not_operator(h, :key?, k)
  assert_equal([h, k], h[k])
  assert_same(h, h[k][0])
  assert_same(k, h[k][1])

  assert_raise(ArgumentError){Hash.new(1,2)}
  assert_raise(ArgumentError){Hash.new(1){}}
end

[%w[keys 19], %w[values 28]].each do |meth, no|
  assert("Hash##{meth}", "15.2.13.4.#{no}") do
    [ar_entries, ht_entries].each do |entries|
      h = entries.hash_for
      assert_equal(entries.__send__(meth), h.__send__(meth))

      h.shift
      entries.shift
      h.delete(entries.delete_at(3)[0])
      assert_equal(entries.__send__(meth), h.__send__(meth))
    end

    assert_equal([], {}.__send__(meth))
  end
end

[%w[length 20], %w[size 25]].each do |meth, no|
  assert("Hash##{meth}", "15.2.13.4.#{no}") do
    [ar_entries, ht_entries].each do |entries|
      h = entries.hash_for
      assert_equal(entries.size, h.__send__(meth))

      h.shift
      entries.shift
      h.delete(entries.delete_at(3)[0])
      assert_equal(entries.size, h.__send__(meth))
    end

    assert_equal(0, Hash.new.__send__(meth))
  end
end

assert('Hash#merge', '15.2.13.4.22') do
  cls = Class.new(Hash){attr_accessor :foo}
  ar_pairs = HashEntries[
    ["id", 32],
    [nil, :two],
    ["&", "&amp;"],
    [:same_key, :AR],
    [HashKey[2], 20],
  ]
  ht_pairs = HashEntries[
    *(1..20).map{[_1, _1.to_s]},
    [:same_key, :HT],
    [:age, 32],
    [HashKey[5], 500],
  ]

  [[ar_pairs, ht_pairs], [ht_pairs, ar_pairs]].each do |entries1, entries2|
    h1 = entries1.hash_for(cls.new(:dv1)){|h| h.foo = :iv1}.freeze
    h2 = entries2.hash_for(Hash.new(:dv2)).freeze
    h3 = h1.merge(h2)
    assert_equal(entries1, h1.to_a)
    assert_equal(merge_entries!(entries1.dup2, entries2), h3.to_a)
    assert_equal(cls, h3.class)
    assert_equal(:dv1, h3.default)
    assert_equal(:iv1, h3.foo)

    h3 = {}.merge(entries2.hash_for(cls.new))
    assert_equal(merge_entries!([], entries2), h3.to_a)
    assert_equal(Hash, h3.class)

    h3 = entries1.hash_for.merge({})
    assert_equal(merge_entries!(entries1.dup2, []), h3.to_a)

    h1 = entries1.hash_for
    h2 = entries2.hash_for
    h3 = h1.merge(h2){|k, v1, v2| [k, v1, v2]}
    exp = merge_entries!(entries1.dup2, entries2)
    exp.find{|k, _| k == :same_key}[1] = [
      :same_key,
      entries1.find{|k, _| k == :same_key}[1],
      entries2.find{|k, _| k == :same_key}[1],
    ]
    assert_equal(exp, h3.to_a)

    assert_raise(TypeError){entries1.hash_for.merge("str")}

    k2 = HashKey[-2]
    entries2 << [k2, 234]
    h1, h2 = entries1.hash_for, entries2.hash_for
    k2.callback = ->(name, *){h1.clear if name == :eql?}
    assert_nothing_crashed{h1.merge(h2)}
    h1, h2 = entries1.hash_for, entries2.hash_for
    k2.callback = ->(name, *){h2.clear if name == :eql?}
    assert_nothing_crashed{h1.merge(h2)}
    h1, h2 = entries1.hash_for, entries2.hash_for
    k2.callback = ->(name, *){h1.clear if name == :hash}
    assert_nothing_crashed{h1.merge(h2)}
    h1, h2 = entries1.hash_for, entries2.hash_for
    k2.callback = ->(name, *){h2.clear if name == :hash}
    assert_nothing_crashed{h1.merge(h2)}

    # single arguments
    assert_equal({a:1,b:2}, {a:1}.merge({b:2}))
    # multiple arguments
    assert_equal({a:1,b:2,c:3}, {a:1}.merge({b:2},{c:3}))
  end
end

assert("Hash#replace", "15.2.13.4.23") do
  cls = Class.new(Hash){attr_accessor :foo}
  e = [ar_entries, ht_entries]
  [e, e.reverse].each do |entries1, entries2|
    h1 = entries1.hash_for
    assert_same(h1, h1.replace(h1))
    assert_equal(entries1, h1.to_a)

    h1 = {}
    assert_same(h1, h1.replace(entries2.hash_for))
    assert_equal(entries2, h1.to_a)

    h1 = entries1.hash_for
    assert_same(h1, h1.replace({}))
    assert_predicate(h1, :empty?)

    pairs2 = entries2.dup
    h2 = pairs2.hash_for
    pairs2.shift
    h2.shift
    h2.delete(pairs2.delete_at(2)[0])
    h2.delete(pairs2.delete_at(4)[0])
    h1 = entries1.hash_for
    assert_same(h1, h1.replace(h2))
    assert_equal(pairs2, h1.to_a)

    h1 = entries1.hash_for(Hash.new(10))
    h2 = entries2.hash_for(Hash.new(20))
    assert_same(h1, h1.replace(h2))
    assert_equal(entries2, h1.to_a)
    assert_equal(20, h1.default)

    h1 = entries1.hash_for(Hash.new{_2})
    h2 = entries2.hash_for(Hash.new{_2.to_s})
    assert_same(h1, h1.replace(h2))
    assert_equal(entries2, h1.to_a)
    assert_equal("-11", h1[-11])

    h1 = entries1.hash_for(Hash.new(10))
    h2 = entries2.hash_for(Hash.new{_2.to_s})
    assert_same(h1, h1.replace(h2))
    assert_equal(entries2, h1.to_a)
    assert_equal("-11", h1[-11])

    h1 = entries1.hash_for(Hash.new{_2})
    h2 = entries2.hash_for(Hash.new(20))
    assert_same(h1, h1.replace(h2))
    assert_equal(entries2, h1.to_a)
    assert_equal(20, h1[-1])

    h1 = entries1.hash_for(cls.new(10)){|h| h.foo = 41}
    h2 = entries2.hash_for(cls.new(20)){|h| h.foo = 42}.freeze
    assert_same(h1, h1.replace(h2))
    assert_equal(entries2, h1.to_a)
    assert_equal(20, h1.default)
    assert_equal(41, h1.foo)

    h1 = entries1.hash_for
    h2 = entries2.hash_for(cls.new)
    assert_same(h1, h1.replace(h2))
    assert_equal(entries2, h1.to_a)

    assert_raise(TypeError){entries1.hash_for.replace([])}

    k2 = HashKey[-2]
    pairs2 = entries2.dup
    pairs2 << [k2, 23]
    h1 = entries1.hash_for
    h2 = pairs2.hash_for
    k2.callback = ->(*){h1.clear; h2.clear}
    assert_nothing_crashed{h1.replace(h2)}

    assert_raise(FrozenError){h1.freeze.replace(h1)}
    assert_raise(FrozenError){{}.freeze.replace({})}
  end
end

assert('Hash#shift', '15.2.13.4.24') do
  [ar_entries, ht_entries].each do |entries|
    pairs = entries.dup
    h = pairs.hash_for
    h.delete(pairs.delete_at(0)[0])
    h.delete(pairs.delete_at(3)[0])
    until pairs.empty?
      exp = pairs.shift
      act = h.shift
      assert_equal(Array, act.class)
      assert_equal(exp, act)
      assert_equal(exp.size, act.size)
      assert_not_operator(h, :key?, exp[0])
    end

    assert_equal(nil, h.shift)
    assert_equal(0, h.size)

    h.default = -456
    assert_equal(nil, h.shift)
    assert_equal(0, h.size)

    h.freeze
    assert_raise(FrozenError){h.shift}
  end

  h = Hash.new{|h, k| [h, k]}
  assert_equal(0, h.size)
  assert_equal(nil, h.shift)
end

# Not ISO specified

%i[reject select].each do |meth|
  assert("Hash##{meth}") do
    cls = Class.new(Hash){attr_accessor :foo}
    [ar_entries, ht_entries].each do |entries|
      params = nil
      filter = ->((k, v)) do
        params << [k, v]
        String === k
      end

      h = entries.hash_for(cls.new(1))
      params = []
      ret = h.__send__(meth, &filter)
      assert_equal(entries, params)
      assert_equal(entries, h.to_a)
      assert_equal(1, h.default)
      assert_equal(entries.__send__(meth, &filter), ret.to_a)
      assert_equal(Hash, ret.class)
      assert_equal(nil, ret.default)

      params = []
      assert_predicate({}.__send__(meth, &filter), :empty?)
      assert_predicate(params, :empty?)
    end
  end
end

%i[reject! select!].each do |meth|
  assert("Hash##{meth}") do
    [ar_entries, ht_entries].each do |entries|
      params = nil
      filter = ->((k, v)) do
        params << [k, v]
        String === k
      end

      pairs = entries.dup << ["_str", 5]
      h = pairs.hash_for(Hash.new(1))
      params = []
      ret = h.__send__(meth, &filter)
      assert_same(h, ret)
      assert_equal(pairs, params)
      assert_equal(pairs.__send__(meth.to_s[0..-2], &filter), h.to_a)
      assert_equal(1, h.default)

      h = pairs.hash_for
      ret = h.__send__(meth){meth == :select!}
      assert_nil(ret)
      assert_equal(pairs, h.to_a)

      assert_raise(FrozenError){h.freeze.__send__(meth, &filter)}
    end

    h = {}
    assert_nil(h.__send__(meth){})
    assert_predicate(h, :empty?)
  end
end

%i[inspect to_s].each do |meth|
  assert("Hash##{meth}") do
    assert_equal('{}', Hash.new.__send__(meth))

    h1 = {s: 0, a: [1,2], 37 => :b, d: "del", "c" => nil}
    h1.shift
    h1.delete(:d)
    s1 = 'a: [1, 2], 37 => :b, "c" => nil'
    h2 = Hash.new(100)

    (1..14).each{h2[_1] = _1 * 2}
    h2 = {**h2, **h1}
    s2 = "1 => 2, 2 => 4, 3 => 6, 4 => 8, 5 => 10, 6 => 12, 7 => 14, 8 => 16, " \
         "9 => 18, 10 => 20, 11 => 22, 12 => 24, 13 => 26, 14 => 28, #{s1}"

    [[h1, s1], [h2, s2]].each do |h, s|
      assert_equal("{#{s}}", h.__send__(meth))

      hh = {}
      hh[:recur] = hh
      h.each{|k, v| hh[k] = v}
      assert_equal("{recur: {...}, #{s}}", hh.__send__(meth))

      hh = h.dup
      hh[hh] = :recur
      assert_equal("{#{s}, {...} => :recur}", hh.__send__(meth))
    end
  end
end

assert('Hash#rehash') do
  cls = Class.new(Hash){attr_accessor :foo}
  [ar_entries, ht_entries].each do |entries|
    k1, k2, k3 = HashKey[-1], HashKey[-2], HashKey[-3]
    pairs = entries.dup.push(
      [-4, -40],
      [HashKey[-11], -5],
      [:_del, "_del"],
      [k1, :_k1],
      ["_a", "_b"],
      [k2, :_k2],
      ["_c", "_d"],
      [HashKey[-22], -21],
      [k3, :_k3],
    )
    h = pairs.hash_for(cls.new(:defvar)){|h| h.foo = "f"}
    k1.value, k2.value, k3.value = -11, -11, -22
    pairs1 = pairs.dup
    pairs1.delete([:_del, h.delete(:_del)])
    exp_pairs1 = pairs1.hash_for.to_a
    assert_same(h, h.rehash)
    assert_equal(exp_pairs1, h.to_a)
    assert_equal(exp_pairs1.size, h.size)
    assert_equal(:defvar, h.default)
    assert_equal("f", h.foo)
    exp_pairs1.each {|k, v| assert_equal(v, h[k])}

    # If an error occurs during rehash, at least the entry list is not broken.
    k1.value, k2.value, k3.value = -1, -2, -3
    h = pairs.hash_for
    k1.value = -11
    pairs2 = pairs.dup
    pairs2.delete([:_del, h.delete(:_del)])
    exp_pairs2 = pairs2.hash_for.to_a
    k2.error = :eql?
    assert_raise{h.rehash}
    act_pairs2 = h.to_a
    unless pairs2 == act_pairs2 && pairs2.size == h.size
      assert_equal(exp_pairs2, act_pairs2)
      assert_equal(exp_pairs2.size, h.size)
    end

    k1.value = -1
    k2.error = false
    h = pairs.hash_for
    k1.callback = ->(name, *){h.clear if name == :eql?}
    assert_nothing_crashed{h.rehash}
    k1.callback = ->(name, *){h.clear if name == :hash}
    assert_nothing_crashed{h.rehash}
  end

  h = {}
  assert_same(h, h.rehash)
  assert_predicate(h, :empty?)

  h = {}
  (1..17).each{h[_1] = _1 * 2}
  (2..16).each{h.delete(_1)}
  assert_same(h, h.rehash)
  assert_equal([[1, 2], [17, 34]], h.to_a)
  assert_equal(2, h.size)
  [1, 17].each{assert_equal(_1 * 2, h[_1])}
end

assert('Hash iteration with entries deleted ahead of the cursor') do
  # Regression for GHSA-jfmr-44fc-gfhg: a block that deletes not-yet-visited
  # entries during iteration must not let the entry scan run past the end of
  # the entry array (heap out-of-bounds read / type confusion).
  h = {}
  20.times { |i| h[i] = i * 10 }
  visited = 0
  h.each do |k, v|
    visited += 1
    # On the first call, delete every entry except the current one.
    h.keys.each { |kk| h.delete(kk) unless kk == k } if visited == 1
  end
  assert_true(visited >= 1)   # must have run without crashing
  assert_equal(1, h.size)     # only the first-visited entry remains

  # Deleting the current entry on every step (delete_if-style) stays correct.
  g = {}
  20.times { |i| g[i] = i }
  g.each { |k, v| g.delete(k) }
  assert_predicate(g, :empty?)
end

assert('Hash#assoc, Hash#rassoc') do
  h = {foo: 0, bar: 1, baz: 2}
  assert_equal([:bar, 1], h.assoc(:bar))
  assert_nil(h.assoc(:quux))
  assert_equal([:foo, 0], h.rassoc(0))
  assert_nil(h.rassoc(4))
end

assert('#== receiver should be specified value') do
  [ar_entries, ht_entries].each do |entries|
    h = entries.hash_for
    v0 = HashKey[-99]
    h[-99] = v0

    v1 = HashKey[-3, error: :==]
    %i[has_value? value?].each{|m| assert_raise{h.__send__(m, v1)}}
    v0.error = :==
    v1.error = false
    %i[has_value? value?].each{|m| assert_nothing_raised{h.__send__(m, v1)}}
  end
end

assert('test value omission') do
  x = 1
  y = 2
  assert_equal({x:1, y:2}, {x:, y:})
end
