diff options
author | John Keiser <john@johnkeiser.com> | 2015-06-15 17:24:41 -0700 |
---|---|---|
committer | John Keiser <john@johnkeiser.com> | 2015-06-18 15:26:09 -0700 |
commit | 20a953d0dbc16d6a0f095a9bf0096175c3727ed9 (patch) | |
tree | bdd2bf87c1a59912a409cf49cafb2a185b5068b5 | |
parent | 36b2a267f14969c73a3d4d5410cacc461523c291 (diff) | |
download | chef-20a953d0dbc16d6a0f095a9bf0096175c3727ed9.tar.gz |
Add property computed: proc { ... } for non-sticky value
-rw-r--r-- | lib/chef/mixin/params_validate.rb | 39 | ||||
-rw-r--r-- | spec/unit/property_spec.rb | 145 |
2 files changed, 170 insertions, 14 deletions
diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb index 3edc761ffc..c5836122a2 100644 --- a/lib/chef/mixin/params_validate.rb +++ b/lib/chef/mixin/params_validate.rb @@ -97,6 +97,8 @@ class Chef else default = NOT_PASSED end + computed = validation.delete(:computed) + computed ||= validation.delete('computed') coerce = validation.delete(:coerce) coerce ||= validation.delete('coerce') name_property = validation.delete(:name_property) @@ -131,8 +133,12 @@ class Chef # Get the default value else _pv_required(opts, symbol, required, explicitly_allows_nil?(symbol, validation)) if required - _pv_default(opts, symbol, default) unless default == NOT_PASSED - _pv_name_property(opts, symbol, name_property) + if !opts.has_key?(symbol) + got_default = true + _pv_computed(opts, symbol, computed) + _pv_default(opts, symbol, default) unless default == NOT_PASSED + _pv_name_property(opts, symbol, name_property) + end if opts.has_key?(symbol) # Handle lazy defaults. @@ -150,7 +156,7 @@ class Chef validate(opts, { symbol => validation }) # Defaults are presently "stickily" set on the instance - self.instance_variable_set(iv_symbol, opts[symbol]) + self.instance_variable_set(iv_symbol, opts[symbol]) unless got_default && computed end end @@ -321,6 +327,33 @@ class Chef end # + # A computed default value (non-sticky). + # + # When the property is not assigned, this will be used. + # + # @example + # ```ruby + # property :x + # property :y, computed: proc { x+2 } + # ``` + # + # @example + # ```ruby + # property :x + # property :y, computed: proc { |r| r.x+2 } + # ``` + # + def _pv_computed(opts, key, computed_value) + if !opts.has_key?(key) && computed_value + if computed_value.arity >= 1 + opts[key] = computed_value.call(self) + else + opts[key] = instance_eval(&computed_value) + end + end + end + + # # List of regexes values that must match. # # Uses regex.match() to evaluate. At least one must match for the value to diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index 8c01bf7b49..f048355269 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -281,9 +281,9 @@ describe "Chef::Resource.property" 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 + it "when x is retrieved, property_is_set?(:x) is true" do resource.x - expect(resource.property_is_set?(:x)).to be_falsey + expect(resource.property_is_set?(:x)).to be_truthy end end @@ -367,8 +367,7 @@ describe "Chef::Resource.property" do 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. + # Bleagh. But it's what we do. expect(resource.x.object_id).to eq(resource_class.new('blah2').x.object_id) end end @@ -377,11 +376,11 @@ describe "Chef::Resource.property" 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 "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 @@ -407,7 +406,7 @@ describe "Chef::Resource.property" do 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" + expect(resource.x).to eq "blah1" end end @@ -418,7 +417,7 @@ describe "Chef::Resource.property" do 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" + expect(resource.x).to eq "classblah1" end end end @@ -535,6 +534,130 @@ describe "Chef::Resource.property" do end end + context "Chef::Resource::PropertyType#computed" do + context "hash computed default" do + with_property ':x, computed: proc { {} }' 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, computed: proc { 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 + end + + with_property ':x, computed: proc { |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 computed defaults" do + with_property ":x, String, computed: proc { 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, 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, computed: proc { Namer.next_index }, is: proc { |v| Namer.next_index; true }" do + it "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 computed defaults" do + with_property ':x, coerce: proc { |v| "#{v}#{next_index}" }, computed: proc { 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, proc { |v| Namer.next_index; true }, coerce: proc { |v| "#{v}#{next_index}" }, computed: proc { 10 }' do + it "coercion is run each 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 '103' + expect(Namer.current_index).to eq 4 + end + end + + context "validation and coercion of computed defaults" do + with_property ':x, String, coerce: proc { |v| "#{v}#{next_index}" }, computed: proc { 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}" }, computed: proc { 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, proc { |v| Namer.next_index; true }, coerce: proc { |v| "#{v}#{next_index}" }, computed: proc { 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 '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 |