summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Keiser <john@johnkeiser.com>2015-06-03 14:05:46 -0700
committerJohn Keiser <john@johnkeiser.com>2015-06-23 15:23:01 -0700
commitc4257f9d016871c6b4243079542d803d5e7fa383 (patch)
tree89fe153347148f7643cd32abb46ad5033e645371
parent2d4e68e84540c536508c9f33fc1e47c3d8ff60f6 (diff)
downloadchef-c4257f9d016871c6b4243079542d803d5e7fa383.tar.gz
Create property on resource, alias attribute to it
-rw-r--r--lib/chef/mixin/params_validate.rb21
-rw-r--r--lib/chef/resource.rb58
-rw-r--r--lib/chef/resource/lwrp_base.rb8
-rw-r--r--spec/unit/property/state_spec.rb498
-rw-r--r--spec/unit/property/validation_spec.rb460
-rw-r--r--spec/unit/property_spec.rb794
6 files changed, 1823 insertions, 16 deletions
diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb
index 78d72dc801..ccc313db32 100644
--- a/lib/chef/mixin/params_validate.rb
+++ b/lib/chef/mixin/params_validate.rb
@@ -136,7 +136,8 @@ class Chef
value = _pv_opts_lookup(opts, key)
unless value.nil?
passes = false
- Array(to_be).each do |tb|
+ to_be = Array(to_be)
+ to_be.each do |tb|
passes = true if value == tb
end
unless passes
@@ -150,7 +151,8 @@ class Chef
value = _pv_opts_lookup(opts, key)
unless value.nil?
passes = false
- Array(to_be).each do |tb|
+ to_be = Array(to_be)
+ to_be.each do |tb|
passes = true if value.kind_of?(tb)
end
unless passes
@@ -177,14 +179,16 @@ class Chef
#
# Note, this will *PASS* if the object doesn't respond to the method.
# So, to make sure a value is not nil and not blank, you need to do
- # both :cannot_be => :blank *and* :cannot_be => :nil (or :required => true)
+ # both :cannot_be => [ :blank, :nil ]
def _pv_cannot_be(opts, key, predicate_method_base_name)
value = _pv_opts_lookup(opts, key)
- predicate_method = (predicate_method_base_name.to_s + "?").to_sym
+ Array(predicate_method_base_name).each do |method_name|
+ predicate_method = :"#{method_name}?"
- if value.respond_to?(predicate_method)
- if value.send(predicate_method)
- raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}"
+ if value.respond_to?(predicate_method)
+ if value.send(predicate_method)
+ raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}"
+ end
end
end
end
@@ -202,7 +206,7 @@ class Chef
value = _pv_opts_lookup(opts, key)
if value != nil
passes = false
- [ regex ].flatten.each do |r|
+ Array(regex).each do |r|
if value != nil
if r.match(value.to_s)
passes = true
@@ -239,4 +243,3 @@ class Chef
end
end
end
-
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index bae7608f5b..40a6e911a6 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -712,6 +712,64 @@ class Chef
provider(arg)
end
+ #
+ # Create a property on this resource class.
+ #
+ # If a superclass has this property, or if this property has already been
+ # defined by this resource, this will *override* the previous value.
+ #
+ # @param name [Symbol] The name of the property.
+ # @param options [Hash<Symbol,Object>] Validation options.
+ # @option options [Object,Array] :equal_to An object, or list
+ # of objects, that must be equal to the value using Ruby's `==`
+ # operator (`options[:is].any? { |v| v == value }`)
+ # @option options [Regexp,Array<Regexp>] :regex An object, or
+ # list of objects, that must match the value with `regex.match(value)`.
+ # @option options [Class,Array<Class>] :kind_of A class, or
+ # list of classes, that the value must be an instance of.
+ # @option options [Hash<String,Proc>] :callbacks A hash of
+ # messages -> procs, all of which match the value. The proc must
+ # return a truthy or falsey value (true means it matches).
+ # @option options [Symbol,Array<Symbol>] :respond_to A method
+ # name, or list of method names, the value must respond to.
+ # @option options [Symbol,Array<Symbol>] :cannot_be A property,
+ # or a list of properties, that the value cannot have (such as `:nil` or
+ # `:empty`). The method with a questionmark at the end is called on the
+ # value (e.g. `value.empty?`). If the value does not have this method,
+ # it is considered valid (i.e. if you don't respond to `empty?` we
+ # assume you are not empty).
+ # @option options [Proc] :coerce A proc which will be called to
+ # transform the user input to canonical form. The value is passed in,
+ # and the transformed value returned as output. Lazy values will *not*
+ # be passed to this method until after they are evaluated. Called in the
+ # context of the resource (meaning you can access other properties).
+ # @option options [Boolean] :required `true` if this property
+ # must be present; `false` otherwise. This is checked after the resource
+ # is fully initialized.
+ # @option options [Boolean] :name_property `true` if this
+ # property defaults to the same value as `name`. Equivalent to
+ # `default: lazy { name }`, except that #property_is_set? will
+ # return `true` if the property is set *or* if `name` is set.
+ # @option options [Boolean] :name_attribute Same as `name_property`.
+ # @option options [Object] :default The value this property
+ # will return if the user does not set one. If this is `lazy`, it will
+ # be run in the context of the instance (and able to access other
+ # properties).
+ #
+ # @return [Chef::Resource::PropertyType] The property type.
+ #
+ # @example With nothing
+ # property :x
+ #
+ # @example With options
+ # property :x, default: 'hi'
+ #
+ def self.property(name, **options)
+ define_method(name) do |arg=nil|
+ set_or_return(name.to_sym, arg, options)
+ end
+ end
+
# Set or return the list of "state attributes" implemented by the Resource
# subclass. State attributes are attributes that describe the desired state
# of the system, such as file permissions or ownership. In general, state
diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb
index ef3c2b5bba..443e0ed819 100644
--- a/lib/chef/resource/lwrp_base.rb
+++ b/lib/chef/resource/lwrp_base.rb
@@ -74,13 +74,7 @@ class Chef
resource_class
end
- # Define an attribute on this resource, including optional validation
- # parameters.
- def attribute(attr_name, validation_opts={})
- define_method(attr_name) do |arg=nil|
- set_or_return(attr_name.to_sym, arg, validation_opts)
- end
- end
+ alias :attribute :property
# Adds +action_names+ to the list of valid actions for this resource.
# Does not include superclass's action list when appending.
diff --git a/spec/unit/property/state_spec.rb b/spec/unit/property/state_spec.rb
new file mode 100644
index 0000000000..5a7d19af11
--- /dev/null
+++ b/spec/unit/property/state_spec.rb
@@ -0,0 +1,498 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Chef::Resource#identity and #state" do
+ include IntegrationSupport
+
+ class NewResourceNamer
+ @i = 0
+ def self.next
+ "chef_resource_property_spec_#{@i += 1}"
+ end
+ end
+
+ def self.new_resource_name
+ NewResourceNamer.next
+ end
+
+ let(:resource_class) do
+ new_resource_name = self.class.new_resource_name
+ Class.new(Chef::Resource) do
+ resource_name new_resource_name
+ end
+ end
+
+ let(:resource) do
+ resource_class.new("blah")
+ end
+
+ def self.english_join(values)
+ return '<nothing>' 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
+ properties = properties.map { |property| "property #{property}" }
+ context "With properties #{english_join(properties)}", *tags do
+ before do
+ properties.each do |property_str|
+ resource_class.class_eval(property_str, __FILE__, __LINE__)
+ end
+ end
+ instance_eval(&block)
+ end
+ end
+
+ # identity
+ context "Chef::Resource#identity_attr" do
+ with_property ":x" do
+ # it "name is the default identity" do
+ # expect(resource_class.identity_attr).to eq :name
+ # expect(resource_class.properties[:name].identity?).to be_falsey
+ # expect(resource.name).to eq 'blah'
+ # expect(resource.identity).to eq 'blah'
+ # end
+
+ it "identity_attr :x changes the identity" do
+ expect(resource_class.identity_attr :x).to eq :x
+ expect(resource_class.identity_attr).to eq :x
+ # expect(resource_class.properties[:name].identity?).to be_falsey
+ # expect(resource_class.properties[:x].identity?).to be_truthy
+
+ expect(resource.x 'woo').to eq 'woo'
+ expect(resource.x).to eq 'woo'
+
+ expect(resource.name).to eq 'blah'
+ expect(resource.identity).to eq 'woo'
+ end
+
+ # with_property ":y, identity: true" do
+ # context "and identity_attr :x" do
+ # before do
+ # resource_class.class_eval do
+ # identity_attr :x
+ # end
+ # end
+ #
+ # it "only returns :x as identity" do
+ # resource.x 'foo'
+ # resource.y 'bar'
+ # expect(resource_class.identity_attr).to eq :x
+ # expect(resource.identity).to eq 'foo'
+ # end
+ # it "does not flip y.desired_state off" do
+ # resource.x 'foo'
+ # resource.y 'bar'
+ # expect(resource_class.state_attrs).to eq [ :x, :y ]
+ # expect(resource.state).to eq({ x: 'foo', y: 'bar' })
+ # end
+ # end
+ # 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) do
+ subresource_class.new('sub')
+ end
+
+ it "name is the default identity on the subclass" do
+ # expect(subresource_class.identity_attr).to eq :name
+ # expect(subresource_class.properties[:name].identity?).to be_falsey
+ expect(subresource.name).to eq 'sub'
+ expect(subresource.identity).to eq 'sub'
+ end
+
+ context "With identity_attr :x on the superclass" do
+ before do
+ resource_class.class_eval do
+ identity_attr :x
+ end
+ end
+
+ it "The subclass inherits :x as identity" do
+ expect(subresource_class.identity_attr).to eq :x
+ # expect(subresource_class.properties[:name].identity?).to be_falsey
+ # expect(subresource_class.properties[:x].identity?).to be_truthy
+
+ subresource.x 'foo'
+ expect(subresource.identity).to eq 'foo'
+ end
+
+ # context "With property :y, identity: true on the subclass" do
+ # before do
+ # subresource_class.class_eval do
+ # property :y, identity: true
+ # end
+ # end
+ # it "The subclass's identity includes both x and y" do
+ # expect(subresource_class.identity_attr).to eq :x
+ # subresource.x 'foo'
+ # subresource.y 'bar'
+ # expect(subresource.identity).to eq({ x: 'foo', y: 'bar' })
+ # end
+ # end
+
+ # with_property ":y, String" do
+ with_property ":y, kind_of: String" do
+ context "With identity_attr :y on the subclass" do
+ before do
+ subresource_class.class_eval do
+ identity_attr :y
+ end
+ end
+ # it "y is part of state" do
+ # subresource.x 'foo'
+ # subresource.y 'bar'
+ # expect(subresource.state).to eq({ x: 'foo', y: 'bar' })
+ # expect(subresource_class.state_attrs).to eq [ :x, :y ]
+ # end
+ it "y is the identity" do
+ expect(subresource_class.identity_attr).to eq :y
+ subresource.x 'foo'
+ subresource.y 'bar'
+ expect(subresource.identity).to eq 'bar'
+ end
+ it "y still has validation" do
+ expect { subresource.y 12 }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+ end
+ end
+ end
+ end
+
+ # with_property ":string_only, String, identity: true", ":string_only2, String" do
+ # it "identity_attr does not change validation" do
+ # resource_class.identity_attr :string_only
+ # expect { resource.string_only 12 }.to raise_error Chef::Exceptions::ValidationFailed
+ # expect { resource.string_only2 12 }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ # end
+ #
+ # with_property ":x, desired_state: false" do
+ # it "identity_attr does not flip on desired_state" do
+ # resource_class.identity_attr :x
+ # resource.x 'hi'
+ # expect(resource.identity).to eq 'hi'
+ # # expect(resource_class.properties[:x].desired_state?).to be_falsey
+ # expect(resource_class.state_attrs).to eq []
+ # expect(resource.state).to eq({})
+ # end
+ # end
+
+ context "With custom property custom_property defined only as methods, using different variables for storage" do
+ before do
+ resource_class.class_eval do
+ def custom_property
+ @blarghle*3
+ end
+ def custom_property=(x)
+ @blarghle = x*2
+ end
+ end
+
+ context "And identity_attr :custom_property" do
+ before do
+ resource_class.class_eval do
+ identity_attr :custom_property
+ end
+ end
+
+ it "identity_attr comes back as :custom_property" do
+ # expect(resource_class.properties[:custom_property].identity?).to be_truthy
+ expect(resource_class.identity_attr).to eq :custom_property
+ end
+ it "custom_property becomes part of desired_state" do
+ # expect(resource_class.properties[:custom_property].desired_state?).to be_truthy
+ expect(resource_class.state_attrs).to eq [ :custom_property ]
+ end
+ it "identity_attr does not change custom_property's getter or setter" do
+ expect(resource.custom_property = 1).to eq 2
+ expect(resource.custom_property).to eq 6
+ end
+ it "custom_property is returned as the identity" do
+ expect(resource_class.identity_attr).to
+ expect(resource.identity).to be_nil
+ resource.custom_property = 1
+ expect(resource.identity).to eq 6
+ end
+ it "custom_property is part of desired state" do
+ resource.custom_property = 1
+ expect(resource.state).to eq({ custom_property: 6 })
+ end
+ it "property_is_set?(:custom_property) returns true even if it hasn't been set" do
+ expect(resource.property_is_set?(:custom_property)).to be_truthy
+ end
+ end
+ end
+ end
+ end
+
+ # context "PropertyType#identity" do
+ # with_property ":x, identity: true" do
+ # it "name is only part of the identity if an identity attribute is defined" do
+ # expect(resource_class.identity_attr).to eq :x
+ # resource.x 'woo'
+ # expect(resource.identity).to eq 'woo'
+ # end
+ # end
+ #
+ # with_property ":x, identity: true, default: 'xxx'",
+ # ":y, identity: true, default: 'yyy'",
+ # ":z, identity: true, default: 'zzz'" do
+ # it "identity_attr returns the first identity attribute if multiple are defined" do
+ # expect(resource_class.identity_attr).to eq :x
+ # end
+ # it "identity returns all identity values in a hash if multiple are defined" do
+ # resource.x 'foo'
+ # resource.y 'bar'
+ # resource.z 'baz'
+ # expect(resource.identity).to eq({ x: 'foo', y: 'bar', z: 'baz' })
+ # end
+ # it "identity returns only identity values that are set, and does not include defaults" do
+ # resource.x 'foo'
+ # resource.z 'baz'
+ # expect(resource.identity).to eq({ x: 'foo', z: 'baz' })
+ # end
+ # it "identity returns only set identity values in a hash, if there is only one set identity value" do
+ # resource.x 'foo'
+ # expect(resource.identity).to eq({ x: 'foo' })
+ # end
+ # it "identity returns an empty hash if no identity values are set" do
+ # expect(resource.identity).to eq({})
+ # end
+ # it "identity_attr wipes out any other identity attributes if multiple are defined" do
+ # resource_class.identity_attr :y
+ # resource.x 'foo'
+ # resource.y 'bar'
+ # resource.z 'baz'
+ # expect(resource.identity).to eq 'bar'
+ # end
+ # end
+ #
+ # with_property ":x, identity: true, name_property: true" do
+ # it "identity when x is not defined returns the value of x" do
+ # expect(resource.identity).to eq 'blah'
+ # end
+ # it "state when x is not defined returns the value of x" do
+ # expect(resource.state).to eq({ x: 'blah' })
+ # end
+ # end
+ # end
+
+ # state_attrs
+ context "Chef::Resource#state_attrs" do
+ it "name is not part of state_attrs" do
+ expect(Chef::Resource.state_attrs).to eq []
+ expect(resource_class.state_attrs).to eq []
+ expect(resource.state).to eq({})
+ end
+
+ # with_property ":x", ":y", ":z" do
+ # it "x, y and z are state attributes" do
+ # resource.x 1
+ # resource.y 2
+ # resource.z 3
+ # expect(resource_class.state_attrs).to eq [ :x, :y, :z ]
+ # expect(resource.state).to eq(x: 1, y: 2, z: 3)
+ # end
+ # it "values that are not set are not included in state" do
+ # resource.x 1
+ # expect(resource.state).to eq(x: 1)
+ # end
+ # it "when no values are set, nothing is included in state" do
+ # end
+ # end
+ #
+ # with_property ":x", ":y, desired_state: false", ":z, desired_state: true" do
+ # it "x and z are state attributes, and y is not" do
+ # resource.x 1
+ # resource.y 2
+ # resource.z 3
+ # expect(resource_class.state_attrs).to eq [ :x, :z ]
+ # expect(resource.state).to eq(x: 1, z: 3)
+ # end
+ # end
+
+ # with_property ":x, name_property: true" do
+ # it "Unset values with name_property are included in state" do
+ # expect(resource.state).to eq(x: 'blah')
+ # end
+ # it "Set values with name_property are included in state" do
+ # resource.x 1
+ # expect(resource.state).to eq(x: 1)
+ # end
+ # end
+
+ # with_property ":x, default: 1" do
+ # it "Unset values with defaults are not included in state" do
+ # expect(resource.state).to eq({})
+ # end
+ # it "Set values with defaults are included in state" do
+ # resource.x 1
+ # expect(resource.state).to eq(x: 1)
+ # end
+ # end
+
+ context "With a class with a normal getter and setter" do
+ before do
+ resource_class.class_eval do
+ def x
+ @blah*3
+ end
+ def x=(value)
+ @blah = value*2
+ end
+ end
+ end
+ it "state_attrs(:x) causes the value to be included in properties" do
+ resource_class.state_attrs(:x)
+ resource.x = 1
+
+ expect(resource.x).to eq 6
+ expect(resource.state).to eq(x: 6)
+ end
+ end
+
+ # with_property ":x, Integer, identity: true" do
+ # it "state_attrs(:x) leaves the property in desired_state" do
+ # resource_class.state_attrs(:x)
+ # resource.x 10
+ #
+ # # expect(resource_class.properties[:x].desired_state?).to be_truthy
+ # expect(resource_class.state_attrs).to eq [ :x ]
+ # expect(resource.state).to eq(x: 10)
+ # end
+ # it "state_attrs(:x) does not turn off validation" do
+ # resource_class.state_attrs(:x)
+ # expect { resource.x 'ouch' }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ # it "state_attrs(:x) does not turn off identity" do
+ # resource_class.state_attrs(:x)
+ # resource.x 10
+ #
+ # expect(resource_class.identity_attr).to eq :x
+ # # expect(resource_class.properties[:x].identity?).to be_truthy
+ # expect(resource.identity).to eq 10
+ # end
+ # end
+
+ # with_property ":x, Integer, identity: true, desired_state: false" do
+ # before do
+ # resource_class.class_eval do
+ # def y
+ # 20
+ # end
+ # end
+ # end
+ # it "state_attrs(:x) sets the property in desired_state" do
+ # resource_class.state_attrs(:x)
+ # resource.x 10
+ #
+ # # expect(resource_class.properties[:x].desired_state?).to be_truthy
+ # expect(resource_class.state_attrs).to eq [ :x ]
+ # expect(resource.state).to eq(x: 10)
+ # end
+ # it "state_attrs(:x) does not turn off validation" do
+ # resource_class.state_attrs(:x)
+ # expect { resource.x 'ouch' }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ # it "state_attrs(:x) does not turn off identity" do
+ # resource_class.state_attrs(:x)
+ # resource.x 10
+ #
+ # expect(resource_class.identity_attr).to eq :x
+ # # expect(resource_class.properties[:x].identity?).to be_truthy
+ # expect(resource.identity).to eq 10
+ # end
+ # it "state_attrs(:y) adds y and removes x from desired state" do
+ # resource_class.state_attrs(:y)
+ # resource.x 10
+ #
+ # # expect(resource_class.properties[:x].desired_state?).to be_falsey
+ # # expect(resource_class.properties[:y].desired_state?).to be_truthy
+ # expect(resource_class.state_attrs).to eq [ :y ]
+ # expect(resource.state).to eq(y: 20)
+ # end
+ # it "state_attrs(:y) does not turn off validation" do
+ # resource_class.state_attrs(:y)
+ #
+ # expect { resource.x 'ouch' }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ # it "state_attrs(:y) does not turn off identity" do
+ # resource_class.state_attrs(:y)
+ # resource.x 10
+ #
+ # expect(resource_class.identity_attr).to eq :x
+ # # expect(resource_class.properties[:x].identity?).to be_truthy
+ # expect(resource.identity).to eq 10
+ # end
+ #
+ # context "With a subclassed resource" do
+ # let(:resource_subclass) do
+ # new_resource_name = self.class.new_resource_name
+ # Class.new(resource_class) do
+ # resource_name new_resource_name
+ # end
+ # end
+ # let(:subresource) do
+ # resource_subclass.new('blah')
+ # end
+ # it "state_attrs(:x) sets the property in desired_state" do
+ # resource_subclass.state_attrs(:x)
+ # subresource.x 10
+ #
+ # # expect(resource_subclass.properties[:x].desired_state?).to be_truthy
+ # expect(resource_subclass.state_attrs).to eq [ :x ]
+ # expect(subresource.state).to eq(x: 10)
+ # end
+ # it "state_attrs(:x) does not turn off validation" do
+ # resource_subclass.state_attrs(:x)
+ # expect { subresource.x 'ouch' }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ # it "state_attrs(:x) does not turn off identity" do
+ # resource_subclass.state_attrs(:x)
+ # subresource.x 10
+ #
+ # expect(resource_subclass.identity_attr).to eq :x
+ # # expect(resource_subclass.properties[:x].identity?).to be_truthy
+ # expect(subresource.identity).to eq 10
+ # end
+ # it "state_attrs(:y) adds y and removes x from desired state" do
+ # resource_subclass.state_attrs(:y)
+ # subresource.x 10
+ #
+ # # expect(resource_subclass.properties[:x].desired_state?).to be_falsey
+ # # expect(resource_subclass.properties[:y].desired_state?).to be_truthy
+ # expect(resource_subclass.state_attrs).to eq [ :y ]
+ # expect(subresource.state).to eq(y: 20)
+ # end
+ # it "state_attrs(:y) does not turn off validation" do
+ # resource_subclass.state_attrs(:y)
+ #
+ # expect { subresource.x 'ouch' }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ # it "state_attrs(:y) does not turn off identity" do
+ # resource_subclass.state_attrs(:y)
+ # subresource.x 10
+ #
+ # expect(resource_subclass.identity_attr).to eq :x
+ # # expect(resource_subclass.properties[:x].identity?).to be_truthy
+ # expect(subresource.identity).to eq 10
+ # end
+ # end
+ # end
+ end
+
+end
diff --git a/spec/unit/property/validation_spec.rb b/spec/unit/property/validation_spec.rb
new file mode 100644
index 0000000000..a88ebfd0cf
--- /dev/null
+++ b/spec/unit/property/validation_spec.rb
@@ -0,0 +1,460 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Chef::Resource.property validation" do
+ include IntegrationSupport
+
+ class 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 blah
+ Namer.next_index
+ end
+ def self.blah
+ "class#{Namer.next_index}"
+ end
+ end
+ end
+
+ let(:resource) do
+ resource_class.new("blah")
+ end
+
+ def self.english_join(values)
+ return '<nothing>' 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
+ properties = properties.map { |property| "property #{property}" }
+ context "With properties #{english_join(properties)}", *tags do
+ before do
+ properties.each do |property_str|
+ resource_class.class_eval(property_str, __FILE__, __LINE__)
+ end
+ end
+ instance_eval(&block)
+ end
+ end
+
+ def self.validation_test(validation, success_values, failure_values)
+ with_property ":x, #{validation}" do
+ success_values.each do |v|
+ it "value #{v.inspect} is valid" do
+ expect(resource.x v).to eq v
+ end
+ end
+ failure_values.each do |v|
+ if v.nil?
+ it "setting value to #{v.inspect} does not change the value" do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ resource.x success_values.first
+ expect(resource.x v).to eq success_values.first
+ expect(resource.x).to eq success_values.first
+ end
+ else
+ it "value #{v.inspect} is invalid" do
+ expect { resource.x v }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+ end
+ end
+ end
+
+ # Bare types
+ # context "bare types" do
+ # validation_test 'String',
+ # [ 'hi' ],
+ # [ 10, nil ]
+ #
+ # validation_test ':a',
+ # [ :a ],
+ # [ :b, nil ]
+ #
+ # validation_test ':a, is: :b',
+ # [ :a, :b ],
+ # [ :c, nil ]
+ #
+ # validation_test ':a, is: [ :b, :c ]',
+ # [ :a, :b, :c ],
+ # [ :d, nil ]
+ #
+ # validation_test '[ :a, :b ], is: :c',
+ # [ :a, :b, :c ],
+ # [ :d, nil ]
+ #
+ # validation_test '[ :a, :b ], is: [ :c, :d ]',
+ # [ :a, :b, :c, :d ],
+ # [ :e, nil ]
+ #
+ # validation_test 'nil',
+ # [ nil ],
+ # [ :a ]
+ #
+ # validation_test '[ nil ]',
+ # [ nil ],
+ # [ :a ]
+ #
+ # validation_test '[]',
+ # [ :a ],
+ # []
+ # end
+
+ # is
+ # context "is" do
+ # # Class
+ # validation_test 'is: String',
+ # [ 'a', '' ],
+ # [ nil, :a, 1 ]
+ #
+ # # Value
+ # validation_test 'is: :a',
+ # [ :a ],
+ # [ :b, nil ]
+ #
+ # validation_test 'is: [ :a, :b ]',
+ # [ :a, :b ],
+ # [ [ :a, :b ], nil ]
+ #
+ # validation_test 'is: [ [ :a, :b ] ]',
+ # [ [ :a, :b ] ],
+ # [ :a, :b, nil ]
+ #
+ # # Regex
+ # validation_test 'is: /abc/',
+ # [ 'abc', 'wowabcwow' ],
+ # [ '', 'abac', nil ]
+ #
+ # # PropertyType
+ # validation_test 'is: PropertyType.new(is: :a)',
+ # [ :a ],
+ # [ :b, nil ]
+ #
+ # # RSpec Matcher
+ # class Globalses
+ # extend RSpec::Matchers
+ # end
+ #
+ # validation_test "is: Globalses.eq(10)",
+ # [ 10 ],
+ # [ 1, nil ]
+ #
+ # # Proc
+ # validation_test 'is: proc { |x| x }',
+ # [ true, 1 ],
+ # [ false, nil ]
+ #
+ # validation_test 'is: proc { |x| x > blah }',
+ # [ 10 ],
+ # [ -1 ]
+ #
+ # validation_test 'is: nil',
+ # [ nil ],
+ # [ 'a' ]
+ #
+ # validation_test 'is: [ String, nil ]',
+ # [ 'a', nil ],
+ # [ :b ]
+ # end
+
+ # Combination
+ context "combination" do
+ validation_test 'kind_of: String, equal_to: "a"',
+ [ 'a' ],
+ [ 'b', nil ]
+ end
+
+ # equal_to
+ context "equal_to" do
+ # Value
+ validation_test 'equal_to: :a',
+ [ :a ],
+ [ :b, nil ]
+
+ validation_test 'equal_to: [ :a, :b ]',
+ [ :a, :b ],
+ [ [ :a, :b ], nil ]
+
+ validation_test 'equal_to: [ [ :a, :b ] ]',
+ [ [ :a, :b ] ],
+ [ :a, :b, nil ]
+
+ validation_test 'equal_to: nil',
+ [ nil ],
+ [ 'a' ]
+
+ validation_test 'equal_to: [ "a", nil ]',
+ [ 'a', nil ],
+ [ 'b' ]
+
+ validation_test 'equal_to: [ nil, "a" ]',
+ [ 'a', nil ],
+ [ 'b' ]
+ end
+
+ # kind_of
+ context "kind_of" do
+ validation_test 'kind_of: String',
+ [ 'a' ],
+ [ :b, nil ]
+
+ validation_test 'kind_of: [ String, Symbol ]',
+ [ 'a', :b ],
+ [ 1, nil ]
+
+ validation_test 'kind_of: [ Symbol, String ]',
+ [ 'a', :b ],
+ [ 1, nil ]
+
+ validation_test 'kind_of: NilClass',
+ [ nil ],
+ [ 'a' ]
+
+ validation_test 'kind_of: [ NilClass, String ]',
+ [ nil, 'a' ],
+ [ :a ]
+ end
+
+ # regex
+ context "regex" do
+ validation_test 'regex: /abc/',
+ [ 'xabcy' ],
+ [ 'gbh', 123, nil ]
+
+ validation_test 'regex: [ /abc/, /z/ ]',
+ [ 'xabcy', 'aza' ],
+ [ 'gbh', 123, nil ]
+
+ validation_test 'regex: [ /z/, /abc/ ]',
+ [ 'xabcy', 'aza' ],
+ [ 'gbh', 123, nil ]
+ end
+
+ # callbacks
+ context "callbacks" do
+ validation_test 'callbacks: { "a" => proc { |x| x > 10 }, "b" => proc { |x| x%2 == 0 } }',
+ [ 12 ],
+ [ 11, 4 ]
+
+ validation_test 'callbacks: { "a" => proc { |x| x%2 == 0 }, "b" => proc { |x| x > 10 } }',
+ [ 12 ],
+ [ 11, 4 ]
+
+ validation_test 'callbacks: { "a" => proc { |x| x.nil? } }',
+ [ nil ],
+ [ 'a' ]
+ end
+
+ # respond_to
+ context "respond_to" do
+ validation_test 'respond_to: :split',
+ [ 'hi' ],
+ [ 1, nil ]
+
+ validation_test 'respond_to: "split"',
+ [ 'hi' ],
+ [ 1, nil ]
+
+ validation_test 'respond_to: [ :split, :to_s ]',
+ [ 'hi' ],
+ [ 1, nil ]
+
+ validation_test 'respond_to: %w(split to_s)',
+ [ 'hi' ],
+ [ 1, nil ]
+
+ validation_test 'respond_to: [ :to_s, :split ]',
+ [ 'hi' ],
+ [ 1, nil ]
+ end
+
+ context "cannot_be" do
+ validation_test 'cannot_be: :empty',
+ [ nil, 1, [1,2], { a: 10 } ],
+ [ [] ]
+
+ validation_test 'cannot_be: "empty"',
+ [ nil, 1, [1,2], { a: 10 } ],
+ [ [] ]
+
+ validation_test 'cannot_be: [ :empty, :nil ]',
+ [ 1, [1,2], { a: 10 } ],
+ [ [], nil ]
+
+ validation_test 'cannot_be: [ "empty", "nil" ]',
+ [ 1, [1,2], { a: 10 } ],
+ [ [], nil ]
+
+ validation_test 'cannot_be: [ :nil, :empty ]',
+ [ 1, [1,2], { a: 10 } ],
+ [ [], nil ]
+
+ validation_test 'cannot_be: [ :empty, :nil, :blahblah ]',
+ [ 1, [1,2], { a: 10 } ],
+ [ [], nil ]
+ end
+
+ context "required" do
+ with_property ':x, required: true' do
+ it "if x is not specified, retrieval fails" do
+ expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ it "value nil is invalid" do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ expect { resource.x nil }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ it "value 1 is valid" do
+ expect(resource.x 1).to eq 1
+ expect(resource.x).to eq 1
+ end
+ end
+
+ # with_property ':x, [String, nil], required: true' do
+ with_property ':x, kind_of: [String, NilClass], required: true' do
+ it "if x is not specified, retrieval fails" do
+ expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ # it "value nil is valid" do
+ # expect(resource.x nil).to be_nil
+ # expect(resource.x).to be_nil
+ # end
+ it "value '1' is valid" do
+ expect(resource.x '1').to eq '1'
+ expect(resource.x).to eq '1'
+ end
+ it "value 1 is invalid" do
+ expect { resource.x 1 }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+
+ # with_property ':x, name_property: true, required: true' do
+ with_property ':x, required: true, name_attribute: true' do
+ it "if x is not specified, retrieval fails" do
+ expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ # it "value nil is invalid" do
+ # expect { resource.x nil }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ it "value 1 is valid" do
+ expect(resource.x 1).to eq 1
+ expect(resource.x).to eq 1
+ end
+ end
+
+ # with_property ':x, default: 10, required: true' do
+ with_property ':x, required: true, default: 10' do
+ it "if x is not specified, retrieval fails" do
+ expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ # it "value nil is invalid" do
+ # expect { resource.x nil }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ it "value 1 is valid" do
+ expect(resource.x 1).to eq 1
+ expect(resource.x).to eq 1
+ end
+ end
+ end
+
+ context "custom validators (def _pv_blarghle)" do
+ before do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ end
+
+ with_property ':x, blarghle: 1' do
+ context "and a class that implements _pv_blarghle" do
+ before do
+ resource_class.class_eval do
+ def _pv_blarghle(opts, key, value)
+ if _pv_opts_lookup(opts, key) != value
+ raise Chef::Exceptions::ValidationFailed, "ouch"
+ end
+ end
+ end
+ end
+
+ # it "getting the value causes a deprecation warning" do
+ # Chef::Config[:treat_deprecation_warnings_as_errors] = true
+ # expect { resource.x }.to raise_error Chef::Exceptions::DeprecatedFeatureError
+ # end
+
+ it "value 1 is valid" do
+ expect(resource.x 1).to eq 1
+ expect(resource.x).to eq 1
+ end
+
+ it "value '1' is invalid" do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ expect { resource.x '1' }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+
+ # it "value nil is invalid" do
+ # Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ # expect { resource.x nil }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ end
+ end
+
+ with_property ':x, blarghle: 1' do
+ context "and a class that implements _pv_blarghle" do
+ before do
+ resource_class.class_eval do
+ def _pv_blarghle(opts, key, value)
+ if _pv_opts_lookup(opts, key) != value
+ raise Chef::Exceptions::ValidationFailed, "ouch"
+ end
+ end
+ end
+ end
+
+ it "value 1 is valid" do
+ expect(resource.x 1).to eq 1
+ expect(resource.x).to eq 1
+ end
+
+ it "value '1' is invalid" do
+ expect { resource.x '1' }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+
+ # it "value nil is invalid" do
+ # expect { resource.x nil }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ end
+ end
+ end
+end
diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb
new file mode 100644
index 0000000000..b9c0d27da8
--- /dev/null
+++ b/spec/unit/property_spec.rb
@@ -0,0 +1,794 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Chef::Resource.property" do
+ include IntegrationSupport
+
+ class 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 '<nothing>' 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
+ properties = properties.map { |property| "property #{property}" }
+ context "With properties #{english_join(properties)}", *tags do
+ before do
+ properties.each do |property_str|
+ resource_class.class_eval(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 "emits a deprecation warning and does a get, if set to nil" do
+ # expect(resource.bare_property 10).to eq 10
+ # expect { resource.bare_property nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError
+ # Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ # expect(resource.bare_property nil).to eq 10
+ # expect(resource.bare_property).to eq 10
+ # 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 ":x, Integer"
+ with_property ':x, kind_of: 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
+ # 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 overwritten" do
+ 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
+ end
+ end
+
+ # context "with property :x, String on the subclass" do
+ context "with property :x, kind_of: String on the subclass" do
+ before do
+ subresource_class.class_eval do
+ # property :x, String
+ property :x, kind_of: 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::PropertyType#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 true" do
+ # resource.x
+ # expect(resource.property_is_set?(:x)).to be_truthy
+ # 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 true" do
+ # resource.x
+ # expect(resource.property_is_set?(:x)).to be_truthy
+ # 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 true" do
+ # resource.x
+ # expect(resource.property_is_set?(:x)).to be_truthy
+ # end
+ # end
+ # end
+
+ context "Chef::Resource::PropertyType#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).to eq({})
+ 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 not included in identity" do
+ expect(resource.state).to eq({})
+ 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 "hash default" do
+ with_property ':x, default: {}' do
+ it "when x is not set, it returns {}" do
+ expect(resource.x).to eq({})
+ 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
+ # TODO this isn't really great behavior, but it's noted here so we find out
+ # if it changed.
+ expect(resource.x.object_id).to eq(resource_class.new('blah2').x.object_id)
+ end
+ end
+
+ # with_property ':x, default: lazy { {} }' do
+ with_property ':x, default: Chef::DelayedEvaluator.new { {} }' do
+ it "when x is not set, it returns {}" do
+ expect(resource.x).to eq({})
+ end
+ # it "The value is different each time it is called" do
+ # value = resource.x
+ # expect(value).to eq({})
+ # expect(resource.x.object_id).not_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 "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
+ with_property ':x, default: Chef::DelayedEvaluator.new { 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"
+ # expect(resource.x).to eq "blah3"
+ # end
+ it "x is run outside the instance" do
+ expect(resource.x).to eq "class"
+ end
+ end
+
+ # with_property ':x, default: lazy { |x| "#{blah}#{x.blah}" }' do
+ with_property ':x, default: Chef::DelayedEvaluator.new { |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"
+ # expect(resource.x).to eq "classblah3"
+ end
+ end
+ end
+
+ context "validation of defaults" do
+ #with_property ':x, String, default: 10' do
+ with_property ':x, default: 10, kind_of: String' 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, a validation error is raised" do
+ expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+
+ # with_property ":x, String, default: lazy { Namer.next_index }" do
+ with_property ":x, default: Chef::DelayedEvaluator.new { Namer.next_index }, kind_of: String" 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, a validation error is raised" 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 }, is: proc { |v| Namer.next_index; true }" do
+ # it "when x is retrieved, 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
+ # 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, no more than once" do
+ # expect(resource.x).to eq '101'
+ # expect(resource.x).to eq '101'
+ # expect(Namer.current_index).to eq 1
+ # end
+ # end
+ #
+ # 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
+ # end
+ #
+ # with_property ':x, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }, is: proc { |v| Namer.next_index; true }' do
+ # it "when x is retrieved, coercion is run each time" do
+ # expect(resource.x).to eq '101'
+ # expect(Namer.current_index).to eq 2
+ # expect(resource.x).to eq '103'
+ # expect(Namer.current_index).to eq 4
+ # 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 before validating and fails" 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 before validating and fails" do
+ # expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ # end
+ # with_property ':x, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }, is: proc { |v| Namer.next_index; true }' do
+ # it "when x is retrieved, coercion and validation is run on each access" do
+ # expect(resource.x).to eq '101'
+ # expect(Namer.current_index).to eq 2
+ # expect(resource.x).to eq '103'
+ # expect(Namer.current_index).to eq 4
+ # 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 caller's blah" do
+ resource.x lazy { blah }
+ expect(resource.x).to eq "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
+ with_property ':x, kind_of: 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
+ with_property ':x, callbacks: { "a" => 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::PropertyType#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 "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| 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::PropertyType validation" do
+ # with_property ':x, is: proc { |v| Namer.next_index; v.is_a?(Integer) }' do
+ with_property ':x, callbacks: { "a" => 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
+
+ # [ 'name_attribute', 'name_property' ].each do |name|
+ name = 'name_attribute'
+ context "Chef::Resource::PropertyType##{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, default: 10, #{name}: true" do
+ it "chooses default over #{name}" do
+ expect(resource.x).to eq 10
+ end
+ end
+ # with_property ":x, #{name}: true, default: 10" do
+ # it "chooses default over #{name}" do
+ # # expect(resource.x).to eq 10
+ # expect(resource.x).to eq 10
+ # end
+ # end
+ end
+ # end
+end