diff --git a/lib/chef/node/attribute_collections.rb b/lib/chef/node/attribute_collections.rb index dcbb4601bd1..d5d496fd603 100644 --- a/lib/chef/node/attribute_collections.rb +++ b/lib/chef/node/attribute_collections.rb @@ -154,7 +154,14 @@ def []=(key, value) alias :attribute? :has_key? def method_missing(symbol, *args) - if args.empty? + # Calling `puts arg` implicitly calls #to_ary on `arg`. If `arg` does + # not implement #to_ary, ruby recognizes it as a single argument, and + # if it returns an Array, then ruby prints each element. If we don't + # account for that here, we'll auto-vivify a VividMash for the key + # :to_ary which creates an unwanted key and raises a TypeError. + if symbol == :to_ary + super + elsif args.empty? self[symbol] elsif symbol.to_s =~ /=$/ key_to_set = symbol.to_s[/^(.+)=$/, 1] diff --git a/spec/unit/node/attribute_spec.rb b/spec/unit/node/attribute_spec.rb index fac898be26b..435dc3230ca 100644 --- a/spec/unit/node/attribute_spec.rb +++ b/spec/unit/node/attribute_spec.rb @@ -234,6 +234,19 @@ end end + describe "when printing attribute components" do + + it "does not cause a type error" do + # See CHEF-3799; IO#puts implicitly calls #to_ary on its argument. This + # is expected to raise a NoMethodError or return an Array. `to_ary` is + # the "strict" conversion method that should only be implemented by + # things that are truly Array-like, so NoMethodError is the right choice. + # (cf. there is no Hash#to_ary). + lambda { @attributes.default.to_ary }.should raise_error(NoMethodError) + end + + end + describe "when fetching values based on precedence" do before do @attributes.default["default"] = "cookbook default"