summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLamont Granquist <lamont@scriptkiddie.org>2015-10-13 09:27:31 -0700
committerLamont Granquist <lamont@scriptkiddie.org>2015-10-14 16:45:06 -0700
commite92f43aa8238061f9cf7a40e7424d95e73964c48 (patch)
tree4ec4b5744966a3fb0c251f23cc439e138d1e6eaa
parentb565185e082172ca075cf365819bb203ea135048 (diff)
downloadchef-lcg/attributes-on-12.4.1-for-phil.tar.gz
more tests, more cleanuplcg/attributes-on-12.4.1-for-phil
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 86de146b23..868f01e08f 100644
--- a/lib/chef/node/attribute.rb
+++ b/lib/chef/node/attribute.rb
@@ -27,8 +27,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
@@ -60,17 +60,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
@@ -85,16 +85,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)
@@ -127,6 +119,8 @@ class Chef
end
end
+ # FIXME: doesn't decorator handle all this delgated crap now?
+ # vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
def to_s
wrapped_object.to_s
end
@@ -156,31 +150,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
@@ -189,9 +187,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
@@ -200,32 +196,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