diff options
author | John Keiser <john@johnkeiser.com> | 2015-12-18 14:48:45 -0800 |
---|---|---|
committer | John Keiser <john@johnkeiser.com> | 2016-01-27 10:23:20 -0800 |
commit | 7520f3d36d2c8029c6c2996dd9289e9f74b9e6d3 (patch) | |
tree | 8e9aa1e623d3c92794dfc502be7350e8aa760e92 | |
parent | c3f1021fc6107808826461279cfd6eb055aa6162 (diff) | |
download | chef-7520f3d36d2c8029c6c2996dd9289e9f74b9e6d3.tar.gz |
Fix nil with properties:
1. Warn when default values are invalid.
2. Never validate nil (on set or get) if there is no default.
3. Emit "will be invalid in Chef 13" warning when setting an invalid nil value.
-rw-r--r-- | lib/chef/exceptions.rb | 1 | ||||
-rw-r--r-- | lib/chef/mixin/params_validate.rb | 12 | ||||
-rw-r--r-- | lib/chef/property.rb | 129 | ||||
-rw-r--r-- | lib/chef/resource/chef_gem.rb | 4 | ||||
-rw-r--r-- | lib/chef/resource/file.rb | 2 | ||||
-rw-r--r-- | spec/integration/client/client_spec.rb | 2 | ||||
-rw-r--r-- | spec/unit/property/validation_spec.rb | 215 | ||||
-rw-r--r-- | spec/unit/property_spec.rb | 88 | ||||
-rw-r--r-- | spec/unit/provider/package/dpkg_spec.rb | 38 | ||||
-rw-r--r-- | spec/unit/resource_reporter_spec.rb | 2 |
10 files changed, 297 insertions, 196 deletions
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index f9a717471c..08d916064f 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -71,6 +71,7 @@ class Chef class RoleNotFound < RuntimeError; end class DuplicateRole < RuntimeError; end class ValidationFailed < ArgumentError; end + class CannotValidateStaticallyError < ArgumentError; end class InvalidPrivateKey < ArgumentError; end class MissingKeyAttribute < ArgumentError; end class KeyCommandInputError < ArgumentError; end diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb index 841cf9e6a2..3585def853 100644 --- a/lib/chef/mixin/params_validate.rb +++ b/lib/chef/mixin/params_validate.rb @@ -22,7 +22,6 @@ require "chef/delayed_evaluator" class Chef module Mixin module ParamsValidate - # Takes a hash of options, along with a map to validate them. Returns the original # options hash, plus any changes that might have been made (through things like setting # default values in the validation map) @@ -333,6 +332,7 @@ class Chef def _pv_name_property(opts, key, is_name_property=true) if is_name_property if opts[key].nil? + raise CannotValidateStaticallyError, "name_property cannot be evaluated without a resource." if self == Chef::Mixin::ParamsValidate opts[key] = self.instance_variable_get(:"@name") end end @@ -403,6 +403,7 @@ class Chef to_be.each do |tb| case tb when Proc + raise CannotValidateStaticallyError, "is: proc { } must be evaluated once for each resource" if self == Chef::Mixin::ParamsValidate return true if instance_exec(value, &tb) when Property validate(opts, { key => tb.validation_options }) @@ -436,12 +437,21 @@ class Chef # def _pv_coerce(opts, key, coercer) if opts.has_key?(key.to_s) + raise CannotValidateStaticallyError, "coerce must be evaluated for each resource." if self == Chef::Mixin::ParamsValidate opts[key.to_s] = instance_exec(opts[key], &coercer) elsif opts.has_key?(key.to_sym) + raise CannotValidateStaticallyError, "coerce must be evaluated for each resource." if self == Chef::Mixin::ParamsValidate opts[key.to_sym] = instance_exec(opts[key], &coercer) end end + # We allow Chef::Mixin::ParamsValidate.validate(), but we will raise an + # error if you try to do anything requiring there to be an actual resource. + # This way, you can statically validate things if you have constant validation + # (which is the norm). + extend self + + # Used by #set_or_return to avoid emitting a deprecation warning for # "value nil" and to keep default stickiness working exactly the same # @api private diff --git a/lib/chef/property.rb b/lib/chef/property.rb index 8b5c6560a9..437ee10147 100644 --- a/lib/chef/property.rb +++ b/lib/chef/property.rb @@ -87,16 +87,21 @@ class Chef # is fully initialized. # def initialize(**options) - options.each { |k,v| options[k.to_sym] = v if k.is_a?(String) } + options.each { |k,v| options[k.to_sym] = v; options.delete(k) if k.is_a?(String) } + @options = options + options[:name] = options[:name].to_sym if options[:name] + options[:instance_variable_name] = options[:instance_variable_name].to_sym if options[:instance_variable_name] + # Replace name_attribute with name_property if options.has_key?(:name_attribute) # If we have both name_attribute and name_property and they differ, raise an error if options.has_key?(:name_property) - raise ArgumentError, "Cannot specify both name_property and name_attribute together on property #{options[:name]}#{options[:declared_in] ? " of resource #{options[:declared_in].resource_name}" : ""}." + raise ArgumentError, "Cannot specify both name_property and name_attribute together on property #{self}." end # replace name_property with name_attribute in place options = Hash[options.map { |k,v| k == :name_attribute ? [ :name_property, v ] : [ k,v ] }] + @options = options end # Only pick the first of :default, :name_property and :name_attribute if @@ -109,17 +114,22 @@ class Chef options.delete(:name_property) preferred_default = :default end - Chef.log_deprecation("Cannot specify both default and name_property together on property #{options[:name]}#{options[:declared_in] ? " of resource #{options[:declared_in].resource_name}" : ""}. Only one (#{preferred_default}) will be obeyed. In Chef 13, this will become an error.") + Chef.log_deprecation("Cannot specify both default and name_property together on property #{self}. Only one (#{preferred_default}) will be obeyed. In Chef 13, this will become an error. Please remove one or the other from the property.") end - @options = options - - options[:name] = options[:name].to_sym if options[:name] - options[:instance_variable_name] = options[:instance_variable_name].to_sym if options[:instance_variable_name] + # Validate the default early, so the user gets a good error message, and + # cache it so we don't do it again if so + begin + # If we can validate it all the way to output, do it. + @stored_default = input_to_stored_value(nil, default, is_default: true) + rescue Chef::Exceptions::CannotValidateStaticallyError + # If the validation is not static (i.e. has procs), we will have to + # coerce and validate the default each time we run + end end def to_s - name + "#{name}#{declared_in ? " of resource #{declared_in.resource_name}" : ""}" end # @@ -253,14 +263,26 @@ class Chef return get(resource) end - if value.nil? && !explicitly_accepts_nil?(resource) + if value.nil? # In Chef 12, value(nil) does a *get* instead of a set, so we # warn if the value would have been changed. In Chef 13, it will be # equivalent to value = nil. result = get(resource) - if !result.nil? - Chef.log_deprecation("#{name} nil currently does not overwrite the value of #{name}. This will change in Chef 13, and the value will be set to nil instead. Please change your code to explicitly accept nil using \"property :#{name}, [MyType, nil]\", or stop setting this value to nil.") + + # Warn about this becoming a set in Chef 13. + begin + input_to_stored_value(resource, value) + # If nil is valid, and it would change the value, warn that this will change to a set. + if !result.nil? + Chef.log_deprecation("An attempt was made to change #{name} from #{result.inspect} to nil by calling #{name}(nil). In Chef 12, this does a get rather than a set. In Chef 13, this will change to set the value to nil.") + end + rescue Chef::Exceptions::DeprecatedFeatureError + raise + rescue + # If nil is invalid, warn that this will become an error. + Chef.log_deprecation("nil is an invalid value for #{self}. In Chef 13, this warning will change to an error. Error: #{$!}") end + result else # Anything else, such as myprop(value) is a set @@ -291,16 +313,14 @@ class Chef # this property, or if the value is required and not set. # def get(resource) + # If it's set, return it (and evaluate any lazy values) if is_set?(resource) value = get_value(resource) - if value.is_a?(DelayedEvaluator) - value = exec_in_resource(resource, value) - value = coerce(resource, value) - validate(resource, value) - end - value + value = stored_value_to_output(resource, value) else + # We are getting the default value. + # If the user does something like this: # # ``` @@ -326,14 +346,14 @@ class Chef end if has_default? - value = default - if value.is_a?(DelayedEvaluator) - value = exec_in_resource(resource, value) + # If we were able to cache the stored_default, grab it. + if defined?(@stored_default) + value = @stored_default + else + # Otherwise, we have to validate it now. + value = input_to_stored_value(resource, default, is_default: true) end - - value = coerce(resource, value) - - # We don't validate defaults + value = stored_value_to_output(resource, value, is_default: true) # If the value is mutable (non-frozen), we set it on the instance # so that people can mutate it. (All constant default values are @@ -366,11 +386,7 @@ class Chef # this property. # def set(resource, value) - unless value.is_a?(DelayedEvaluator) - value = coerce(resource, value) - validate(resource, value) - end - set_value(resource, value) + set_value(resource, input_to_stored_value(resource, value)) end # @@ -423,7 +439,10 @@ class Chef # def coerce(resource, value) if options.has_key?(:coerce) - value = exec_in_resource(resource, options[:coerce], value) + # If we have no default value, `nil` is never coerced or validated + unless !has_default? && value.nil? + value = exec_in_resource(resource, options[:coerce], value) + end end value end @@ -442,7 +461,10 @@ class Chef # this property. # def validate(resource, value) - resource.validate({ name => value }, { name => validation_options }) + # If we have no default value, `nil` is never coerced or validated + unless value.nil? && !has_default? + (resource || Chef::Mixin::ParamsValidate).validate({ name => value }, { name => validation_options }) + end end # @@ -595,14 +617,53 @@ class Chef value = resource.instance_exec(*args, &proc) end else - value = proc.call + # If we don't have a resource yet, we can't exec in resource! + raise Chef::Exceptions::CannotValidateStaticallyError, "Cannot validate or coerce without a resource" + end + end + + def input_to_stored_value(resource, value, is_default: false) + unless value.is_a?(DelayedEvaluator) + value = coerce_and_validate(resource, value, is_default: is_default) end + value + end + def stored_value_to_output(resource, value, is_default: false) + # Crack open lazy values before giving the result to the user if value.is_a?(DelayedEvaluator) - value = coerce(resource, value) - validate(resource, value) + value = exec_in_resource(resource, value) + value = coerce_and_validate(resource, value, is_default: is_default) end value end + + # Coerces and validates the value. If the value is a default, it will warn + # the user that invalid defaults are bad mmkay, and return it as if it were + # valid. + def coerce_and_validate(resource, value, is_default: false) + result = coerce(resource, value) + begin + # If the input is from a default, we need to emit an invalid default warning on validate. + validate(resource, result) + rescue Chef::Exceptions::CannotValidateStaticallyError + # This one gets re-raised + raise + rescue + # Anything else is just an invalid default: in those cases, we just + # warn and return the (possibly coerced) value to the user. + if is_default + if value.nil? + Chef.log_deprecation("Default value nil is invalid for property #{self}. Possible fixes: 1. Remove 'default: nil' if nil means 'undefined'. 2. Set a valid default value if there is a reasonable one. 3. Allow nil as a valid value of your property (for example, 'property #{name.inspect}, [ String, nil ], default: nil'). Error: #{$!}") + else + Chef.log_deprecation("Default value #{value.inspect} is invalid for property #{self}. In Chef 13 this will become an error: #{$!}.") + end + else + raise + end + end + + result + end end end diff --git a/lib/chef/resource/chef_gem.rb b/lib/chef/resource/chef_gem.rb index 233ae66026..655534684c 100644 --- a/lib/chef/resource/chef_gem.rb +++ b/lib/chef/resource/chef_gem.rb @@ -26,9 +26,9 @@ class Chef property :gem_binary, default: "#{RbConfig::CONFIG['bindir']}/gem", callbacks: { - "The chef_gem resource is restricted to the current gem environment, use gem_package to install to other environments." => proc { false } + "The chef_gem resource is restricted to the current gem environment, use gem_package to install to other environments." => proc { |v| v == properties[:gem_binary].default } } - property :compile_time, [ true, false ], default: lazy { Chef::Config[:chef_gem_compile_time] }, desired_state: false + property :compile_time, [ true, false, nil ], default: lazy { Chef::Config[:chef_gem_compile_time] }, desired_state: false def after_created # Chef::Resource.run_action: Caveat: this skips Chef::Runner.run_action, where notifications are handled diff --git a/lib/chef/resource/file.rb b/lib/chef/resource/file.rb index 0a530e27c9..52cb409226 100644 --- a/lib/chef/resource/file.rb +++ b/lib/chef/resource/file.rb @@ -49,7 +49,7 @@ class Chef allowed_actions :create, :delete, :touch, :create_if_missing property :path, String, name_property: true, identity: true - property :atomic_update, [ true, false ], desired_state: false, default: Chef::Config[:file_atomic_update] + property :atomic_update, [ true, false ], desired_state: false, default: lazy { Chef::Config[:file_atomic_update] } property :backup, [ Integer, false ], desired_state: false, default: 5 property :checksum, [ /^[a-zA-Z0-9]{64}$/, nil ] property :content, [ String, nil ], desired_state: false diff --git a/spec/integration/client/client_spec.rb b/spec/integration/client/client_spec.rb index d08935d854..24ce69ac03 100644 --- a/spec/integration/client/client_spec.rb +++ b/spec/integration/client/client_spec.rb @@ -430,7 +430,7 @@ EOM expect(run_complete).to be >= 0 # Make sure there is exactly one result for each, and that it occurs *after* the complete message. - expect(match_indices(/nil currently does not overwrite the value of/, result.stdout)).to match([ be > run_complete ]) + expect(match_indices(/An attempt was made to change x from \[\] to nil by calling x\(nil\). In Chef 12, this does a get rather than a set. In Chef 13, this will change to set the value to nil./, result.stdout)).to match([ be > run_complete ]) end end diff --git a/spec/unit/property/validation_spec.rb b/spec/unit/property/validation_spec.rb index 7e84a651e2..5a40cce145 100644 --- a/spec/unit/property/validation_spec.rb +++ b/spec/unit/property/validation_spec.rb @@ -72,7 +72,7 @@ describe "Chef::Resource.property validation" do end end - def self.validation_test(validation, success_values, failure_values, getter_values=[], *tags) + def self.validation_test(validation, success_values, failure_values, *tags) with_property ":x, #{validation}", *tags do it "gets nil when retrieving the initial (non-set) value" do expect(resource.x).to be_nil @@ -91,11 +91,52 @@ describe "Chef::Resource.property validation" do expect { resource.x v }.to raise_error Chef::Exceptions::ValidationFailed end end - getter_values.each do |v| - it "setting value to #{v.inspect} does not change the value" do + it "setting x to nil when it is already nil does not emit a warning" do + expect(resource.x nil).to be_nil + expect(resource.x).to be_nil + end + it "changing x to nil warns that the get will change to a set in Chef 13 and does not change the value" do + resource.instance_eval { @x = "default" } + expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError, + /An attempt was made to change x from "default" to nil by calling x\(nil\). In Chef 12, this does a get rather than a set. In Chef 13, this will change to set the value to nil./ + Chef::Config[:treat_deprecation_warnings_as_errors] = false + expect(resource.x nil).to eq "default" + expect(resource.x).to eq "default" + end + end + if tags.include?(:nil_is_valid) + with_property ":x, #{validation}, default: nil" do + it "setting x to nil when it is already nil does not emit a warning" do + expect(resource.x nil).to be_nil + expect(resource.x).to be_nil + end + it "changing x to nil warns that the get will change to a set in Chef 13 and does not change the value" do + resource.instance_eval { @x = "default" } + expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError, + /An attempt was made to change x from "default" to nil by calling x\(nil\). In Chef 12, this does a get rather than a set. In Chef 13, this will change to set the value to nil./ Chef::Config[:treat_deprecation_warnings_as_errors] = false + expect(resource.x nil).to eq "default" + expect(resource.x).to eq "default" + end + end + else + it "property :x, #{validation}, default: nil warns that the default is invalid" do + expect { resource_class.class_eval("property :x, #{validation}, default: nil", __FILE__, __LINE__) }.to raise_error Chef::Exceptions::DeprecatedFeatureError, + /Default value nil is invalid for property x of resource chef_resource_property_spec_(\d+). Possible fixes: 1. Remove 'default: nil' if nil means 'undefined'. 2. Set a valid default value if there is a reasonable one. 3. Allow nil as a valid value of your property \(for example, 'property :x, \[ String, nil \], default: nil'\)./ + end + context "With property :x, #{validation}, default: nil" do + before do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + resource_class.class_eval("property :x, #{validation}, default: nil", __FILE__, __LINE__) + Chef::Config[:treat_deprecation_warnings_as_errors] = true + end + + it "changing x to nil emits a warning that the value is invalid and does not change the value" do resource.instance_eval { @x = "default" } - expect(resource.x v).to eq "default" + expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError, + /nil is an invalid value for x of resource chef_resource_property_spec_(\d+). In Chef 13, this warning will change to an error./ + Chef::Config[:treat_deprecation_warnings_as_errors] = false + expect(resource.x nil).to eq "default" expect(resource.x).to eq "default" end end @@ -150,9 +191,12 @@ describe "Chef::Resource.property validation" do it "get succeeds" do expect(resource.x).to eq "default" end - it "set(nil) sets the value" do - expect(resource.x nil).to be_nil - expect(resource.x).to be_nil + it "set(nil) emits a warning that the value will be set, but does not set the value" do + expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError, + /An attempt was made to change x from "default" to nil by calling x\(nil\). In Chef 12, this does a get rather than a set. In Chef 13, this will change to set the value to nil./ + Chef::Config[:treat_deprecation_warnings_as_errors] = false + expect(resource.x nil).to eq "default" + expect(resource.x).to eq "default" end it "set to valid value succeeds" do expect(resource.x "str").to eq "str" @@ -185,46 +229,41 @@ describe "Chef::Resource.property validation" do context "bare types" do validation_test "String", [ "hi" ], - [ 10 ], - [ nil ] + [ 10 ] validation_test ":a", [ :a ], - [ :b ], - [ nil ] + [ :b ] validation_test ":a, is: :b", [ :a, :b ], - [ :c ], - [ nil ] + [ :c ] validation_test ":a, is: [ :b, :c ]", [ :a, :b, :c ], - [ :d ], - [ nil ] + [ :d ] validation_test "[ :a, :b ], is: :c", [ :a, :b, :c ], - [ :d ], - [ nil ] + [ :d ] validation_test "[ :a, :b ], is: [ :c, :d ]", [ :a, :b, :c, :d ], - [ :e ], - [ nil ] + [ :e ] validation_test "nil", - [ nil ], - [ :a ] + [ ], + [ :a ], + :nil_is_valid validation_test "[ nil ]", - [ nil ], - [ :a ] + [ ], + [ :a ], + :nil_is_valid validation_test "[]", [], - [ :a ], - [ nil ] + [ :a ] end # is @@ -232,35 +271,30 @@ describe "Chef::Resource.property validation" do # Class validation_test "is: String", [ "a", "" ], - [ :a, 1 ], - [ nil ] + [ :a, 1 ] # Value validation_test "is: :a", [ :a ], - [ :b ], - [ nil ] + [ :b ] validation_test "is: [ :a, :b ]", [ :a, :b ], - [ [ :a, :b ] ], - [ nil ] + [ [ :a, :b ] ] validation_test "is: [ [ :a, :b ] ]", [ [ :a, :b ] ], - [ :a, :b ], - [ nil ] + [ :a, :b ] # Regex validation_test "is: /abc/", [ "abc", "wowabcwow" ], - [ "", "abac" ], - [ nil ] + [ "", "abac" ] # Property validation_test "is: Chef::Property.new(is: :a)", [ :a ], - [ :b, nil ] + [ :b ] # RSpec Matcher class Globalses @@ -269,31 +303,30 @@ describe "Chef::Resource.property validation" do validation_test "is: Globalses.eq(10)", [ 10 ], - [ 1 ], - [ nil ] + [ 1 ] # Proc validation_test "is: proc { |x| x }", [ true, 1 ], - [ false ], - [ nil ] + [ false ] validation_test "is: proc { |x| x > blah }", [ 10 ], [ -1 ] validation_test "is: nil", - [ nil ], - [ "a" ] + [ ], + [ "a" ], + :nil_is_valid validation_test "is: [ String, nil ]", - [ "a", nil ], - [ :b ] + [ "a" ], + [ :b ], + :nil_is_valid validation_test "is: []", [], - [ :a ], - [ nil ] + [ :a ] end # Combination @@ -301,7 +334,7 @@ describe "Chef::Resource.property validation" do validation_test 'kind_of: String, equal_to: "a"', [ "a" ], [ "b" ], - [ nil ] + :nil_is_valid end # equal_to @@ -310,37 +343,38 @@ describe "Chef::Resource.property validation" do validation_test "equal_to: :a", [ :a ], [ :b ], - [ nil ] + :nil_is_valid validation_test "equal_to: [ :a, :b ]", [ :a, :b ], [ [ :a, :b ] ], - [ nil ] + :nil_is_valid validation_test "equal_to: [ [ :a, :b ] ]", [ [ :a, :b ] ], [ :a, :b ], - [ nil ] + :nil_is_valid validation_test "equal_to: nil", [ ], [ "a" ], - [ nil ] + :nil_is_valid validation_test 'equal_to: [ "a", nil ]', [ "a" ], [ "b" ], - [ nil ] + :nil_is_valid validation_test 'equal_to: [ nil, "a" ]', [ "a" ], [ "b" ], - [ nil ] + :nil_is_valid validation_test "equal_to: []", [], [ :a ], - [ nil ] + :nil_is_valid + end # kind_of @@ -348,37 +382,37 @@ describe "Chef::Resource.property validation" do validation_test "kind_of: String", [ "a" ], [ :b ], - [ nil ] + :nil_is_valid validation_test "kind_of: [ String, Symbol ]", [ "a", :b ], [ 1 ], - [ nil ] + :nil_is_valid validation_test "kind_of: [ Symbol, String ]", [ "a", :b ], [ 1 ], - [ nil ] + :nil_is_valid validation_test "kind_of: NilClass", [ ], [ "a" ], - [ nil ] + :nil_is_valid validation_test "kind_of: [ NilClass, String ]", [ "a" ], [ :a ], - [ nil ] + :nil_is_valid validation_test "kind_of: []", [], [ :a ], - [ nil ] + :nil_is_valid validation_test "kind_of: nil", [], [ :a ], - [ nil ] + :nil_is_valid end # regex @@ -386,53 +420,55 @@ describe "Chef::Resource.property validation" do validation_test "regex: /abc/", [ "xabcy" ], [ "gbh", 123 ], - [ nil ] + :nil_is_valid validation_test "regex: [ /abc/, /z/ ]", [ "xabcy", "aza" ], [ "gbh", 123 ], - [ nil ] + :nil_is_valid validation_test "regex: [ /z/, /abc/ ]", [ "xabcy", "aza" ], [ "gbh", 123 ], - [ nil ] + :nil_is_valid validation_test "regex: [ [ /z/, /abc/ ], [ /n/ ] ]", [ "xabcy", "aza", "ana" ], [ "gbh", 123 ], - [ nil ] + :nil_is_valid validation_test "regex: []", [], [ :a ], - [ nil ] + :nil_is_valid validation_test "regex: nil", [], [ :a ], - [ nil ] + :nil_is_valid end # callbacks context "callbacks" do validation_test 'callbacks: { "a" => proc { |x| x > 10 }, "b" => proc { |x| x%2 == 0 } }', [ 12 ], - [ 11, 4 ] + [ 11, 4 ], + :nil_is_valid validation_test 'callbacks: { "a" => proc { |x| x%2 == 0 }, "b" => proc { |x| x > 10 } }', [ 12 ], - [ 11, 4 ] + [ 11, 4 ], + :nil_is_valid validation_test 'callbacks: { "a" => proc { |x| x.nil? } }', [ ], [ "a" ], - [ nil ] + :nil_is_valid validation_test "callbacks: {}", [ :a ], [], - [ nil ] + :nil_is_valid end # respond_to @@ -440,84 +476,84 @@ describe "Chef::Resource.property validation" do validation_test "respond_to: :split", [ "hi" ], [ 1 ], - [ nil ] + :nil_is_valid validation_test 'respond_to: "split"', [ "hi" ], [ 1 ], - [ nil ] + :nil_is_valid validation_test "respond_to: :to_s", [ :a ], [], - [ nil ] + :nil_is_valid validation_test "respond_to: [ :split, :to_s ]", [ "hi" ], [ 1 ], - [ nil ] + :nil_is_valid validation_test "respond_to: %w(split to_s)", [ "hi" ], [ 1 ], - [ nil ] + :nil_is_valid validation_test "respond_to: [ :to_s, :split ]", [ "hi" ], [ 1 ], - [ nil ] + :nil_is_valid validation_test "respond_to: []", [ :a ], [], - [ nil ] + :nil_is_valid validation_test "respond_to: nil", [ :a ], [], - [ nil ] + :nil_is_valid end context "cannot_be" do validation_test "cannot_be: :empty", [ 1, [1,2], { a: 10 } ], [ [] ], - [ nil ] + :nil_is_valid validation_test 'cannot_be: "empty"', [ 1, [1,2], { a: 10 } ], [ [] ], - [ nil ] + :nil_is_valid validation_test "cannot_be: [ :empty, :nil ]", [ 1, [1,2], { a: 10 } ], [ [] ], - [ nil ] + :nil_is_valid validation_test 'cannot_be: [ "empty", "nil" ]', [ 1, [1,2], { a: 10 } ], [ [] ], - [ nil ] + :nil_is_valid validation_test "cannot_be: [ :nil, :empty ]", [ 1, [1,2], { a: 10 } ], [ [] ], - [ nil ] + :nil_is_valid validation_test "cannot_be: [ :empty, :nil, :blahblah ]", [ 1, [1,2], { a: 10 } ], [ [] ], - [ nil ] + :nil_is_valid validation_test "cannot_be: []", [ :a ], [], - [ nil ] + :nil_is_valid validation_test "cannot_be: nil", [ :a ], [], - [ nil ] + :nil_is_valid end @@ -549,9 +585,8 @@ describe "Chef::Resource.property validation" 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 + it "value nil is not valid (required means 'not nil')" 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" diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index 1fe8abc706..ffdbccf019 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -203,7 +203,7 @@ describe "Chef::Resource.property" do 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.y 10 }.to raise_error NoMethodError expect(resource_class.properties[:y]).to be_nil end end @@ -581,10 +581,17 @@ describe "Chef::Resource.property" do end context "validation of defaults" do - with_property ":x, String, default: 10" do - it "when the resource is created, no error is raised" do - resource + it "When a class is declared with property :x, String, default: 10, a warning is emitted" do + expect { resource_class.class_eval { property :x, String, default: 10 } }.to raise_error Chef::Exceptions::DeprecatedFeatureError, + /Default value 10 is invalid for property x of resource chef_resource_property_spec_(\d+). In Chef 13 this will become an error: Option x must be one of: String! You passed 10./ + end + context "With property :x, String, default: 10" do + before do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + resource_class.class_eval { property :x, String, default: 10 } + Chef::Config[:treat_deprecation_warnings_as_errors] = true end + it "when x is set, no error is raised" do expect(resource.x "hi").to eq "hi" expect(resource.x).to eq "hi" @@ -592,9 +599,6 @@ describe "Chef::Resource.property" do it "when x is retrieved, no validation error is raised" do expect(resource.x).to eq 10 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 @@ -605,40 +609,31 @@ describe "Chef::Resource.property" do expect(resource.x "hi").to eq "hi" expect(resource.x).to eq "hi" end - it "when x is retrieved, no validation error is raised" do - expect(resource.x).to eq 1 + it "when x is retrieved, an invalid default warning is emitted and the value is returned" do + expect { resource.x }.to raise_error Chef::Exceptions::DeprecatedFeatureError, + /Default value 1 is invalid for property x of resource chef_resource_property_spec_(\d+). In Chef 13 this will become an error: Option x must be one of: String! You passed 1./ expect(Namer.current_index).to eq 1 + Chef::Config[:treat_deprecation_warnings_as_errors] = false + expect(resource.x).to eq 2 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.to_s }, is: proc { |v| Namer.next_index; true }" do - it "validation is not run at all on the default value" do + it "coercion and validation is only run the first time" do expect(resource.x).to eq "1" - expect(Namer.current_index).to eq 1 + expect(Namer.current_index).to eq 2 + expect(resource.x).to eq "1" + expect(Namer.current_index).to eq 2 end - # 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 '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 "validation is not run at all on the default value" do + it "coercion and validation is run each time" do expect(resource.x).to eq "1" - expect(Namer.current_index).to eq 1 + expect(Namer.current_index).to eq 2 + expect(resource.x).to eq "3" + expect(Namer.current_index).to eq 4 end - # it "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 end @@ -716,12 +711,12 @@ describe "Chef::Resource.property" do 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 not run" 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 1 + expect(Namer.current_index).to eq 2 expect(resource.x).to eq "101" - expect(Namer.current_index).to eq 1 + expect(Namer.current_index).to eq 2 end end @@ -732,12 +727,12 @@ describe "Chef::Resource.property" do end end with_property ':x, Integer, coerce: proc { |v| "#{v}#{next_index}" }, default: 10' do - it "when x is retrieved, it is coerced and not validated" do - expect(resource.x).to eq "101" + it "when x is retrieved, it is coerced and emits an invalid default warning, but still returns the value" do + expect { resource.x }.to raise_error Chef::Exceptions::DeprecatedFeatureError, + /Default value 10 is invalid for property x of resource chef_resource_property_spec_(\d+). In Chef 13 this will become an error: Option x must be one of: Integer! You passed "101"./ + Chef::Config[:treat_deprecation_warnings_as_errors] = false + expect(resource.x).to eq "102" end - # 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 @@ -745,20 +740,20 @@ describe "Chef::Resource.property" do 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 not validated" do - expect(resource.x).to eq "101" + it "when x is retrieved, it is coerced and emits an invalid default warning; the value is still returned." do + expect { resource.x }.to raise_error Chef::Exceptions::DeprecatedFeatureError, + /Default value 10 is invalid for property x of resource chef_resource_property_spec_(\d+). In Chef 13 this will become an error: Option x must be one of: Integer! You passed "101"./ + Chef::Config[:treat_deprecation_warnings_as_errors] = false + expect(resource.x).to eq "102" end - # 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}" }, default: lazy { 10 }' do - it "coercion is only run the first time x is retrieved, and validation is not run" 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 1 + expect(Namer.current_index).to eq 2 expect(resource.x).to eq "101" - expect(Namer.current_index).to eq 1 + expect(Namer.current_index).to eq 2 end end end @@ -922,7 +917,8 @@ describe "Chef::Resource.property" do expect(Namer.current_index).to eq 1 end it "does not emit a deprecation warning if set to nil" do - expect(resource.x nil).to eq "1" + # 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" diff --git a/spec/unit/provider/package/dpkg_spec.rb b/spec/unit/provider/package/dpkg_spec.rb index e0bf2accde..f4c938ac7a 100644 --- a/spec/unit/provider/package/dpkg_spec.rb +++ b/spec/unit/provider/package/dpkg_spec.rb @@ -80,30 +80,26 @@ Conflicts: wget-ssl expect { provider.run_action(:purge) }.not_to raise_error end - it "should raise an exception if a source is nil when :install" do - new_resource.source nil - allow(::File).to receive(:exist?).with(source).and_return(false) - expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package) - end + context "when source is nil" do + let(:source) { nil } - it "should raise an exception if a source is nil when :upgrade" do - new_resource.source nil - allow(::File).to receive(:exist?).with(source).and_return(false) - expect { provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package) - end + it "should raise an exception if a source is nil when :install" do + expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package) + end - it "should not raise an exception if a source is nil when :remove" do - new_resource.source nil - allow(::File).to receive(:exist?).with(source).and_return(false) - expect(provider).to receive(:action_remove) - expect { provider.run_action(:remove) }.not_to raise_error - end + it "should raise an exception if a source is nil when :upgrade" do + expect { provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package) + end - it "should not raise an exception if a source is nil when :purge" do - new_resource.source nil - allow(::File).to receive(:exist?).with(source).and_return(false) - expect(provider).to receive(:action_purge) - expect { provider.run_action(:purge) }.not_to raise_error + it "should not raise an exception if a source is nil when :remove" do + expect(provider).to receive(:action_remove) + expect { provider.run_action(:remove) }.not_to raise_error + end + + it "should not raise an exception if a source is nil when :purge" do + expect(provider).to receive(:action_purge) + expect { provider.run_action(:purge) }.not_to raise_error + end end end diff --git a/spec/unit/resource_reporter_spec.rb b/spec/unit/resource_reporter_spec.rb index aa162fd161..3dcd51deab 100644 --- a/spec/unit/resource_reporter_spec.rb +++ b/spec/unit/resource_reporter_spec.rb @@ -271,6 +271,7 @@ describe Chef::ResourceReporter do @bad_resource = Chef::Resource::File.new("/tmp/nameless_file.txt") allow(@bad_resource).to receive(:name).and_return(nil) allow(@bad_resource).to receive(:identity).and_return(nil) + allow(@bad_resource).to receive(:path).and_return(nil) @resource_reporter.resource_action_start(@bad_resource, :create) @resource_reporter.resource_current_state_loaded(@bad_resource, :create, @current_resource) @resource_reporter.resource_updated(@bad_resource, :create) @@ -294,6 +295,7 @@ describe Chef::ResourceReporter do @bad_resource = Chef::Resource::File.new("/tmp/filename_as_hash.txt") allow(@bad_resource).to receive(:name).and_return({:foo=>:bar}) allow(@bad_resource).to receive(:identity).and_return({:foo=>:bar}) + allow(@bad_resource).to receive(:path).and_return({:foo=>:bar}) @resource_reporter.resource_action_start(@bad_resource, :create) @resource_reporter.resource_current_state_loaded(@bad_resource, :create, @current_resource) @resource_reporter.resource_updated(@bad_resource, :create) |