summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLamont Granquist <lamont@scriptkiddie.org>2015-10-13 09:27:31 -0700
committerLamont Granquist <lamont@scriptkiddie.org>2016-01-07 11:24:28 -0800
commit7637515eb55dd982d6192d055af7e746cc61f0ae (patch)
treed85184fb8fc2de15978c499680240d288ff1313d
parente7ca84129e47d8a60ded1358757005e2af587ba4 (diff)
downloadchef-7637515eb55dd982d6192d055af7e746cc61f0ae.tar.gz
more tests, more cleanup
add hash tests from ruby, remove enumerable, cleanup some code, add test showing what needs to be done to validate that hash and array methods that return/yield elements need to return decorated objects when appropriate (e.g. Array #map needs to use the Decorator#each method and not delegate).
-rw-r--r--lib/chef/node/attribute.rb92
-rw-r--r--lib/chef/node/attribute_cell.rb28
-rw-r--r--lib/chef/node/attribute_trait/convert_value.rb1
-rw-r--r--lib/chef/node/attribute_trait/decorator.rb107
-rw-r--r--spec/unit/node/attribute_trait/decorator_hash_spec.rb1327
-rw-r--r--spec/unit/node/attribute_trait/decorator_spec.rb45
6 files changed, 1486 insertions, 114 deletions
diff --git a/lib/chef/node/attribute.rb b/lib/chef/node/attribute.rb
index 3e469e70c3..1c1cbc36a9 100644
--- a/lib/chef/node/attribute.rb
+++ b/lib/chef/node/attribute.rb
@@ -46,8 +46,8 @@ class Chef
env_override: {},
force_override: {},
automatic: automatic || {},
- node: __node,
- deep_merge_cache: __deep_merge_cache
+ deep_merge_cache: __deep_merge_cache,
+ node: __node
)
end
@@ -79,17 +79,17 @@ class Chef
def normal_unless(*args)
return UnMethodChain.new(wrapped_object: self, method_to_call: :normal_unless) unless args.length > 0
- write_value(:normal, *args) if args_to_cell(*args).nil?
+ write_value(:normal, *args) if safe_reader(*args[0...-1]).nil?
end
def default_unless(*args)
return UnMethodChain.new(wrapped_object: self, method_to_call: :default_unless) unless args.length > 0
- write_value(:default, *args) if args_to_cell(*args).nil?
+ write_value(:default, *args) if safe_reader(*args[0...-1]).nil?
end
def override_unless(*args)
return UnMethodChain.new(wrapped_object: self, method_to_call: :override_unless) unless args.length > 0
- write_value(:override, *args) if args_to_cell(*args).nil?
+ write_value(:override, *args) if safe_reader(*args[0...-1]).nil?
end
# should deprecate all of these, epecially #set
@@ -104,16 +104,8 @@ class Chef
alias_method :automatic_attrs, :automatic
alias_method :automatic_attrs=, :automatic=
- def has_key?(key)
- self.public_send(:key?, key)
- end
-
- alias_method :attribute?, :has_key?
- alias_method :member?, :has_key?
-
- def include?(val)
- wrapped_object.public_send(:include?, val)
- end
+ alias_method :attribute?, :include?
+ alias_method :member?, :include?
def each_attribute(&block)
self.public_send(:each, &block)
@@ -146,6 +138,8 @@ class Chef
end
end
+ # FIXME: doesn't decorator handle all this delgated crap now?
+ # vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
def to_s
wrapped_object.to_s
end
@@ -175,31 +169,35 @@ class Chef
# - does not autovivify
# - does not trainwreck if interior keys do not exist
def rm(*args)
- cell = args_to_cell(*args)
- return nil unless cell.is_a?(Hash)
- ret = cell[args.last]
- rm_default(*args)
- rm_normal(*args)
- rm_override(*args)
+ with_deep_merged_return_value(self, *args) do
+ rm_default(*args)
+ rm_normal(*args)
+ rm_override(*args)
+ end
+ end
+
+ def with_deep_merged_return_value(obj, *args)
+ hash = obj.safe_reader(*args[0...-1])
+ return nil unless hash.is_a?(Hash)
+ ret = hash[args.last]
+ yield
ret
end
+ private :with_deep_merged_return_value
+
# clears attributes from all default precedence levels
#
# - similar to: force_default!['foo']['bar'].delete('baz')
# - does not autovivify
# - does not trainwreck if interior keys do not exist
def rm_default(*args)
- cell = args_to_cell(*args)
- return nil unless cell.is_a?(Hash)
- ret = if cell.combined_default.is_a?(Hash)
- cell.combined_default[args.last]
- end
- cell.default.delete(args.last) if cell.default.is_a?(Hash)
- cell.role_default.delete(args.last) if cell.role_default.is_a?(Hash)
- cell.env_default.delete(args.last) if cell.env_default.is_a?(Hash)
- cell.force_default.delete(args.last) if cell.force_default.is_a?(Hash)
- ret
+ with_deep_merged_return_value(combined_default, *args) do
+ default.safe_delete(*args)
+ role_default.safe_delete(*args)
+ env_default.safe_delete(*args)
+ force_default.safe_delete(*args)
+ end
end
# clears attributes from normal precedence
@@ -208,9 +206,7 @@ class Chef
# - does not autovivify
# - does not trainwreck if interior keys do not exist
def rm_normal(*args)
- cell = args_to_cell(*args)
- return nil unless cell.is_a?(Hash)
- cell.normal.delete(args.last) if cell.normal.is_a?(Hash)
+ normal.safe_delete(*args)
end
# clears attributes from all override precedence levels
@@ -219,32 +215,14 @@ class Chef
# - does not autovivify
# - does not trainwreck if interior keys do not exist
def rm_override(*args)
- cell = args_to_cell(*args)
- return nil unless cell.is_a?(Hash)
- ret = if cell.combined_override.is_a?(Hash)
- cell.combined_override[args.last]
- end
- cell.override.delete(args.last) if cell.override.is_a?(Hash)
- cell.role_override.delete(args.last) if cell.role_override.is_a?(Hash)
- cell.env_override.delete(args.last) if cell.env_override.is_a?(Hash)
- cell.force_override.delete(args.last) if cell.force_override.is_a?(Hash)
- ret
- end
-
- def args_to_cell(*args)
- begin
- last = args.pop
- cell = args.inject(self) do |memo, arg|
- memo[arg]
- end
- cell
- rescue NoMethodError
- nil
+ with_deep_merged_return_value(combined_override, *args) do
+ override.safe_delete(*args)
+ role_override.safe_delete(*args)
+ env_override.safe_delete(*args)
+ force_override.safe_delete(*args)
end
end
- private :args_to_cell
-
# FIXME: should probably be another decorator behavior that changes :[] and :[]= to wipe
# out intermediate non-hash things and replace them with hashes in addition to autovivifying
# and/or add #hashifying_accessor and #hashifying_writer methods directly to VividMash.
diff --git a/lib/chef/node/attribute_cell.rb b/lib/chef/node/attribute_cell.rb
index 9252baf005..a3e4b5703a 100644
--- a/lib/chef/node/attribute_cell.rb
+++ b/lib/chef/node/attribute_cell.rb
@@ -171,6 +171,34 @@ class Chef
end
end
+ # perf
+ def include?(key)
+ if self.is_a?(Hash)
+ merged_hash_has_key?(key)
+ else
+ as_simple_object.include?(key)
+ end
+ end
+
+ # perf
+ def member?(key)
+ if self.is_a?(Hash)
+ merged_hash_has_key?(key)
+ else
+ as_simple_object.member?(key)
+ end
+ end
+
+ # perf
+ def has_key?(key)
+ if self.is_a?(Hash)
+ merged_hash_has_key?(key)
+ else
+ as_simple_object.has_key?(key)
+ end
+ end
+
+
def method_missing(method, *args, &block)
as_simple_object.public_send(method, *args, &block)
end
diff --git a/lib/chef/node/attribute_trait/convert_value.rb b/lib/chef/node/attribute_trait/convert_value.rb
index 9e59fcceb5..4e9d1f8a79 100644
--- a/lib/chef/node/attribute_trait/convert_value.rb
+++ b/lib/chef/node/attribute_trait/convert_value.rb
@@ -30,6 +30,7 @@ class Chef
alias_method :has_key?, :key?
alias_method :member?, :key?
+ #alias_method :include?, :key?
def delete(key)
super(convert_key(key))
diff --git a/lib/chef/node/attribute_trait/decorator.rb b/lib/chef/node/attribute_trait/decorator.rb
index c8510a4c69..6effd389d8 100644
--- a/lib/chef/node/attribute_trait/decorator.rb
+++ b/lib/chef/node/attribute_trait/decorator.rb
@@ -3,7 +3,30 @@ class Chef
class AttributeTrait
module Decorator
attr_accessor :wrapped_object
- include Enumerable
+
+ #
+ # Delegate methods common to Array and Hash
+ # - this is done for speed over method_missing
+ # - some of these methods are overridden later with different semantics
+ #
+ methods = ( Array.instance_methods & Hash.instance_methods ) - Object.instance_methods +
+ [ :!, :!=, :<=>, :==, :===, :eql?, :to_s, :hash ]
+
+ methods.each do |method|
+ define_method method do |*args, &block|
+ wrapped_object.public_send(method, *args, &block)
+ end
+ end
+
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ def [](*args)
+ new(wrapped_object: Hash[ *args ])
+ end
+ end
def initialize(wrapped_object: nil, **args)
@wrapped_object = wrapped_object
@@ -42,11 +65,26 @@ class Chef
end
def regular_reader(*path)
- maybe_decorated_value(
+ ret = maybe_decorated_value(
path.inject(wrapped_object) { |memo, key| memo[key] }
)
end
+ def safe_reader(*path)
+ begin
+ regular_reader(*path)
+ rescue NoMethodError
+ nil
+ end
+ end
+
+ def safe_delete(*path)
+ last = path.pop
+ hash = safe_reader(*path)
+ return nil unless hash.is_a?(Hash)
+ hash.delete(last)
+ end
+
def [](key)
maybe_decorated_value(wrapped_object[key])
end
@@ -74,9 +112,9 @@ class Chef
def respond_to?(method, include_private = false)
# since we define these methods, :respond_to_missing? doesn't work.
- return false if is_a?(Array) && method == :to_hash
- return false if is_a?(Hash) && method == :to_ary
return false if is_a?(Array) && method == :each_pair
+ return false if is_a?(Array) && method == :key?
+ return false if is_a?(Array) && method == :has_key?
wrapped_object.respond_to?(method, false) || super
end
@@ -84,62 +122,14 @@ class Chef
wrapped_object.respond_to?(method, false) || super
end
- # avoid method_missing perf hit
- def delete(key)
- wrapped_object.delete(key)
- end
-
- # avoid method_missing perf hit
- def clear
- wrapped_object.clear
- end
-
- def to_s
- wrapped_object.to_s
- end
-
- def to_h
- wrapped_object.to_h
- end
-
- def to_hash
- wrapped_object.to_hash
- end
-
- def to_a
- wrapped_object.to_a
- end
-
- def to_ary
- wrapped_object.to_ary
- end
def initialize_copy(source)
super
@wrapped_object = safe_dup(source.wrapped_object)
end
- # http://blog.rubybestpractices.com/posts/rklemme/018-Complete_Class.html
-
- def eql?(other)
- wrapped_object.eql?(other)
- end
-
- def ==(other)
- wrapped_object == other
- end
-
- def ===(other)
- wrapped_object === other
- end
-
- def []=(key, value)
- wrapped_object[key] = value
- end
-
- # performance
def include?(key)
- wrapped_object.key?(key)
+ wrapped_object.include?(key)
end
# performance
@@ -147,9 +137,9 @@ class Chef
wrapped_object.key?(key)
end
- # when we're a Hash pick up Hash#select which is different from Enumerable#select
- def select(&block)
- wrapped_object.select(&block)
+ # performance
+ def has_key?(key)
+ wrapped_object.has_key?(key)
end
# we need to be careful to return decorated values when appropriate
@@ -175,6 +165,7 @@ class Chef
end
end
+ # return decorated values when appropriate
alias_method :each_pair, :each
# nil, true, false and Fixnums are not dup'able
@@ -188,7 +179,9 @@ class Chef
if is_a?(Array)
map(&method(:safe_dup))
elsif is_a?(Hash)
- Hash[map { |k, v| [ safe_dup(k), safe_dup(v) ] } ]
+ h = {}
+ each { |k, v| h[safe_dup(k)] = safe_dup(v) }
+ h
else
safe_dup(wrapped_object)
end
diff --git a/spec/unit/node/attribute_trait/decorator_hash_spec.rb b/spec/unit/node/attribute_trait/decorator_hash_spec.rb
new file mode 100644
index 0000000000..c0c9a13a00
--- /dev/null
+++ b/spec/unit/node/attribute_trait/decorator_hash_spec.rb
@@ -0,0 +1,1327 @@
+
+# based on https://github.com/ruby/ruby/blob/d41838c8d4c5b6fe956b07ade79f2d4954fd7c68/test/ruby/test_hash.rb
+# see https://github.com/ruby/ruby/blob/2cd6800fd8437b1f862f3f5c44db877159271d17/COPYING
+# for the applicable ruby license.
+
+require 'spec_helper'
+
+describe Chef::Node::AttributeTrait::Decorator do
+
+ class Test
+ include Chef::Node::AttributeTrait::Decorator
+ end
+
+ def hash_new(obj = nil, &block)
+ d = Test.new
+ d.wrapped_object = Hash.new(obj ? obj : block)
+ d
+ end
+
+ def hash_bracket(*args)
+ Test[*args]
+ end
+
+ it "Hash" do
+ x = hash_bracket(1=>2, 2=>4, 3=>6)
+ y = hash_bracket(1=>2, 2=>4, 3=>6)
+
+ expect(2).to eql(x[1])
+
+ expect {
+ for k,v in y
+ raise if k*2 != v
+ end
+ }.not_to raise_error
+
+ expect(3).to eql(x.length)
+ expect(x.send(:has_key?, 1)).to be true
+ expect(x.send(:has_value?, 4)).to be true
+ expect([4,6]).to eql(x.values_at(2,3))
+ expect({1=>2, 2=>4, 3=>6}).to eql(x)
+
+ z = y.keys.join(":")
+ expect("1:2:3").to eql(z)
+
+ z = y.values.join(":")
+ expect("2:4:6").to eql(z)
+ expect(x).to eql(y)
+
+ y.shift
+ expect(2).to eql(y.length)
+
+ z = [1,2]
+ y[z] = 256
+ expect(256).to eql(y[z])
+
+ x = hash_new(0)
+ x[1] = 1
+ expect(1).to eql(x[1])
+ expect(0).to eql(x[2])
+
+ #x = hash_new([])
+ #expect([]).to eql(x[22])
+ #expect(x[22]).to equal(x[22])
+
+ #x = hash_new{[]}
+ #expect([]).to eql(x[22])
+ #expect(x[22]).not_to equal(x[22])
+
+ #x = hash_new{|h,kk| z = kk; h[kk] = kk*2}
+ #z = 0
+ #expect(44).to eql(x[22])
+ #expect(22).to eql(z)
+ #z = 0
+ #expect(44).to eql(x[22])
+ #expect(0).to eql(z)
+ #x.default = 5
+ #expect(5).to eql(x[23])
+
+ #x = hash_new
+ #def x.default(k)
+ # $z = k
+ # self[k] = k*2
+ #end
+ #$z = 0
+ #expect(44).to eql(x[22])
+ #expect(22).to eql($z)
+ #$z = 0
+ #expect(44).to eql(x[22])
+ #expect(0).to eql($z)
+ end
+
+ before do
+ @h = hash_bracket(
+ 1 => 'one', 2 => 'two', 3 => 'three',
+ self => 'self', true => 'true', nil => 'nil',
+ 'nil' => nil
+ )
+ end
+
+# it "#initialize_copy bad" do
+# h = hash_bracket(Class.new(Hash) {
+# def initialize_copy(h)
+# super(Object.new)
+# end
+# }.new)
+# expect { h.dup }.to raise_error(TypeError)
+# end
+
+ it "#initialize_copy clear" do
+ h = hash_bracket(1=>2)
+ d = Test.new
+ d.wrapped_object = {}
+ h.instance_eval {initialize_copy(d)}
+ expect(h.empty?).to be true
+ end
+
+ it "#initialize_copy self" do
+ h = hash_bracket(1=>2)
+ h.instance_eval {initialize_copy(h)}
+ expect(2).to eql(h[1])
+ end
+
+# it "#dup will rehash" do
+# skip "we deep-dup"
+# set1 = hash_bracket()
+# set2 = hash_bracket(set1 => true)
+#
+# set1[set1] = true
+#
+# expect(set2).to eql(set2.dup)
+# end
+
+# def test_s_AREF
+# h = @cls["a" => 100, "b" => 200]
+# expect(100).to eql(h['a'])
+# expect(200).to eql(h['b'])
+# assert_nil(h['c'])
+#
+# h = @cls.[]("a" => 100, "b" => 200)
+# expect(100).to eql(h['a'])
+# expect(200).to eql(h['b'])
+# assert_nil(h['c'])
+# end
+
+ it "#new" do
+ h = hash_new
+ expect(h.kind_of?(Hash)).to be true
+ expect(h.default).to be nil
+ expect(h['spurious']).to be nil
+
+ h = hash_new('default')
+ expect(h.kind_of?(Hash)).to be true
+ expect(h.default).to eql('default')
+ expect(h['spurious']).to eql('default')
+ end
+
+ it "#[]" do # '[]'
+ t = Time.now
+ h = hash_bracket(
+ 1 => 'one', 2 => 'two', 3 => 'three',
+ self => 'self', t => 'time', nil => 'nil',
+ 'nil' => nil
+ )
+
+ expect('one').to eql( h[1])
+ expect('two').to eql( h[2])
+ expect('three').to eql(h[3])
+ expect('self').to eql( h[self])
+ expect('time').to eql( h[t])
+ expect('nil').to eql( h[nil])
+ expect(nil).to eql( h['nil'])
+ expect(nil).to eql( h['koala'])
+
+ h1 = h.dup
+ h1.default = :default
+
+ expect('one').to eql( h1[1])
+ expect('two').to eql( h1[2])
+ expect('three').to eql( h1[3])
+ #expect('self').to eql( h1[self])
+ expect('time').to eql( h1[t])
+ expect('nil').to eql( h1[nil])
+ expect(nil).to eql( h1['nil'])
+ expect(:default).to eql(h1['koala'])
+ end
+
+ it "#[]=" do
+ t = Time.now
+ h = hash_new
+ h[1] = 'one'
+ h[2] = 'two'
+ h[3] = 'three'
+ h[self] = 'self'
+ h[t] = 'time'
+ h[nil] = 'nil'
+ h['nil'] = nil
+ expect('one').to eql( h[1])
+ expect('two').to eql( h[2])
+ expect('three').to eql(h[3])
+ expect('self').to eql( h[self])
+ expect('time').to eql( h[t])
+ expect('nil').to eql( h[nil])
+ expect(nil).to eql( h['nil'])
+ expect(nil).to eql( h['koala'])
+
+ h[1] = 1
+ h[nil] = 99
+ h['nil'] = nil
+ z = [1,2]
+ h[z] = 256
+ expect(1).to eql( h[1])
+ expect('two').to eql( h[2])
+ expect('three').to eql(h[3])
+ expect('self').to eql( h[self])
+ expect('time').to eql( h[t])
+ expect(99).to eql( h[nil])
+ expect(nil).to eql( h['nil'])
+ expect(nil).to eql( h['koala'])
+ expect(256).to eql( h[z])
+ end
+
+# it "#[] fstring key" do
+# skip "suspect this doesn't work by design with decorators"
+# h = hash_bracket("abc" => 1)
+# before = GC.stat(:total_allocated_objects)
+# 5.times{ h["abc"] }
+# expect(GC.stat(:total_allocated_objects)).to eql(before)
+# end
+
+ it "test_ASET_fstring_key" do
+ pending "seems odd this doesn't work"
+ a, b = hash_new, hash_new
+ expect(a["abc"] = 1).to eql(1)
+ expect(b["abc"] = 1).to eql(1)
+ expect(a.keys[0]).to equal(b.keys[0])
+ end
+
+ it "test_NEWHASH_fstring_key" do
+ a = hash_bracket("ABC" => :t)
+ b = hash_bracket("ABC" => :t)
+ expect(a.keys[0]).to equal(b.keys[0])
+ expect("ABC".freeze).to equal(a.keys[0])
+ end
+
+ it "#==" do # '=='
+ h1 = hash_bracket( "a" => 1, "c" => 2 )
+ h2 = hash_bracket( "a" => 1, "c" => 2, 7 => 35 )
+ h3 = hash_bracket( "a" => 1, "c" => 2, 7 => 35 )
+ h4 = hash_bracket( )
+ expect(h1).to be == h1
+ expect(h2).to be == h2
+ expect(h3).to be == h3
+ expect(h4).to be == h4
+ expect(h1).not_to be == h2
+ expect(h2).to be == h3
+ expect(h3).not_to be == h4
+ end
+
+ it "#clear" do
+ expect(@h.size).to be > 0
+ @h.clear
+ expect(0).to eql(@h.size)
+ expect(@h[1]).to be nil
+ end
+
+# it "test_clone" do
+# for taint in [ false, true ]
+# for frozen in [ false, true ]
+# a = @h.clone
+# a.taint if taint
+# a.freeze if frozen
+# b = a.clone
+#
+# expect(a).to eql(b)
+# expect(a).not_to eql(b)
+# expect(a.frozen?).to eql(b.frozen?)
+# expect(a.tainted?).to eql(b.tainted?)
+# end
+# end
+# end
+
+ it "#default" do
+ expect(@h.default).to be nil
+ h = hash_new(:xyzzy)
+ expect(:xyzzy).to eql(h.default)
+ end
+
+ it "#default=" do
+ expect(@h.default).to be nil
+ @h.default = :xyzzy
+ expect(:xyzzy).to eql(@h.default)
+ end
+
+ it "#delete" do
+ h1 = hash_bracket( 1 => 'one', 2 => 'two', true => 'true' )
+ h2 = hash_bracket( 1 => 'one', 2 => 'two' )
+ h3 = hash_bracket( 2 => 'two' )
+
+ expect('true').to eql(h1.delete(true))
+ expect(h2).to eql(h1)
+
+ expect('one').to eql(h1.delete(1))
+ expect(h3).to eql(h1)
+
+ expect('two').to eql(h1.delete(2))
+ expect(hash_bracket()).to eql(h1)
+
+ expect(h1.delete(99)).to be nil
+ expect(hash_bracket()).to eql(h1)
+
+ expect('default 99').to eql(h1.delete(99) {|i| "default #{i}" })
+ end
+
+ it "#delete_if" do
+ base = hash_bracket( 1 => 'one', 2 => false, true => 'true', 'cat' => 99 )
+ h1 = hash_bracket( 1 => 'one', 2 => false, true => 'true' )
+ h2 = hash_bracket( 2 => false, 'cat' => 99 )
+ h3 = hash_bracket( 2 => false )
+
+ h = base.dup
+ expect(h).to eql(h.delete_if { false })
+ expect(hash_bracket()).to eql(h.delete_if { true })
+
+ h = base.dup
+ expect(h1).to eql(h.delete_if {|k,v| k.instance_of?(String) })
+ expect(h1).to eql(h)
+
+ h = base.dup
+ expect(h2).to eql(h.delete_if {|k,v| v.instance_of?(String) })
+ expect(h2).to eql(h)
+
+ h = base.dup
+ expect(h3).to eql(h.delete_if {|k,v| v })
+ expect(h3).to eql(h)
+
+ h = base.dup
+ n = 0
+ h.delete_if {|*a|
+ n += 1
+ expect(2).to eql(a.size)
+ expect(base[a[0]]).to eql(a[1])
+ h.shift
+ true
+ }
+ expect(base.size).to eql(n)
+ end
+
+ it "#keep_if" do
+ h = hash_bracket(1=>2,3=>4,5=>6)
+ expect({3=>4,5=>6}).to eql(h.keep_if {|k,v| k + v >= 7 })
+ h = hash_bracket(1=>2,3=>4,5=>6)
+ expect({1=>2,3=>4,5=>6}).to eql(h.keep_if{true})
+ end
+
+# def test_dup
+# for taint in [ false, true ]
+# for frozen in [ false, true ]
+# a = @h.dup
+# a.taint if taint
+# a.freeze if frozen
+# b = a.dup
+#
+# expect(a).to eql(b)
+# assert_not_same(a, b)
+# expect(false).to eql(b.frozen?)
+# expect(a.tainted?).to eql(b.tainted?)
+# end
+# end
+# end
+
+ it "test_dup_equality" do
+ skip "we deep-dup which breaks this"
+ h = hash_bracket('k' => 'v')
+ expect(h).to eql(h.dup)
+ h1 = hash_bracket(h => 1)
+ expect(h1).to eql(h1.dup)
+ h[1] = 2
+ expect(h1).to eql(h1.dup)
+ end
+
+ it "#each" do
+ count = 0
+ hash_bracket().each { |k, v| count + 1 }
+ expect(0).to eql(count)
+
+ h = @h
+ h.each do |k, v|
+ expect(v).to eql(h.delete(k))
+ end
+ expect(hash_bracket()).to eql(h)
+
+ h = hash_bracket()
+ h[1] = 1
+ h[2] = 2
+ expect([[1,1],[2,2]]).to eql(h.each.to_a)
+ end
+
+ it "#each_key" do
+ count = 0
+ hash_bracket().each_key { |k| count + 1 }
+ expect(0).to eql(count)
+
+ h = @h
+ h.each_key do |k|
+ h.delete(k)
+ end
+ expect(hash_bracket()).to eql(h)
+ end
+
+ it "#each_pair" do
+ count = 0
+ hash_bracket().each_pair { |k, v| count + 1 }
+ expect(0).to eql(count)
+
+ h = @h
+ h.each_pair do |k, v|
+ expect(v).to eql(h.delete(k))
+ end
+ expect(hash_bracket()).to eql(h)
+ end
+
+ it "#each_value" do
+ res = []
+ hash_bracket().each_value { |v| res << v }
+ expect(0).to eql([].length)
+
+ @h.each_value { |v| res << v }
+ expect(0).to eql([].length)
+
+ expected = []
+ @h.each { |k, v| expected << v }
+
+ expect([]).to eql(expected - res)
+ expect([]).to eql(res - expected)
+ end
+
+ it "#empty?" do
+ expect(hash_bracket().empty?).to be true
+ expect(@h.empty?).to be false
+ end
+
+ it "#fetch" do
+ expect('gumbygumby').to eql(@h.fetch('gumby') {|k| k * 2 })
+ expect('pokey').to eql(@h.fetch('gumby', 'pokey'))
+
+ expect('one').to eql(@h.fetch(1))
+ expect(nil).to eql(@h.fetch('nil'))
+ expect('nil').to eql(@h.fetch(nil))
+ end
+
+ it "#fetch error" do
+ expect { hash_bracket().fetch(1) }.to raise_error(KeyError)
+ expect { @h.fetch('gumby') }.to raise_error(KeyError)
+ expect { @h.fetch('gumby'*20) }.to raise_error(
+ KeyError,
+ /key not found: "gumbygumby.*\.\.\.\z/
+ )
+ end
+
+ it "test_key2?" do
+ expect(hash_bracket().key?(1)).to be false
+ expect(hash_bracket().key?(nil)).to be false
+ expect(@h.send(:key?, nil)).to be true
+ expect(@h.send(:key?, 1)).to be true
+ expect(@h.key?('gumby')).to be false
+ end
+
+ it "#value?" do
+ expect(hash_bracket().value?(1)).to be false
+ expect(hash_bracket().value?(nil)).to be false
+ expect(@h.value?('one')).to be true
+ expect(@h.value?(nil)).to be true
+ expect(@h.value?('gumby')).to be false
+ end
+
+ it "#include?" do
+ expect(hash_bracket().include?(1)).to be false
+ expect(hash_bracket().include?(1)).to be false
+ expect(@h.send(:include?, nil)).to be true
+ expect(@h.send(:include?, 1)).to be true
+ expect(@h.send(:include?, 'gumby')).to be false
+ end
+
+ it "#key" do
+ expect(1).to eql( @h.key('one'))
+ expect(nil).to eql( @h.key('nil'))
+ expect('nil').to eql(@h.key(nil))
+
+ expect(nil).to eql( @h.key('gumby'))
+ expect(nil).to eql( hash_bracket().key('gumby'))
+ end
+
+ it "#values_at" do
+ res = @h.values_at('dog', 'cat', 'horse')
+ expect(3).to eql(res.length)
+ expect([nil, nil, nil]).to eql(res)
+
+ res = @h.values_at
+ expect(0).to eql(res.length)
+
+ res = @h.values_at(3, 2, 1, nil)
+ expect(res.length).to eql(4)
+ expect(res).to eql(%w( three two one nil ))
+
+ res = @h.values_at(3, 99, 1, nil)
+ expect(res.length).to eql(4)
+ expect(res).to eql(['three', nil, 'one', 'nil'])
+ end
+
+ it "#fetch_values" do
+ pending "ruby 2.3 only"
+ res = @h.fetch_values
+ expect(0).to eql(res.length)
+
+ res = @h.fetch_values(3, 2, 1, nil)
+ expect(4).to eql(res.length)
+ expect(%w( three two one nil )).to eql(res)
+
+ expect { @h.fetch_values(3, 'invalid') }.to raise_error(KeyError)
+
+ res = @h.fetch_values(3, 'invalid') { |k| k.upcase }
+ expect(%w( three INVALID )).to eql(res)
+ end
+
+ it "#invert" do
+ h = @h.invert
+ expect(1).to eql(h['one'])
+ expect(true).to eql(h['true'])
+ expect(nil).to eql( h['nil'])
+
+ h.each do |k, v|
+ expect(@h.send(:key?, v)).to be true # not true in general, but works here
+ end
+
+ h = hash_bracket('a' => 1, 'b' => 2, 'c' => 1).invert
+ expect(2).to eql(h.length)
+ expect(%w[a c].include?(h[1])).to be true
+ expect('b').to eql(h[2])
+ end
+
+ it "#key?" do
+ expect(hash_bracket().key?(1)).to be false
+ expect(hash_bracket().key?(nil)).to be false
+ expect(@h.send(:key?, nil)).to be true
+ expect(@h.send(:key?, 1)).to be true
+ expect(@h.key?('gumby')).to be false
+ end
+
+ it "#keys" do
+ expect([]).to eql(hash_bracket().keys)
+
+ keys = @h.keys
+ expected = []
+ @h.each { |k, v| expected << k }
+ expect([]).to eql(keys - expected)
+ expect([]).to eql(expected - keys)
+ end
+
+ it "#length?" do
+ expect(0).to eql(hash_bracket().length)
+ expect(7).to eql(@h.length)
+ end
+
+ it "#member?" do
+ expect(hash_bracket().member?(1)).to be false
+ expect(hash_bracket().member?(nil)).to be false
+ expect(@h.send(:member?, nil)).to be true
+ expect(@h.send(:member?, 1)).to be true
+ expect(@h.member?('gumby')).to be false
+ end
+
+ it "#rehash" do
+ a = [ "a", "b" ]
+ c = [ "c", "d" ]
+ h = hash_bracket( a => 100, c => 300 )
+ expect(100).to eql(h[a])
+ a[0] = "z"
+ expect(h[a]).to be nil
+ h.rehash
+ expect(100).to eql(h[a])
+ end
+
+ it "#reject" do
+ expect({3=>4,5=>6}).to eql(hash_bracket(1=>2,3=>4,5=>6).reject {|k, v| k + v < 7 })
+
+ base = hash_bracket( 1 => 'one', 2 => false, true => 'true', 'cat' => 99 )
+ h1 = hash_bracket( 1 => 'one', 2 => false, true => 'true' )
+ h2 = hash_bracket( 2 => false, 'cat' => 99 )
+ h3 = hash_bracket( 2 => false )
+
+ h = base.dup
+ expect(h).to eql(h.reject { false })
+ expect(hash_bracket()).to eql(h.reject { true })
+
+ h = base.dup
+ expect(h1).to eql(h.reject {|k,v| k.instance_of?(String) })
+
+ expect(h2).to eql(h.reject {|k,v| v.instance_of?(String) })
+
+ expect(h3).to eql(h.reject {|k,v| v })
+ expect(base).to eql(h)
+
+ h.instance_variable_set(:@foo, :foo)
+ h.default = 42
+ h.taint
+ #h = EnvUtil.suppress_warning {h.reject {false}}
+ #assert_instance_of(Hash, h)
+ #assert_not_predicate(h, :tainted?)
+ #assert_nil(h.default)
+ #assert_not_send([h, :instance_variable_defined?, :@foo])
+ end
+
+ it "#reject!" do
+ base = hash_bracket( 1 => 'one', 2 => false, true => 'true', 'cat' => 99 )
+ h1 = hash_bracket( 1 => 'one', 2 => false, true => 'true' )
+ h2 = hash_bracket( 2 => false, 'cat' => 99 )
+ h3 = hash_bracket( 2 => false )
+
+ h = base.dup
+ expect(nil).to eql(h.reject! { false })
+ expect(hash_bracket()).to eql( h.reject! { true })
+
+ h = base.dup
+ expect(h1).to eql(h.reject! {|k,v| k.instance_of?(String) })
+ expect(h1).to eql(h)
+
+ h = base.dup
+ expect(h2).to eql(h.reject! {|k,v| v.instance_of?(String) })
+ expect(h2).to eql(h)
+
+ h = base.dup
+ expect(h3).to eql(h.reject! {|k,v| v })
+ expect(h3).to eql(h)
+ end
+
+ it "#replace" do
+ h = hash_bracket( 1 => 2, 3 => 4 )
+ h1 = h.replace(hash_bracket( 9 => 8, 7 => 6 ))
+ expect(h).to eql(h1)
+ expect(8).to eql(h[9])
+ expect(6).to eql(h[7])
+ expect(h[1]).to be nil
+ expect(h[2]).to be nil
+ end
+
+ it "#replace bug9230" do
+ h = hash_bracket()
+ h.replace(hash_bracket())
+ expect(h.empty?).to be true
+
+ h = hash_bracket()
+ h.replace(hash_bracket().compare_by_identity)
+ expect(h.compare_by_identity?).to be true
+ end
+
+ it "#shift" do
+ h = @h.dup
+
+ @h.length.times {
+ k, v = h.shift
+ next if v == 'self' # FIXME: related to other failures with 'self'
+ expect(@h.send(:key?, k)).to be true
+ expect(@h[k]).to eql(v)
+ }
+
+ expect(0).to eql(h.length)
+ end
+
+ it "#size" do
+ expect(0).to eql(hash_bracket().length)
+ expect(7).to eql(@h.length)
+ end
+
+ it "#sort" do
+ h = hash_bracket().sort
+ expect([]).to eql(h)
+
+ h = hash_bracket( 1 => 1, 2 => 1 ).sort
+ expect([[1,1], [2,1]]).to eql(h)
+
+ h = hash_bracket( 'cat' => 'feline', 'ass' => 'asinine', 'bee' => 'beeline' )
+ h1 = h.sort
+ expect([ %w(ass asinine), %w(bee beeline), %w(cat feline)]).to eql(h1)
+ end
+
+# def test_store
+# t = Time.now
+# h = @cls.new
+# h.store(1, 'one')
+# h.store(2, 'two')
+# h.store(3, 'three')
+# h.store(self, 'self')
+# h.store(t, 'time')
+# h.store(nil, 'nil')
+# h.store('nil', nil)
+# expect('one').to eql( h[1])
+# expect('two').to eql( h[2])
+# expect('three').to eql(h[3])
+# expect('self').to eql( h[self])
+# expect('time').to eql( h[t])
+# expect('nil').to eql( h[nil])
+# expect(nil).to eql( h['nil'])
+# expect(nil).to eql( h['koala'])
+#
+# h.store(1, 1)
+# h.store(nil, 99)
+# h.store('nil', nil)
+# expect(1).to eql( h[1])
+# expect('two').to eql( h[2])
+# expect('three').to eql(h[3])
+# expect('self').to eql( h[self])
+# expect('time').to eql( h[t])
+# expect(99).to eql( h[nil])
+# expect(nil).to eql( h['nil'])
+# expect(nil).to eql( h['koala'])
+# end
+#
+# def test_to_a
+# expect([]).to eql(hash_bracket().to_a)
+# expect([[1,2]]).to eql(@cls[ 1=>2 ].to_a)
+# a = @cls[ 1=>2, 3=>4, 5=>6 ].to_a
+# expect([1,2]).to eql(a.delete([1,2]))
+# expect([3,4]).to eql(a.delete([3,4]))
+# expect([5,6]).to eql(a.delete([5,6]))
+# expect(0).to eql(a.length)
+#
+# h = @cls[ 1=>2, 3=>4, 5=>6 ]
+# h.taint
+# a = h.to_a
+# expect(true).to eql(a.tainted?)
+# end
+#
+# def test_to_hash
+# h = @h.to_hash
+# expect(@h).to eql(h)
+# assert_instance_of(@cls, h)
+# end
+#
+# def test_to_h
+# h = @h.to_h
+# expect(@h).to eql(h)
+# assert_instance_of(Hash, h)
+# end
+
+ it "nil#to_h" do
+ h = nil.to_h
+ expect(hash_new).to eql(h)
+ expect(h.default).to be nil
+ expect(h.default_proc).to be nil
+ end
+
+# it "#to_s" do
+# skip "we override inspect to have more info"
+# begin
+# h = hash_bracket( 1 => 2, "cat" => "dog", 1.5 => :fred )
+# expect(h.inspect).to eql(h.to_s)
+# $, = ":"
+# expect(h.inspect).to eql(h.to_s)
+# h = hash_bracket()
+# expect(h.inspect).to eql(h.to_s)
+# ensure
+# $, = nil
+# end
+# end
+
+ def test_update
+ h1 = hash_bracket( 1 => 2, 2 => 3, 3 => 4 )
+ h2 = hash_bracket( 2 => 'two', 4 => 'four' )
+
+ ha = hash_bracket( 1 => 2, 2 => 'two', 3 => 4, 4 => 'four' )
+ hb = hash_bracket( 1 => 2, 2 => 3, 3 => 4, 4 => 'four' )
+
+ expect(ha).to eql(h1.update(h2))
+ expect(ha).to eql(h1)
+
+ h1 = hash_bracket( 1 => 2, 2 => 3, 3 => 4 )
+ h2 = hash_bracket( 2 => 'two', 4 => 'four' )
+
+ expect(hb).to eql(h2.update(h1))
+ expect(hb).to eql(h2)
+ end
+
+# def test_value2?
+# assert_not_send([hash_bracket(), :value?, 1])
+# assert_not_send([hash_bracket(), :value?, nil])
+# expect(@h.send(:value?, nil)).to be true
+# expect(@h.send(:value?, 'one')).to be true
+# assert_not_send([@h, :value?, 'gumby'])
+# end
+#
+# def test_values
+# expect([]).to eql(hash_bracket().values)
+#
+# vals = @h.values
+# expected = []
+# @h.each { |k, v| expected << v }
+# expect([]).to eql(vals - expected)
+# expect([]).to eql(expected - vals)
+# end
+#
+# def test_intialize_wrong_arguments
+# assert_raise(ArgumentError) do
+# Hash.new(0) { }
+# end
+# end
+#
+# def test_create
+# expect({1=>2, 3=>4}).to eql(@cls[[[1,2],[3,4]]])
+# assert_raise(ArgumentError) { Hash[0, 1, 2] }
+# assert_warning(/wrong element type Fixnum at 1 /) {@cls[[[1, 2], 3]]}
+# bug5406 = '[ruby-core:39945]'
+# assert_raise(ArgumentError, bug5406) { @cls[[[1, 2], [3, 4, 5]]] }
+# expect({1=>2, 3=>4}).to eql(@cls[1,2,3,4])
+# o = Object.new
+# def o.to_hash() {1=>2} end
+# expect({1=>2}, @cls[o]).to eql("[ruby-dev:34555]")
+# end
+#
+# def test_rehash2
+# h = @cls[1 => 2, 3 => 4]
+# expect(h.dup).to eql(h.rehash)
+# assert_raise(RuntimeError) { h.each { h.rehash } }
+# expect({}).to eql(hash_bracket().rehash)
+# end
+#
+# def test_fetch2
+# expect(:bar, @h.fetch(0).to eql(:foo) { :bar })
+# end
+#
+# def test_default_proc
+# h = @cls.new {|hh, k| hh + k + "baz" }
+# expect("foobarbaz", h.default_proc.call("foo").to eql("bar"))
+# assert_nil(h.default_proc = nil)
+# assert_nil(h.default_proc)
+# h.default_proc = ->(_,_){ true }
+# expect(true).to eql(h[:nope])
+# h = hash_bracket()
+# assert_nil(h.default_proc)
+# end
+#
+# def test_shift2
+# h = @cls.new {|hh, k| :foo }
+# h[1] = 2
+# expect([1, 2]).to eql(h.shift)
+# expect(:foo).to eql(h.shift)
+# expect(:foo).to eql(h.shift)
+#
+# h = @cls.new(:foo)
+# h[1] = 2
+# expect([1, 2]).to eql(h.shift)
+# expect(:foo).to eql(h.shift)
+# expect(:foo).to eql(h.shift)
+#
+# h =@cls[1=>2]
+# h.each { expect([1, 2]).to eql(h.shift) }
+# end
+#
+# def test_shift_none
+# h = @cls.new {|hh, k| "foo"}
+# def h.default(k = nil)
+# super.upcase
+# end
+# expect("FOO").to eql(h.shift)
+# end
+#
+# def test_reject_bang2
+# expect({1=>2}, @cls[1=>2,3=>4].reject! {|k).to eql(v| k + v == 7 })
+# assert_nil(@cls[1=>2,3=>4].reject! {|k, v| k == 5 })
+# assert_nil(hash_bracket().reject! { })
+# end
+#
+# def test_select
+# expect({3=>4,5=>6}, @cls[1=>2,3=>4,5=>6].select {|k).to eql(v| k + v >= 7 })
+#
+# base = @cls[ 1 => 'one', '2' => false, true => 'true', 'cat' => 99 ]
+# h1 = @cls[ '2' => false, 'cat' => 99 ]
+# h2 = @cls[ 1 => 'one', true => 'true' ]
+# h3 = @cls[ 1 => 'one', true => 'true', 'cat' => 99 ]
+#
+# h = base.dup
+# expect(h).to eql(h.select { true })
+# expect(hash_bracket()).to eql(h.select { false })
+#
+# h = base.dup
+# expect(h1).to eql(h.select {|k,v| k.instance_of?(String) })
+#
+# expect(h2).to eql(h.select {|k,v| v.instance_of?(String) })
+#
+# expect(h3).to eql(h.select {|k,v| v })
+# expect(base).to eql(h)
+#
+# h.instance_variable_set(:@foo, :foo)
+# h.default = 42
+# h.taint
+# h = h.select {true}
+# assert_instance_of(Hash, h)
+# assert_not_predicate(h, :tainted?)
+# assert_nil(h.default)
+# assert_not_send([h, :instance_variable_defined?, :@foo])
+# end
+#
+# def test_select!
+# h = @cls[1=>2,3=>4,5=>6]
+# expect(h, h.select! {|k).to eql(v| k + v >= 7 })
+# expect({3=>4,5=>6}).to eql(h)
+# h = @cls[1=>2,3=>4,5=>6]
+# expect(nil).to eql(h.select!{true})
+# end
+#
+# def test_clear2
+# expect({}).to eql(@cls[1=>2,3=>4,5=>6].clear)
+# h = @cls[1=>2,3=>4,5=>6]
+# h.each { h.clear }
+# expect({}).to eql(h)
+# end
+#
+# def test_replace2
+# h1 = @cls.new { :foo }
+# h2 = @cls.new
+# h2.replace h1
+# expect(:foo).to eql(h2[0])
+#
+# assert_raise(ArgumentError) { h2.replace() }
+# assert_raise(TypeError) { h2.replace(1) }
+# h2.freeze
+# assert_raise(ArgumentError) { h2.replace() }
+# assert_raise(RuntimeError) { h2.replace(h1) }
+# assert_raise(RuntimeError) { h2.replace(42) }
+# end
+#
+# def test_size2
+# expect(0).to eql(hash_bracket().size)
+# end
+#
+# def test_equal2
+# assert_not_equal(0, hash_bracket())
+# o = Object.new
+# o.instance_variable_set(:@cls, @cls)
+# def o.to_hash; hash_bracket(); end
+# def o.==(x); true; end
+# expect({}).to eql(o)
+# def o.==(x); false; end
+# assert_not_equal({}, o)
+#
+# h1 = @cls[1=>2]; h2 = @cls[3=>4]
+# assert_not_equal(h1, h2)
+# h1 = @cls[1=>2]; h2 = @cls[1=>4]
+# assert_not_equal(h1, h2)
+# end
+
+ it "test_eql" do
+ expect(hash_bracket().eql?(0)).to be false
+ o = Object.new
+ o.instance_variable_set(:@cls, Hash)
+ def o.to_hash; hash_bracket(); end
+ def o.eql?(x); true; end
+ expect(hash_bracket().send(:eql?, o)).to be true
+ def o.eql?(x); false; end
+ expect(hash_bracket().eql?(o)).to be false
+ end
+
+ it "test_hash2" do
+ expect(hash_bracket().hash).to be_kind_of(Integer)
+ h = hash_bracket(1=>2)
+ h.shift
+ expect({}).to eql(h)
+ expect({}.hash).to eql(h.hash)
+ expect(hash_bracket().hash).not_to eql(0)
+ end
+
+ it "test_update2" do
+ h1 = hash_bracket(1=>2, 3=>4)
+ h2 = hash_bracket(1=>3, 5=>7)
+ h1.update(h2) {|k, v1, v2| k + v1 + v2 }
+ expect(hash_bracket(1=>6, 3=>4, 5=>7)).to eql(h1)
+ end
+
+ it "#merge" do
+ h1 = hash_bracket(1=>2, 3=>4)
+ h2 = hash_bracket({1=>3, 5=>7})
+ expect({1=>3, 3=>4, 5=>7}).to eql(h1.merge(h2))
+ expect({1=>6, 3=>4, 5=>7}).to eql(h1.merge(h2) {|k, v1, v2| k + v1 + v2 })
+ end
+
+ it "#assoc" do
+ expect([3,4]).to eql(hash_bracket(1=>2, 3=>4, 5=>6).assoc(3))
+ expect(hash_bracket(1=>2, 3=>4, 5=>6).assoc(4)).to be nil
+ expect([1.0,1]).to eql(hash_bracket(1.0=>1).assoc(1))
+ end
+
+# def test_assoc_compare_by_identity
+# h = hash_bracket()
+# h.compare_by_identity
+# h["a"] = 1
+# h["a".dup] = 2
+# expect(["a",1]).to eql(h.assoc("a"))
+# end
+
+ it "#rassoc" do
+ expect([3,4]).to eql(hash_bracket(1=>2, 3=>4, 5=>6).rassoc(4))
+ expect({1=>2, 3=>4, 5=>6}.rassoc(3)).to be nil
+ end
+
+ it "#flatten" do
+ expect([[1], [2]]).to eql(hash_bracket([1] => [2]).flatten)
+
+ a = hash_bracket(1=> "one", 2 => [2,"two"], 3 => [3, ["three"]])
+ expect([1, "one", 2, [2, "two"], 3, [3, ["three"]]]).to eql(a.flatten)
+ expect([[1, "one"], [2, [2, "two"]], [3, [3, ["three"]]]]).to eql(a.flatten(0))
+ expect([1, "one", 2, [2, "two"], 3, [3, ["three"]]]).to eql(a.flatten(1))
+ expect([1, "one", 2, 2, "two", 3, 3, ["three"]]).to eql(a.flatten(2))
+ expect([1, "one", 2, 2, "two", 3, 3, "three"]).to eql(a.flatten(3))
+ expect([1, "one", 2, 2, "two", 3, 3, "three"]).to eql(a.flatten(-1))
+ expect { a.flatten(Object) }.to raise_error(TypeError)
+ end
+#
+# def test_callcc
+# h = @cls[1=>2]
+# c = nil
+# f = false
+# h.each { callcc {|c2| c = c2 } }
+# unless f
+# f = true
+# c.call
+# end
+# assert_raise(RuntimeError) { h.each { h.rehash } }
+#
+# h = @cls[1=>2]
+# c = nil
+# assert_raise(RuntimeError) do
+# h.each { callcc {|c2| c = c2 } }
+# h.clear
+# c.call
+# end
+# end
+#
+# def test_callcc_iter_level
+# bug9105 = '[ruby-dev:47803] [Bug #9105]'
+# h = @cls[1=>2, 3=>4]
+# c = nil
+# f = false
+# h.each {callcc {|c2| c = c2}}
+# unless f
+# f = true
+# c.call
+# end
+# assert_nothing_raised(RuntimeError, bug9105) do
+# h.each {|i, j|
+# h.delete(i);
+# assert_not_equal(false, i, bug9105)
+# }
+# end
+# end
+#
+# def test_callcc_escape
+# bug9105 = '[ruby-dev:47803] [Bug #9105]'
+# assert_nothing_raised(RuntimeError, bug9105) do
+# h=hash_bracket()
+# cnt=0
+# c = callcc {|cc|cc}
+# h[cnt] = true
+# h.each{|i|
+# cnt+=1
+# c.call if cnt == 1
+# }
+# end
+# end
+#
+# def test_callcc_reenter
+# bug9105 = '[ruby-dev:47803] [Bug #9105]'
+# assert_nothing_raised(RuntimeError, bug9105) do
+# h = @cls[1=>2,3=>4]
+# c = nil
+# f = false
+# h.each { |i|
+# callcc {|c2| c = c2 } unless c
+# h.delete(1) if f
+# }
+# unless f
+# f = true
+# c.call
+# end
+# end
+# end
+#
+# def test_threaded_iter_level
+# bug9105 = '[ruby-dev:47807] [Bug #9105]'
+# h = @cls[1=>2]
+# 2.times.map {
+# f = false
+# th = Thread.start {h.each {f = true; sleep}}
+# Thread.pass until f
+# Thread.pass until th.stop?
+# th
+# }.each {|th| th.run; th.join}
+# assert_nothing_raised(RuntimeError, bug9105) do
+# h[5] = 6
+# end
+# expect(6, h[5]).to eql(bug9105)
+# end
+#
+# def test_compare_by_identity
+# a = "foo"
+# assert_not_predicate(hash_bracket(), :compare_by_identity?)
+# h = @cls[a => "bar"]
+# assert_not_predicate(h, :compare_by_identity?)
+# h.compare_by_identity
+# assert_predicate(h, :compare_by_identity?)
+# #expect("bar").to eql(h[a])
+# assert_nil(h["foo"])
+#
+# bug8703 = '[ruby-core:56256] [Bug #8703] copied identhash'
+# h.clear
+# assert_predicate(h.dup, :compare_by_identity?, bug8703)
+# end
+
+ it "test_same_key" do
+ h = hash_bracket(a=[], 1)
+ a << 1
+ h[[]] = 2
+ a.clear
+ cnt = 0
+ r = h.each{ break nil if (cnt+=1) > 100 }
+ expect(r).not_to be nil
+ end
+
+# class ObjWithHash
+# def initialize(value, hash)
+# @value = value
+# @hash = hash
+# end
+# attr_reader :value, :hash
+#
+# def eql?(other)
+# @value == other.value
+# end
+# end
+#
+# def test_hash_hash
+# expect({0=>2,11=>1}.hash).to eql(@cls[11=>1,0=>2].hash)
+# o1 = ObjWithHash.new(0,1)
+# o2 = ObjWithHash.new(11,1)
+# expect({o1=>1,o2=>2}.hash).to eql(@cls[o2=>2,o1=>1].hash)
+# end
+
+ it "test_hash_bignum_hash" do
+ x = 2<<(32-3)-1
+ expect({x=>1}.hash).to eql(hash_bracket(x=>1).hash)
+ x = 2<<(64-3)-1
+ expect({x=>1}.hash).to eql(hash_bracket(x=>1).hash)
+
+ o = Object.new
+ def o.hash; 2 << 100; end
+ expect({o=>1}.hash).to eql(hash_bracket(o=>1).hash)
+ end
+
+ it "test_hash_poped" do
+ expect { eval("a = 1; hash_bracket(a => a); a") }.not_to raise_error
+ end
+
+ it "test_recursive_key" do
+ h = hash_bracket()
+ expect { h[h] = :foo }.not_to raise_error
+ h.rehash
+ expect(:foo).to eql(h[h])
+ end
+
+ it "test_inverse_hash" do
+ [hash_bracket(1=>2), hash_bracket(123=>"abc")].each do |h|
+ expect(h.hash).not_to eql(h.invert.hash)
+ end
+ end
+
+# def test_recursive_hash_value_struct
+# bug9151 = '[ruby-core:58567] [Bug #9151]'
+#
+# s = Struct.new(:x) {def hash; [x,""].hash; end}
+# a = s.new
+# b = s.new
+# a.x = b
+# b.x = a
+# assert_nothing_raised(SystemStackError, bug9151) {a.hash}
+# assert_nothing_raised(SystemStackError, bug9151) {b.hash}
+#
+# h = hash_bracket()
+# h[[a,"hello"]] = 1
+# expect(1).to eql(h.size)
+# h[[b,"world"]] = 2
+# expect(2).to eql(h.size)
+#
+# obj = Object.new
+# h = @cls[a => obj]
+# assert_same(obj, h[b])
+# end
+#
+# def test_recursive_hash_value_array
+# h = hash_bracket()
+# h[[[1]]] = 1
+# expect(1).to eql(h.size)
+# h[[[2]]] = 1
+# expect(2).to eql(h.size)
+#
+# a = []
+# a << a
+#
+# h = hash_bracket()
+# h[[a, 1]] = 1
+# expect(1).to eql(h.size)
+# h[[a, 2]] = 2
+# expect(2).to eql(h.size)
+# h[[a, a]] = 3
+# expect(3).to eql(h.size)
+#
+# obj = Object.new
+# h = @cls[a => obj]
+# assert_same(obj, h[[[a]]])
+# end
+#
+# def test_recursive_hash_value_array_hash
+# h = hash_bracket()
+# rec = [h]
+# h[:x] = rec
+#
+# obj = Object.new
+# h2 = {rec => obj}
+# [h, {x: rec}].each do |k|
+# k = [k]
+# assert_same(obj, h2[k], ->{k.inspect})
+# end
+# end
+#
+# def test_recursive_hash_value_hash_array
+# h = hash_bracket()
+# rec = [h]
+# h[:x] = rec
+#
+# obj = Object.new
+# h2 = {h => obj}
+# [rec, [h]].each do |k|
+# k = {x: k}
+# assert_same(obj, h2[k], ->{k.inspect})
+# end
+# end
+#
+# def test_exception_in_rehash_memory_leak
+# return unless @cls == Hash
+#
+# bug9187 = '[ruby-core:58728] [Bug #9187]'
+#
+# prepare = <<-EOS
+# class Foo
+# def initialize
+# @raise = false
+# end
+#
+# def hash
+# raise if @raise
+# @raise = true
+# return 0
+# end
+# end
+# h = {Foo.new => true}
+# EOS
+#
+# code = <<-EOS
+# 10_0000.times do
+# h.rehash rescue nil
+# end
+# GC.start
+# EOS
+#
+# assert_no_memory_leak([], prepare, code, bug9187)
+# end
+#
+# def test_wrapper_of_special_const
+# bug9381 = '[ruby-core:59638] [Bug #9381]'
+#
+# wrapper = Class.new do
+# def initialize(obj)
+# @obj = obj
+# end
+#
+# def hash
+# @obj.hash
+# end
+#
+# def eql?(other)
+# @obj.eql?(other)
+# end
+# end
+#
+# bad = [
+# 5, true, false, nil,
+# 0.0, 1.72723e-77,
+# :foo, "dsym_#{self.object_id.to_s(16)}_#{Time.now.to_i.to_s(16)}".to_sym,
+# ].select do |x|
+# hash = {x => bug9381}
+# hash[wrapper.new(x)] != bug9381
+# end
+# assert_empty(bad, bug9381)
+# end
+#
+# def test_label_syntax
+# return unless @cls == Hash
+#
+# feature4935 = '[ruby-core:37553] [Feature #4935]'
+# x = 'world'
+# hash = assert_nothing_raised(SyntaxError, feature4935) do
+# break eval(%q({foo: 1, "foo-bar": 2, "hello-#{x}": 3, 'hello-#{x}': 4, 'bar': {}}))
+# end
+# expect({:foo => 1, :'foo-bar' => 2, :'hello-world' => 3, :'hello-#{x}' => 4, :bar => {}}, hash).to eql(feature4935)
+# x = x
+# end
+#
+# class TestSubHash < TestHash
+# class SubHash < Hash
+# def reject(*)
+# super
+# end
+# end
+#
+# def setup
+# @cls = SubHash
+# super
+# end
+# end
+end
diff --git a/spec/unit/node/attribute_trait/decorator_spec.rb b/spec/unit/node/attribute_trait/decorator_spec.rb
new file mode 100644
index 0000000000..a87109216f
--- /dev/null
+++ b/spec/unit/node/attribute_trait/decorator_spec.rb
@@ -0,0 +1,45 @@
+
+require 'spec_helper'
+
+describe Chef::Node::AttributeTrait::Decorator do
+ class Test
+ include Chef::Node::AttributeTrait::Decorator
+ end
+
+ let(:test) { Test[] }
+
+ context "#to_hash" do
+ it "return a real hash" do
+ test['foo'] = 'bar'
+ expect(test.to_hash).to be_instance_of(Hash)
+ end
+ end
+
+ context "#each" do
+ it "yields decorated objects when nested" do
+ test['foo'] = { 'bar' => 'baz' }
+ test['bar'] = [ 1, 2 ]
+ test.each { |k, v| expect(v).to be_instance_of(Test) }
+ end
+
+ it "yields undecorated objects when not nested" do
+ test['foo'] = 1
+ test['bar'] = "string"
+ test.each { |k, v| expect(v).not_to be_instance_of(Test) }
+ end
+ end
+
+ context "Array#map" do
+ it "yields decorated objects when nested" do
+ test['array'] = [ { a: 'a' }, { b: 'b' } ]
+ map = test['array'].map { |e| expect(e).to be_instance_of(Test) }
+ expect(map[0]).to be_instance_of(Test)
+ end
+
+ it "yields undecorated objects when not nested" do
+ test['array'] = [ 1, true, "string" ]
+ map = test['array'].map { |e| expect(e).not_to be_instance_of(Test) }
+ expect(map[0]).not_to be_instance_of(Test)
+ end
+ end
+end