require "support/shared/integration/integration_helper" describe "Chef::Resource.property" do include IntegrationSupport module Namer @i = 0 def self.next_resource_name "chef_resource_property_spec_#{@i += 1}" end def self.reset_index @current_index = 0 end def self.current_index @current_index end def self.next_index @current_index += 1 end end def lazy(&block) Chef::DelayedEvaluator.new(&block) end before do Namer.reset_index end def self.new_resource_name Namer.next_resource_name end let(:resource_class) do new_resource_name = self.class.new_resource_name Class.new(Chef::Resource) do resource_name new_resource_name def next_index Namer.next_index end end end let(:resource) do resource_class.new("blah") end def self.english_join(values) return "" if values.size == 0 return values[0].inspect if values.size == 1 "#{values[0..-2].map { |v| v.inspect }.join(", ")} and #{values[-1].inspect}" end def self.with_property(*properties, &block) tags_index = properties.find_index { |p| !p.is_a?(String) } if tags_index properties, tags = properties[0..tags_index - 1], properties[tags_index..-1] else tags = [] end if properties.size == 1 description = "With property #{properties.first}" else description = "With properties #{english_join(properties.map { |property| (property.inspect).to_s })}" end context description, *tags do before do properties.each do |property_str| resource_class.class_eval("property #{property_str}", __FILE__, __LINE__) end end instance_eval(&block) end end # Basic properties with_property ":bare_property" do it "can be set" do expect(resource.bare_property 10).to eq 10 expect(resource.bare_property).to eq 10 end it "nil does a set" do expect(resource.bare_property 10).to eq 10 expect(resource.bare_property nil).to eq nil expect(resource.bare_property).to eq nil end it "can be updated" do expect(resource.bare_property 10).to eq 10 expect(resource.bare_property 20).to eq 20 expect(resource.bare_property).to eq 20 end it "can be set with =" do expect(resource.bare_property = 10).to eq 10 expect(resource.bare_property).to eq 10 end it "can be set to nil with =" do expect(resource.bare_property 10).to eq 10 expect(resource.bare_property = nil).to be_nil expect(resource.bare_property).to be_nil end it "can be updated with =" do expect(resource.bare_property 10).to eq 10 expect(resource.bare_property = 20).to eq 20 expect(resource.bare_property).to eq 20 end end with_property ":Straße" do it "properties with UTF-8 in their name work" do expect(resource.Straße).to eql(nil) expect(resource.Straße "foo").to eql("foo") expect(resource.Straße).to eql("foo") expect(resource.Straße = "bar").to eql("bar") expect(resource.Straße).to eql("bar") end end context "deprecated properties" do it "does not create a deprecation warning on definition" do expect { resource_class.class_eval { property :x, String, deprecated: 10 } }.not_to raise_error Chef::Exceptions::DeprecatedFeatureError end with_property ":x, deprecated: 'a deprecated property'" do it "deprecated properties emit a deprecation warning" do expect(Chef).to receive(:deprecated).with(:property, "a deprecated property") expect(resource.x 10).to eq 10 end end end with_property ":x, name_property: true" do context "and subclass" do let(:subresource_class) do new_resource_name = self.class.new_resource_name Class.new(resource_class) do resource_name new_resource_name end end let(:subresource) do subresource_class.new("blah") end context "with property :x on the subclass" do before do subresource_class.class_eval do property :x end end it "x is still name_property" do expect(subresource.x).to eq "blah" end end context "with property :x, name_attribute: false on the subclass" do before do subresource_class.class_eval do property :x, name_attribute: false end end it "x is no longer name_property" do expect(subresource.x).to be_nil end end context "with property :x, default: 10 on the subclass" do before do subresource_class.class_eval do property :x, default: 10 end end it "x is no longer name_property" do expect(subresource.x).to eq(10) end end end end with_property ":x, Integer" do context "and subclass" do let(:subresource_class) do new_resource_name = self.class.new_resource_name Class.new(resource_class) do resource_name new_resource_name end end let(:subresource) do subresource_class.new("blah") end it "x is inherited" do expect(subresource.x 10).to eq 10 expect(subresource.x).to eq 10 expect(subresource.x = 20).to eq 20 expect(subresource.x).to eq 20 expect(subresource_class.properties[:x]).not_to be_nil end it "x's validation is inherited" do expect { subresource.x "ohno" }.to raise_error Chef::Exceptions::ValidationFailed end context "with property :y on the subclass" do before do subresource_class.class_eval do property :y end end it "x is still there" do expect(subresource.x 10).to eq 10 expect(subresource.x).to eq 10 expect(subresource.x = 20).to eq 20 expect(subresource.x).to eq 20 expect(subresource_class.properties[:x]).not_to be_nil end it "y is there" do expect(subresource.y 10).to eq 10 expect(subresource.y).to eq 10 expect(subresource.y = 20).to eq 20 expect(subresource.y).to eq 20 expect(subresource_class.properties[:y]).not_to be_nil end it "y is not on the superclass" do expect { resource_class.y 10 }.to raise_error NoMethodError expect(resource_class.properties[:y]).to be_nil end end context "with property :x on the subclass" do before do subresource_class.class_eval do property :x end end it "x is still there" do expect(subresource.x 10).to eq 10 expect(subresource.x).to eq 10 expect(subresource.x = 20).to eq 20 expect(subresource.x).to eq 20 expect(subresource_class.properties[:x]).not_to be_nil expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x] end it "x's validation is inherited" do expect { subresource.x "ohno" }.to raise_error Chef::Exceptions::ValidationFailed end end context "with property :x, default: 80 on the subclass" do before do subresource_class.class_eval do property :x, default: 80 end end it "x is still there" do expect(subresource.x 10).to eq 10 expect(subresource.x).to eq 10 expect(subresource.x = 20).to eq 20 expect(subresource.x).to eq 20 expect(subresource_class.properties[:x]).not_to be_nil expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x] end it "x defaults to 80" do expect(subresource.x).to eq 80 end it "x's validation is inherited" do expect { subresource.x "ohno" }.to raise_error Chef::Exceptions::ValidationFailed end end context "with property :x, String on the subclass" do before do subresource_class.class_eval do property :x, String end end it "x is still there" do expect(subresource.x "10").to eq "10" expect(subresource.x).to eq "10" expect(subresource.x = "20").to eq "20" expect(subresource.x).to eq "20" expect(subresource_class.properties[:x]).not_to be_nil expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x] end it "x's validation is overwritten" do expect { subresource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed expect(subresource.x "ohno").to eq "ohno" expect(subresource.x).to eq "ohno" end it "the superclass's validation for x is still there" do expect { resource.x "ohno" }.to raise_error Chef::Exceptions::ValidationFailed expect(resource.x 10).to eq 10 expect(resource.x).to eq 10 end end end end context "Chef::Resource::Property#reset_property" do it "when a resource is newly created, reset_property(:name) sets property to nil" do expect(resource.property_is_set?(:name)).to be_truthy resource.reset_property(:name) expect(resource.property_is_set?(:name)).to be_falsey expect { resource.name }.to raise_error Chef::Exceptions::ValidationFailed end it "when referencing an undefined property, reset_property(:x) raises an error" do expect { resource.reset_property(:x) }.to raise_error(ArgumentError) end with_property ":x" do it "when the resource is newly created, reset_property(:x) does nothing" do expect(resource.property_is_set?(:x)).to be_falsey resource.reset_property(:x) expect(resource.property_is_set?(:x)).to be_falsey expect(resource.x).to be_nil end it "when x is set, reset_property resets it" do resource.x 10 expect(resource.property_is_set?(:x)).to be_truthy resource.reset_property(:x) expect(resource.property_is_set?(:x)).to be_falsey expect(resource.x).to be_nil end end with_property ":x, Integer" do it "when the resource is newly created, reset_property(:x) does nothing" do expect(resource.property_is_set?(:x)).to be_falsey resource.reset_property(:x) expect(resource.property_is_set?(:x)).to be_falsey expect(resource.x).to be_nil end it "when x is set, reset_property resets it even though `nil` is technically invalid" do resource.x 10 expect(resource.property_is_set?(:x)).to be_truthy resource.reset_property(:x) expect(resource.property_is_set?(:x)).to be_falsey expect(resource.x).to be_nil end end with_property ":x, default: 10" do it "when the resource is newly created, reset_property(:x) does nothing" do expect(resource.property_is_set?(:x)).to be_falsey resource.reset_property(:x) expect(resource.property_is_set?(:x)).to be_falsey expect(resource.x).to eq 10 end it "when x is set, reset_property resets it and it returns the default" do resource.x 20 resource.reset_property(:x) expect(resource.property_is_set?(:x)).to be_falsey expect(resource.x).to eq 10 end end with_property ":x, default: lazy { 10 }" do it "when the resource is newly created, reset_property(:x) does nothing" do expect(resource.property_is_set?(:x)).to be_falsey resource.reset_property(:x) expect(resource.property_is_set?(:x)).to be_falsey expect(resource.x).to eq 10 end it "when x is set, reset_property resets it and it returns the default" do resource.x 20 resource.reset_property(:x) expect(resource.property_is_set?(:x)).to be_falsey expect(resource.x).to eq 10 end end end context "Chef::Resource::Property#property_is_set?" do it "when a resource is newly created, property_is_set?(:name) is true" do expect(resource.property_is_set?(:name)).to be_truthy end it "when referencing an undefined property, property_is_set?(:x) raises an error" do expect { resource.property_is_set?(:x) }.to raise_error(ArgumentError) end with_property ":x" do it "when the resource is newly created, property_is_set?(:x) is false" do expect(resource.property_is_set?(:x)).to be_falsey end it "when x is set, property_is_set?(:x) is true" do resource.x 10 expect(resource.property_is_set?(:x)).to be_truthy end it "when x is set with =, property_is_set?(:x) is true" do resource.x = 10 expect(resource.property_is_set?(:x)).to be_truthy end it "when x is set to a lazy value, property_is_set?(:x) is true" do resource.x lazy { 10 } expect(resource.property_is_set?(:x)).to be_truthy end it "when x is retrieved, property_is_set?(:x) is false" do resource.x expect(resource.property_is_set?(:x)).to be_falsey end end with_property ":x, default: 10" do it "when the resource is newly created, property_is_set?(:x) is false" do expect(resource.property_is_set?(:x)).to be_falsey end it "when x is set, property_is_set?(:x) is true" do resource.x 10 expect(resource.property_is_set?(:x)).to be_truthy end it "when x is set with =, property_is_set?(:x) is true" do resource.x = 10 expect(resource.property_is_set?(:x)).to be_truthy end it "when x is set to a lazy value, property_is_set?(:x) is true" do resource.x lazy { 10 } expect(resource.property_is_set?(:x)).to be_truthy end it "when x is retrieved, property_is_set?(:x) is false" do resource.x expect(resource.property_is_set?(:x)).to be_falsey end end with_property ":x, default: nil" do it "when the resource is newly created, property_is_set?(:x) is false" do expect(resource.property_is_set?(:x)).to be_falsey end it "when x is set, property_is_set?(:x) is true" do resource.x 10 expect(resource.property_is_set?(:x)).to be_truthy end it "when x is set with =, property_is_set?(:x) is true" do resource.x = 10 expect(resource.property_is_set?(:x)).to be_truthy end it "when x is set to a lazy value, property_is_set?(:x) is true" do resource.x lazy { 10 } expect(resource.property_is_set?(:x)).to be_truthy end it "when x is retrieved, property_is_set?(:x) is false" do resource.x expect(resource.property_is_set?(:x)).to be_falsey end end with_property ":x, default: lazy { 10 }" do it "when the resource is newly created, property_is_set?(:x) is false" do expect(resource.property_is_set?(:x)).to be_falsey end it "when x is set, property_is_set?(:x) is true" do resource.x 10 expect(resource.property_is_set?(:x)).to be_truthy end it "when x is set with =, property_is_set?(:x) is true" do resource.x = 10 expect(resource.property_is_set?(:x)).to be_truthy end it "when x is retrieved, property_is_set?(:x) is false" do resource.x expect(resource.property_is_set?(:x)).to be_falsey end end end context "Chef::Resource::Property#default" do with_property ":x, default: 10" do it "when x is set, it returns its value" do expect(resource.x 20).to eq 20 expect(resource.property_is_set?(:x)).to be_truthy expect(resource.x).to eq 20 end it "when x is not set, it returns 10" do expect(resource.x).to eq 10 end it "when x is not set, it is not included in state" do expect(resource.state_for_resource_reporter).to eq({}) end it "when x is set to nil, it returns nil" do resource.instance_eval { @x = nil } expect(resource.x).to be_nil end context "With a subclass" do let(:subresource_class) do new_resource_name = self.class.new_resource_name Class.new(resource_class) do resource_name new_resource_name end end let(:subresource) { subresource_class.new("blah") } it "The default is inherited" do expect(subresource.x).to eq 10 end end end with_property ":x, default: 10, identity: true" do it "when x is not set, it is included in identity" do expect(resource.identity).to eq(10) end end with_property ":x, default: 1, identity: true", ":y, default: 2, identity: true" do it "when x is not set, it is still included in identity" do resource.y 20 expect(resource.identity).to eq(x: 1, y: 20) end end with_property ":x, default: nil" do it "when x is not set, it returns nil" do expect(resource.x).to be_nil end end with_property ":x" do it "when x is not set, it returns nil" do expect(resource.x).to be_nil end end context "string default" do with_property ":x, default: ''" do it "when x is not set, it returns ''" do expect(resource.x).to eq "" end it "x is immutable" do expect { resource.x << "foo" }.to raise_error(RuntimeError, "can't modify frozen String") end end with_property ":x, default: lazy { '' }" do it "x is immutable" do expect(resource.x).to eq "" resource.x << "foo" expect(resource.x).to eq "foo" expect(resource_class.new("other").x).to eq "" end end end context "hash default" do with_property ":x, default: {}" do it "when x is not set, it returns {}" do expect(resource.x).to eq({}) end it "x is immutable" do expect { resource.x["foo"] = "bar" }.to raise_error(RuntimeError, "can't modify frozen Hash") end it "The same exact value is returned multiple times in a row" do value = resource.x expect(value).to eq({}) expect(resource.x.object_id).to eq(value.object_id) end it "Multiple instances of x receive the exact same value" do expect(resource.x.object_id).to eq(resource_class.new("blah2").x.object_id) end end with_property ":x, default: lazy { {} }" do it "when x is not set, it returns {}" do expect(resource.x).to eq({}) end it "The value is the same each time it is called" do value = resource.x expect(value).to eq({}) expect(resource.x.object_id).to eq(value.object_id) end it "Multiple instances of x receive different values" do expect(resource.x.object_id).not_to eq(resource_class.new("blah2").x.object_id) end end end context "complex, nested default" do with_property ":x, default: [{foo: 'bar'}]" do it "when x is not set, it returns [{foo: 'bar'}]" do expect(resource.x).to eq([{ foo: "bar" }]) end it "x is immutable" do expect { resource.x << :other }.to raise_error(RuntimeError, "can't modify frozen Array") end it "x.first is immutable" do expect { resource.x.first[:foo] = "other" }.to raise_error(RuntimeError, "can't modify frozen Hash") end it "x.first[:foo] is immutable" do expect { resource.x.first[:foo] << "other" }.to raise_error(RuntimeError, "can't modify frozen String") end end end context "with a class with 'blah' as both class and instance methods" do before do resource_class.class_eval do def self.blah "class" end def blah "#{name}#{next_index}" end end end with_property ":x, default: lazy { blah }" do it "x is run in context of the instance" do expect(resource.x).to eq "blah1" end it "x is run in the context of each instance it is run in" do expect(resource.x).to eq "blah1" expect(resource_class.new("another").x).to eq "another2" end end with_property ':x, default: lazy { |x| "#{blah}#{x.blah}" }' do it "x is run in context of the class (where it was defined) and passed the instance" do expect(resource.x).to eq "classblah1" end it "x is passed the value of each instance it is run in" do expect(resource.x).to eq "classblah1" expect(resource_class.new("another").x).to eq "classanother2" end end end context "validation of defaults" do it "When a class is declared with property :x, String, default: 10, it immediately fails validation" do expect { resource_class.class_eval { property :x, String, default: 10 } }.to raise_error Chef::Exceptions::ValidationFailed end with_property ":x, String, default: lazy { Namer.next_index }" do it "when the resource is created, no error is raised" do resource end it "when x is set, no error is raised" do expect(resource.x "hi").to eq "hi" expect(resource.x).to eq "hi" end it "when x is retrieved, it fails validation" do expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed expect(Namer.current_index).to eq 1 end end with_property ":x, default: lazy { Namer.next_index.to_s }, is: proc { |v| Namer.next_index; true }" do it "coercion and validation is only run the first time" do expect(resource.x).to eq "1" expect(Namer.current_index).to eq 2 expect(resource.x).to eq "1" expect(Namer.current_index).to eq 2 end end with_property ":x, default: lazy { Namer.next_index.to_s.freeze }, is: proc { |v| Namer.next_index; true }" do it "coercion and validation is run each time" do expect(resource.x).to eq "1" expect(Namer.current_index).to eq 2 expect(resource.x).to eq "3" expect(Namer.current_index).to eq 4 end end end context "coercion of defaults" do # Frozen default, non-frozen coerce with_property ':x, coerce: proc { |v| "#{v}#{next_index}" }, default: 10' do it "when the resource is created, the proc is not yet run" do resource expect(Namer.current_index).to eq 0 end it "when x is set, coercion is run" do expect(resource.x "hi").to eq "hi1" expect(resource.x).to eq "hi1" expect(Namer.current_index).to eq 1 end it "when x is retrieved, coercion is run exactly once" do expect(resource.x).to eq "101" expect(resource.x).to eq "101" expect(Namer.current_index).to eq 1 end end # Frozen default, frozen coerce with_property ':x, coerce: proc { |v| "#{v}#{next_index}".freeze }, default: 10' do it "when the resource is created, the proc is not yet run" do resource expect(Namer.current_index).to eq 0 end it "when x is set, coercion is run" do expect(resource.x "hi").to eq "hi1" expect(resource.x).to eq "hi1" expect(Namer.current_index).to eq 1 end it "when x is retrieved, coercion is run each time" do expect(resource.x).to eq "101" expect(resource.x).to eq "102" expect(Namer.current_index).to eq 2 end end # Frozen lazy default, non-frozen coerce with_property ':x, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do it "when the resource is created, the proc is not yet run" do resource expect(Namer.current_index).to eq 0 end it "when x is set, coercion is run" do expect(resource.x "hi").to eq "hi1" expect(resource.x).to eq "hi1" expect(Namer.current_index).to eq 1 end it "when x is retrieved, coercion is run exactly once" do expect(resource.x).to eq "101" expect(resource.x).to eq "101" expect(Namer.current_index).to eq 1 end end # Non-frozen lazy default, frozen coerce with_property ':x, coerce: proc { |v| "#{v}#{next_index}".freeze }, default: lazy { "10" }' do it "when the resource is created, the proc is not yet run" do resource expect(Namer.current_index).to eq 0 end it "when x is set, coercion is run" do expect(resource.x "hi").to eq "hi1" expect(resource.x).to eq "hi1" expect(Namer.current_index).to eq 1 end it "when x is retrieved, coercion is run each time" do expect(resource.x).to eq "101" expect(resource.x).to eq "102" expect(Namer.current_index).to eq 2 end end with_property ':x, proc { |v| Namer.next_index; true }, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do it "coercion and validation is only run the first time x is retrieved" do expect(Namer.current_index).to eq 0 expect(resource.x).to eq "101" expect(Namer.current_index).to eq 2 expect(resource.x).to eq "101" expect(Namer.current_index).to eq 2 end end context "validation and coercion of defaults" do with_property ':x, String, coerce: proc { |v| "#{v}#{next_index}" }, default: 10' do it "when x is retrieved, it is coerced before validating and passes" do expect(resource.x).to eq "101" end end with_property ':x, Integer, coerce: proc { |v| "#{v}#{next_index}" }, default: 10' do it "when x is retrieved, it is coerced and fails validation" do expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed end end with_property ':x, String, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do it "when x is retrieved, it is coerced before validating and passes" do expect(resource.x).to eq "101" end end with_property ':x, Integer, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do it "when x is retrieved, it is coerced and fails validation" do expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed end end with_property ':x, proc { |v| Namer.next_index; true }, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do it "coercion is only run the first time x is retrieved, and validation is run" do expect(Namer.current_index).to eq 0 expect(resource.x).to eq "101" expect(Namer.current_index).to eq 2 expect(resource.x).to eq "101" expect(Namer.current_index).to eq 2 end end end end end context "Chef::Resource#lazy" do with_property ":x" do it "setting x to a lazy value does not run it immediately" do resource.x lazy { Namer.next_index } expect(Namer.current_index).to eq 0 end it "you can set x to a lazy value in the instance" do resource.instance_eval do x lazy { Namer.next_index } end expect(resource.x).to eq 1 expect(Namer.current_index).to eq 1 end it "retrieving a lazy value pops it open" do resource.x lazy { Namer.next_index } expect(resource.x).to eq 1 expect(Namer.current_index).to eq 1 end it "retrieving a lazy value twice evaluates it twice" do resource.x lazy { Namer.next_index } expect(resource.x).to eq 1 expect(resource.x).to eq 2 expect(Namer.current_index).to eq 2 end it "setting the same lazy value on two different instances runs it on each instancee" do resource2 = resource_class.new("blah2") l = lazy { Namer.next_index } resource.x l resource2.x l expect(resource2.x).to eq 1 expect(resource.x).to eq 2 expect(resource2.x).to eq 3 end context "when the class has a class and instance method named blah" do before do resource_class.class_eval do def self.blah "class" end def blah "#{name}#{Namer.next_index}" end end end def blah "example" end # it "retrieving lazy { blah } gets the instance variable" do # resource.x lazy { blah } # expect(resource.x).to eq "blah1" # end # it "retrieving lazy { blah } from two different instances gets two different instance variables" do # resource2 = resource_class.new("another") # l = lazy { blah } # resource2.x l # resource.x l # expect(resource2.x).to eq "another1" # expect(resource.x).to eq "blah2" # expect(resource2.x).to eq "another3" # end it 'retrieving lazy { |x| "#{blah}#{x.blah}" } gets the example and instance variables' do resource.x lazy { |x| "#{blah}#{x.blah}" } expect(resource.x).to eq "exampleblah1" end it 'retrieving lazy { |x| "#{blah}#{x.blah}" } from two different instances gets two different instance variables' do resource2 = resource_class.new("another") l = lazy { |x| "#{blah}#{x.blah}" } resource2.x l resource.x l expect(resource2.x).to eq "exampleanother1" expect(resource.x).to eq "exampleblah2" expect(resource2.x).to eq "exampleanother3" end end end with_property ':x, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do it "lazy values are not coerced on set" do resource.x lazy { Namer.next_index } expect(Namer.current_index).to eq 0 end it "lazy values are coerced on get" do resource.x lazy { Namer.next_index } expect(resource.x).to eq "12" expect(Namer.current_index).to eq 2 end it "lazy values are coerced on each access" do resource.x lazy { Namer.next_index } expect(resource.x).to eq "12" expect(Namer.current_index).to eq 2 expect(resource.x).to eq "34" expect(Namer.current_index).to eq 4 end end with_property ":x, String" do it "lazy values are not validated on set" do resource.x lazy { Namer.next_index } expect(Namer.current_index).to eq 0 end it "lazy values are validated on get" do resource.x lazy { Namer.next_index } expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed expect(Namer.current_index).to eq 1 end end with_property ":x, is: proc { |v| Namer.next_index; true }" do it "lazy values are validated on each access" do resource.x lazy { Namer.next_index } expect(resource.x).to eq 1 expect(Namer.current_index).to eq 2 expect(resource.x).to eq 3 expect(Namer.current_index).to eq 4 end end with_property ':x, Integer, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do it "lazy values are not validated or coerced on set" do resource.x lazy { Namer.next_index } expect(Namer.current_index).to eq 0 end it "lazy values are coerced before being validated, which fails" do resource.x lazy { Namer.next_index } expect(Namer.current_index).to eq 0 expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed expect(Namer.current_index).to eq 2 end end with_property ':x, coerce: proc { |v| "#{v}#{Namer.next_index}" }, is: proc { |v| Namer.next_index; true }' do it "lazy values are coerced and validated exactly once" do resource.x lazy { Namer.next_index } expect(resource.x).to eq "12" expect(Namer.current_index).to eq 3 expect(resource.x).to eq "45" expect(Namer.current_index).to eq 6 end end with_property ':x, String, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do it "lazy values are coerced before being validated, which succeeds" do resource.x lazy { Namer.next_index } expect(resource.x).to eq "12" expect(Namer.current_index).to eq 2 end end end context "Chef::Resource::Property#coerce" do with_property ':x, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do it "coercion runs on set" do expect(resource.x 10).to eq "101" expect(Namer.current_index).to eq 1 end it "does not emit a deprecation warning if set to nil" do # nil is never coerced expect(resource.x nil).to be_nil end it "coercion sets the value (and coercion does not run on get)" do expect(resource.x 10).to eq "101" expect(resource.x).to eq "101" expect(Namer.current_index).to eq 1 end it "coercion runs each time set happens" do expect(resource.x 10).to eq "101" expect(Namer.current_index).to eq 1 expect(resource.x 10).to eq "102" expect(Namer.current_index).to eq 2 end end with_property ":x, coerce: proc { |x| x }" do it "does not emit a deprecation warning if set to nil" do expect(resource.x nil).to be_nil end end with_property ':x, coerce: proc { |x| Namer.next_index; raise "hi" if x == 10; x }, is: proc { |x| Namer.next_index; x != 10 }' do it "failed coercion fails to set the value" do resource.x 20 expect(resource.x).to eq 20 expect(Namer.current_index).to eq 2 expect { resource.x 10 }.to raise_error "hi" expect(resource.x).to eq 20 expect(Namer.current_index).to eq 3 end it "validation does not run if coercion fails" do expect { resource.x 10 }.to raise_error "hi" expect(Namer.current_index).to eq 1 end end end context "Chef::Resource::Property validation" do with_property ":x, is: proc { |v| Namer.next_index; v.is_a?(Integer) }" do it "validation runs on set" do expect(resource.x 10).to eq 10 expect(Namer.current_index).to eq 1 end it "validation sets the value (and validation does not run on get)" do expect(resource.x 10).to eq 10 expect(resource.x).to eq 10 expect(Namer.current_index).to eq 1 end it "validation runs each time set happens" do expect(resource.x 10).to eq 10 expect(Namer.current_index).to eq 1 expect(resource.x 10).to eq 10 expect(Namer.current_index).to eq 2 end it "failed validation fails to set the value" do expect(resource.x 10).to eq 10 expect(Namer.current_index).to eq 1 expect { resource.x "blah" }.to raise_error Chef::Exceptions::ValidationFailed expect(resource.x).to eq 10 expect(Namer.current_index).to eq 2 end end end %w{name_attribute name_property}.each do |name| context "Chef::Resource::Property##{name}" do with_property ":x, #{name}: true" do it "defaults x to resource.name" do expect(resource.x).to eq "blah" end it "does not pick up resource.name if set" do expect(resource.x 10).to eq 10 expect(resource.x).to eq 10 end it "binds to the latest resource.name when run" do resource.name "foo" expect(resource.x).to eq "foo" end it "caches resource.name" do expect(resource.x).to eq "blah" resource.name "foo" expect(resource.x).to eq "blah" end end with_property ":x, #{name}: false" do it "defaults to nil" do expect(resource.x).to be_nil end end with_property ":x, #{name}: nil" do it "defaults to nil" do expect(resource.x).to be_nil end end context "default ordering deprecation warnings" do it "emits an error for property :x, default: 10, #{name}: true" do expect { resource_class.property :x, :default => 10, name.to_sym => true }.to raise_error ArgumentError, /A property cannot be both a name_property\/name_attribute and have a default value. Use one or the other on property x of resource chef_resource_property_spec_(\d+)/ end it "emits an error for property :x, default: nil, #{name}: true" do expect { resource_class.property :x, :default => nil, name.to_sym => true }.to raise_error ArgumentError, /A property cannot be both a name_property\/name_attribute and have a default value. Use one or the other on property x of resource chef_resource_property_spec_(\d+)/ end it "emits an error for property :x, #{name}: true, default: 10" do expect { resource_class.property :x, name.to_sym => true, :default => 10 }.to raise_error ArgumentError, /A property cannot be both a name_property\/name_attribute and have a default value. Use one or the other on property x of resource chef_resource_property_spec_(\d+)/ end it "emits an error for property :x, #{name}: true, default: nil" do expect { resource_class.property :x, name.to_sym => true, :default => nil }.to raise_error ArgumentError, /A property cannot be both a name_property\/name_attribute and have a default value. Use one or the other on property x of resource chef_resource_property_spec_(\d+)/ end end end end it "raises an error if both name_property and name_attribute are specified" do expect { resource_class.property :x, name_property: false, name_attribute: 1 }.to raise_error ArgumentError, /name_attribute and name_property are functionally identical and both cannot be specified on a property at once. Use just one on property x of resource chef_resource_property_spec_(\d+)./ expect { resource_class.property :x, name_property: false, name_attribute: nil }.to raise_error ArgumentError, /name_attribute and name_property are functionally identical and both cannot be specified on a property at once. Use just one on property x of resource chef_resource_property_spec_(\d+)./ expect { resource_class.property :x, name_property: false, name_attribute: false }.to raise_error ArgumentError, /name_attribute and name_property are functionally identical and both cannot be specified on a property at once. Use just one on property x of resource chef_resource_property_spec_(\d+)./ expect { resource_class.property :x, name_property: true, name_attribute: true }.to raise_error ArgumentError, /name_attribute and name_property are functionally identical and both cannot be specified on a property at once. Use just one on property x of resource chef_resource_property_spec_(\d+)./ end context "property_type" do it "property_types validate their defaults" do expect do module ::PropertySpecPropertyTypes include Chef::Mixin::Properties property_type(is: [:a, :b], default: :c) end end.to raise_error(Chef::Exceptions::ValidationFailed) expect do module ::PropertySpecPropertyTypes include Chef::Mixin::Properties property_type(is: [:a, :b], default: :b) end end.not_to raise_error end context "With property_type ABType (is: [:a, :b]) and CDType (is: [:c, :d])" do before :all do module ::PropertySpecPropertyTypes include Chef::Mixin::Properties ABType = property_type(is: [:a, :b]) CDType = property_type(is: [:c, :d]) end end with_property ":x, [PropertySpecPropertyTypes::ABType, nil, PropertySpecPropertyTypes::CDType]" do it "The property can be set to nil without triggering a warning" do expect(resource.x nil).to be_nil expect(resource.x).to be_nil end it "The property can be set to :a" do expect(resource.x :a).to eq(:a) expect(resource.x).to eq(:a) end it "The property can be set to :c" do expect(resource.x :c).to eq(:c) expect(resource.x).to eq(:c) end it "The property cannot be set to :z" do expect { resource.x :z }.to raise_error(Chef::Exceptions::ValidationFailed, /Property x must be one of/) end end with_property ":x, [nil, PropertySpecPropertyTypes::ABType, PropertySpecPropertyTypes::CDType]" do it "The property can be set to nil without triggering a warning" do expect(resource.x nil).to be_nil expect(resource.x).to be_nil end it "The property can be set to :a" do expect(resource.x :a).to eq(:a) expect(resource.x).to eq(:a) end it "The property can be set to :c" do expect(resource.x :c).to eq(:c) expect(resource.x).to eq(:c) end it "The property cannot be set to :z" do expect { resource.x :z }.to raise_error(Chef::Exceptions::ValidationFailed, /Property x must be one of/) end end with_property ":x, [PropertySpecPropertyTypes::ABType, nil], default: nil" do it "The value defaults to nil" do expect(resource.x).to be_nil end end with_property ":x, [PropertySpecPropertyTypes::ABType, nil], default: lazy { nil }" do it "The value defaults to nil" do expect(resource.x).to be_nil end end end end context "with aliased properties" do with_property ":real, Integer" do it "should set the real property and emit a deprecation message" do expect(Chef).to receive(:deprecated).with(:property, "we don't like the deprecated property no more") resource_class.class_eval { deprecated_property_alias :deprecated, :real, "we don't like the deprecated property no more" } resource.deprecated 10 expect(resource.real).to eq 10 end end end context "redefining Object methods" do it "disallows redefining Object methods" do expect { resource_class.class_eval { property :hash } }.to raise_error(ArgumentError) end it "disallows redefining Chef::Resource methods" do expect { resource_class.class_eval { property :action } }.to raise_error(ArgumentError) end it "allows redefining properties on Chef::Resource" do expect { resource_class.class_eval { property :sensitive } }.not_to raise_error end end context "with a custom property type" do class CustomPropertyType < Chef::Property end with_property ":x, CustomPropertyType.new" do it "creates x with the given type" do expect(resource_class.properties[:x]).to be_kind_of(CustomPropertyType) end context "and a subclass" do let(:subresource_class) do new_resource_name = self.class.new_resource_name Class.new(resource_class) do resource_name new_resource_name end end let(:subresource) do subresource_class.new("blah") end context "with property :x, default: 10 on the subclass" do before do subresource_class.class_eval do property :x, default: 10 end end it "x has the given type and default on the subclass" do expect(subresource_class.properties[:x]).to be_kind_of(CustomPropertyType) expect(subresource_class.properties[:x].default).to eq(10) end it "x does not have the default on the superclass" do expect(resource_class.properties[:x]).to be_kind_of(CustomPropertyType) expect(resource_class.properties[:x].default).to be_nil end end end end with_property ":x, CustomPropertyType.new, default: 10" do it "passes the default to the custom property type" do expect(resource_class.properties[:x]).to be_kind_of(CustomPropertyType) expect(resource_class.properties[:x].default).to eq(10) end end end end