diff options
Diffstat (limited to 'spec')
33 files changed, 2911 insertions, 112 deletions
diff --git a/spec/data/run_context/cookbooks/include/recipes/default.rb b/spec/data/run_context/cookbooks/include/recipes/default.rb new file mode 100644 index 0000000000..8d22994252 --- /dev/null +++ b/spec/data/run_context/cookbooks/include/recipes/default.rb @@ -0,0 +1,24 @@ +module ::RanResources + def self.resources + @resources ||= [] + end +end +class RunContextCustomResource < Chef::Resource + action :create do + ruby_block '4' do + block { RanResources.resources << 4 } + end + recipe_eval do + ruby_block '1' do + block { RanResources.resources << 1 } + end + include_recipe 'include::includee' + ruby_block '3' do + block { RanResources.resources << 3 } + end + end + ruby_block '5' do + block { RanResources.resources << 5 } + end + end +end diff --git a/spec/data/run_context/cookbooks/include/recipes/includee.rb b/spec/data/run_context/cookbooks/include/recipes/includee.rb new file mode 100644 index 0000000000..87bb7f114e --- /dev/null +++ b/spec/data/run_context/cookbooks/include/recipes/includee.rb @@ -0,0 +1,3 @@ +ruby_block '2' do + block { RanResources.resources << 2 } +end diff --git a/spec/integration/recipes/lwrp_spec.rb b/spec/integration/recipes/lwrp_spec.rb index e93763fddc..7ecdfc7c3a 100644 --- a/spec/integration/recipes/lwrp_spec.rb +++ b/spec/integration/recipes/lwrp_spec.rb @@ -45,12 +45,8 @@ log_level :warn EOM result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" --no-color -F doc -o 'l-w-r-p::default'", :cwd => chef_dir) - actual = result.stdout.lines.map { |l| l.chomp }.join("\n") - expected = <<EOM - * l_w_r_p_foo[me] action create (up to date) -EOM - expected = expected.lines.map { |l| l.chomp }.join("\n") - expect(actual).to include(expected) + expect(result.stdout).to match(/\* l_w_r_p_foo\[me\] action create \(up to date\)/) + expect(result.stdout).not_to match(/WARN: You are overriding l_w_r_p_foo/) result.error! end end diff --git a/spec/integration/recipes/recipe_dsl_spec.rb b/spec/integration/recipes/recipe_dsl_spec.rb index 3f4bf9fd5f..6bbb9a5c4c 100644 --- a/spec/integration/recipes/recipe_dsl_spec.rb +++ b/spec/integration/recipes/recipe_dsl_spec.rb @@ -15,13 +15,8 @@ describe "Recipe DSL methods" do before(:context) { class BaseThingy < Chef::Resource - def initialize(*args, &block) - super - @allowed_actions = [ :create ] - @action = :create - end - resource_name 'base_thingy' + default_action :create class<<self attr_accessor :created_resource @@ -61,11 +56,7 @@ describe "Recipe DSL methods" do before(:context) { class Chef::Resource::BackcompatThingy < Chef::Resource - def initialize(*args, &block) - super - @allowed_actions = [ :create ] - @action = :create - end + default_action :create end class Chef::Provider::BackcompatThingy < Chef::Provider def load_current_resource @@ -100,7 +91,7 @@ describe "Recipe DSL methods" do recipe = converge { backcompat_thingy 'blah' do; end } - expect(recipe.logged_warnings).to eq '' + expect(recipe.logged_warnings).to match(/Class Chef::Provider::BackcompatThingy does not declare 'resource_name :backcompat_thingy'./) expect(BaseThingy.created_resource).not_to be_nil end end @@ -114,19 +105,17 @@ describe "Recipe DSL methods" do } - it "bar_thingy works" do - recipe = converge { + it "bar_thingy does not work" do + expect_converge { bar_thingy 'blah' do; end - } - expect(recipe.logged_warnings).to eq '' - expect(BaseThingy.created_resource).to eq(RecipeDSLSpecNamespace::Bar::BarThingy) + }.to raise_error(NoMethodError) end end - context "With a resource named NoNameThingy with resource_name nil" do + context "With a resource named Chef::Resource::NoNameThingy with resource_name nil" do before(:context) { - class NoNameThingy < BaseThingy + class Chef::Resource::NoNameThingy < BaseThingy resource_name nil end @@ -134,7 +123,7 @@ describe "Recipe DSL methods" do it "no_name_thingy does not work" do expect_converge { - thingy 'blah' do; end + no_name_thingy 'blah' do; end }.to raise_error(NoMethodError) end end @@ -199,6 +188,7 @@ describe "Recipe DSL methods" do before(:context) { class AnotherNoNameThingy3 < BaseThingy + resource_name :another_no_name_thingy_3 provides :another_no_name_thingy3, os: 'blarghle' end @@ -227,6 +217,7 @@ describe "Recipe DSL methods" do before(:context) { class AnotherNoNameThingy4 < BaseThingy + resource_name :another_no_name_thingy_4 provides :another_no_name_thingy4, os: 'blarghle' provides :another_no_name_thingy4, platform_family: 'foo' end @@ -418,6 +409,7 @@ describe "Recipe DSL methods" do before { eval <<-EOM, nil, __FILE__, __LINE__+1 class #{class_name} < BaseThingy + resource_name #{dsl_method.inspect} end EOM } @@ -426,11 +418,12 @@ describe "Recipe DSL methods" do eval <<-EOM, nil, __FILE__, __LINE__+1 module BlahModule class #{class_name} < BaseThingy + resource_name #{dsl_method.inspect} end end EOM } - it "two_classes_one_dsl resolves to BlahModule::TwoClassesOneDsl (last declared)" do + it "two_classes_one_dsl resolves to BlahModule::TwoClassesOneDsl (alphabetical)" do dsl_method = self.dsl_method recipe = converge { instance_eval("#{dsl_method} 'blah' do; end") @@ -491,6 +484,7 @@ describe "Recipe DSL methods" do eval <<-EOM, nil, __FILE__, __LINE__+1 module BlahModule class BlahModule::#{class_name} < BaseThingy + resource_name #{dsl_method.inspect} provides #{dsl_method.inspect}, os: 'blarghle' end end @@ -572,11 +566,11 @@ describe "Recipe DSL methods" do } - it "thingy3 works in a recipe and yields Foo::Thingy4 (the explicit one)" do + it "thingy3 works in a recipe and yields Thingy3 (the alphabetical one)" do recipe = converge { thingy3 'blah' do; end } - expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy4 + expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3 end it "thingy4 does not work in a recipe" do @@ -586,16 +580,19 @@ describe "Recipe DSL methods" do end it "resource_matching_short_name returns Thingy4" do - expect(Chef::Resource.resource_matching_short_name(:thingy3)).to eq RecipeDSLSpecNamespace::Thingy4 + expect(Chef::Resource.resource_matching_short_name(:thingy3)).to eq RecipeDSLSpecNamespace::Thingy3 end end end - context "when Thingy5 has resource_name :thingy5" do + context "when Thingy5 has resource_name :thingy5 and provides :thingy5reverse, :thingy5_2 and :thingy5_2reverse" do before(:context) { class RecipeDSLSpecNamespace::Thingy5 < BaseThingy resource_name :thingy5 + provides :thingy5reverse + provides :thingy5_2 + provides :thingy5_2reverse end } @@ -611,6 +608,7 @@ describe "Recipe DSL methods" do before(:context) { class RecipeDSLSpecNamespace::Thingy6 < BaseThingy + resource_name :thingy6 provides :thingy5 end @@ -623,23 +621,151 @@ describe "Recipe DSL methods" do expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy6 end - it "thingy5 works in a recipe and yields Foo::Thingy6 (the later one)" do + it "thingy5 works in a recipe and yields Foo::Thingy5 (the alphabetical one)" do recipe = converge { thingy5 'blah' do; end } - expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy6 + expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5 end it "resource_matching_short_name returns Thingy5" do expect(Chef::Resource.resource_matching_short_name(:thingy5)).to eq RecipeDSLSpecNamespace::Thingy5 end + + context "and AThingy5 provides :thingy5reverse" do + before(:context) { + + class RecipeDSLSpecNamespace::AThingy5 < BaseThingy + resource_name :thingy5reverse + end + + } + + it "thingy5reverse works in a recipe and yields AThingy5 (the alphabetical one)" do + recipe = converge { + thingy5reverse 'blah' do; end + } + expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::AThingy5 + end + end + + context "and ZRecipeDSLSpecNamespace::Thingy5 provides :thingy5_2" do + before(:context) { + + module ZRecipeDSLSpecNamespace + class Thingy5 < BaseThingy + resource_name :thingy5_2 + end + end + + } + + it "thingy5_2 works in a recipe and yields the RecipeDSLSpaceNamespace one (the alphabetical one)" do + recipe = converge { + thingy5_2 'blah' do; end + } + expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5 + end + end + + context "and ARecipeDSLSpecNamespace::Thingy5 provides :thingy5_2" do + before(:context) { + + module ARecipeDSLSpecNamespace + class Thingy5 < BaseThingy + resource_name :thingy5_2reverse + end + end + + } + + it "thingy5_2reverse works in a recipe and yields the ARecipeDSLSpaceNamespace one (the alphabetical one)" do + recipe = converge { + thingy5_2reverse 'blah' do; end + } + expect(BaseThingy.created_resource).to eq ARecipeDSLSpecNamespace::Thingy5 + end + end end + + context "when Thingy3 has resource_name :thingy3" do + before(:context) { + + class RecipeDSLSpecNamespace::Thingy3 < BaseThingy + resource_name :thingy3 + end + + } + + it "thingy3 works in a recipe" do + expect_recipe { + thingy3 'blah' do; end + }.to emit_no_warnings_or_errors + expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3 + end + + context "and Thingy4 has resource_name :thingy3" do + before(:context) { + + class RecipeDSLSpecNamespace::Thingy4 < BaseThingy + resource_name :thingy3 + end + + } + + it "thingy3 works in a recipe and yields Thingy3 (the alphabetical one)" do + recipe = converge { + thingy3 'blah' do; end + } + expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3 + end + + it "thingy4 does not work in a recipe" do + expect_converge { + thingy4 'blah' do; end + }.to raise_error(NoMethodError) + end + + it "resource_matching_short_name returns Thingy4" do + expect(Chef::Resource.resource_matching_short_name(:thingy3)).to eq RecipeDSLSpecNamespace::Thingy3 + end + end + + context "and Thingy4 has resource_name :thingy3" do + before(:context) { + + class RecipeDSLSpecNamespace::Thingy4 < BaseThingy + resource_name :thingy3 + end + + } + + it "thingy3 works in a recipe and yields Thingy3 (the alphabetical one)" do + recipe = converge { + thingy3 'blah' do; end + } + expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3 + end + + it "thingy4 does not work in a recipe" do + expect_converge { + thingy4 'blah' do; end + }.to raise_error(NoMethodError) + end + + it "resource_matching_short_name returns Thingy4" do + expect(Chef::Resource.resource_matching_short_name(:thingy3)).to eq RecipeDSLSpecNamespace::Thingy3 + end + end + end + end context "when Thingy7 provides :thingy8" do before(:context) { class RecipeDSLSpecNamespace::Thingy7 < BaseThingy + resource_name :thingy7 provides :thingy8 end @@ -661,11 +787,11 @@ describe "Recipe DSL methods" do expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy7 end - it "thingy8 works in a recipe and yields Thingy8 (the later one)" do + it "thingy8 works in a recipe and yields Thingy7 (alphabetical)" do recipe = converge { thingy8 'blah' do; end } - expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy8 + expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy7 end it "resource_matching_short_name returns Thingy8" do @@ -674,36 +800,36 @@ describe "Recipe DSL methods" do end end - context "when Thingy5 provides :thingy5, :twizzle and :twizzle2" do + context "when Thingy12 provides :thingy12, :twizzle and :twizzle2" do before(:context) { - class RecipeDSLSpecNamespace::Thingy5 < BaseThingy - resource_name :thingy5 + class RecipeDSLSpecNamespace::Thingy12 < BaseThingy + resource_name :thingy12 provides :twizzle provides :twizzle2 end } - it "thingy5 works in a recipe and yields Thingy5" do + it "thingy12 works in a recipe and yields Thingy12" do expect_recipe { - thingy5 'blah' do; end + thingy12 'blah' do; end }.to emit_no_warnings_or_errors - expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5 + expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy12 end - it "twizzle works in a recipe and yields Thingy5" do + it "twizzle works in a recipe and yields Thingy12" do expect_recipe { twizzle 'blah' do; end }.to emit_no_warnings_or_errors - expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5 + expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy12 end - it "twizzle2 works in a recipe and yields Thingy5" do + it "twizzle2 works in a recipe and yields Thingy12" do expect_recipe { twizzle2 'blah' do; end }.to emit_no_warnings_or_errors - expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5 + expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy12 end end @@ -752,6 +878,95 @@ describe "Recipe DSL methods" do }.to raise_error(Chef::Exceptions::NoSuchResourceType) end end + + context "when Thingy9 provides :thingy9" do + before(:context) { + class RecipeDSLSpecNamespace::Thingy9 < BaseThingy + resource_name :thingy9 + end + } + + it "declaring a resource providing the same :thingy9 produces a warning" do + expect(Chef::Log).to receive(:warn).with("You declared a new resource RecipeDSLSpecNamespace::Thingy9AlternateProvider for resource thingy9, but it comes alphabetically after RecipeDSLSpecNamespace::Thingy9 and has the same filters ({}), so it will not be used. Use override: true if you want to use it for thingy9.") + class RecipeDSLSpecNamespace::Thingy9AlternateProvider < BaseThingy + resource_name :thingy9 + end + end + end + + context "when Thingy10 provides :thingy10" do + before(:context) { + class RecipeDSLSpecNamespace::Thingy10 < BaseThingy + resource_name :thingy10 + end + } + + it "declaring a resource providing the same :thingy10 with override: true does not produce a warning" do + expect(Chef::Log).not_to receive(:warn) + class RecipeDSLSpecNamespace::Thingy10AlternateProvider < BaseThingy + provides :thingy10, override: true + end + end + end + + context "when Thingy11 provides :thingy11" do + before(:context) { + class RecipeDSLSpecNamespace::Thingy11 < BaseThingy + resource_name :thingy10 + end + } + + it "declaring a resource providing the same :thingy11 with os: 'linux' does not produce a warning" do + expect(Chef::Log).not_to receive(:warn) + class RecipeDSLSpecNamespace::Thingy11AlternateProvider < BaseThingy + provides :thingy11, os: 'linux' + end + end + end + end + end + + before(:all) { Namer.current_index = 0 } + before { Namer.current_index += 1 } + + context "with an LWRP that declares actions" do + let(:resource_class) { + Class.new(Chef::Resource::LWRPBase) do + provides :"recipe_dsl_spec#{Namer.current_index}" + actions :create + end + } + let(:resource) { + resource_class.new("blah", run_context) + } + it "The actions are part of actions along with :nothing" do + expect(resource_class.actions).to eq [ :nothing, :create ] + end + it "The actions are part of allowed_actions along with :nothing" do + expect(resource.allowed_actions).to eq [ :nothing, :create ] + end + + context "and a subclass that declares more actions" do + let(:subresource_class) { + Class.new(Chef::Resource::LWRPBase) do + provides :"recipe_dsl_spec_sub#{Namer.current_index}" + actions :delete + end + } + let(:subresource) { + subresource_class.new("subblah", run_context) + } + + it "The parent class actions are not part of actions" do + expect(subresource_class.actions).to eq [ :nothing, :delete ] + end + it "The parent class actions are not part of allowed_actions" do + expect(subresource.allowed_actions).to eq [ :nothing, :delete ] + end + it "The parent class actions do not change" do + expect(resource_class.actions).to eq [ :nothing, :create ] + expect(resource.allowed_actions).to eq [ :nothing, :create ] + end end end end diff --git a/spec/integration/recipes/resource_action_spec.rb b/spec/integration/recipes/resource_action_spec.rb new file mode 100644 index 0000000000..4786294803 --- /dev/null +++ b/spec/integration/recipes/resource_action_spec.rb @@ -0,0 +1,343 @@ +require 'support/shared/integration/integration_helper' + +describe "Resource.action" do + include IntegrationSupport + + def converge(str=nil, file=nil, line=nil, &block) + if block + super(&block) + else + super() do + eval(str, nil, file, line) + end + end + end + + shared_context "ActionJackson" do + it "The default action is the first declared action" do + converge <<-EOM, __FILE__, __LINE__+1 + #{resource_dsl} 'hi' do + foo 'foo!' + end + EOM + expect(ActionJackson.ran_action).to eq :access_recipe_dsl + expect(ActionJackson.succeeded).to eq true + end + + it "The action can access recipe DSL" do + converge <<-EOM, __FILE__, __LINE__+1 + #{resource_dsl} 'hi' do + foo 'foo!' + action :access_recipe_dsl + end + EOM + expect(ActionJackson.ran_action).to eq :access_recipe_dsl + expect(ActionJackson.succeeded).to eq true + end + + it "The action can access attributes" do + converge <<-EOM, __FILE__, __LINE__+1 + #{resource_dsl} 'hi' do + foo 'foo!' + action :access_attribute + end + EOM + expect(ActionJackson.ran_action).to eq :access_attribute + expect(ActionJackson.succeeded).to eq 'foo!' + end + + it "The action can access public methods" do + converge <<-EOM, __FILE__, __LINE__+1 + #{resource_dsl} 'hi' do + foo 'foo!' + action :access_method + end + EOM + expect(ActionJackson.ran_action).to eq :access_method + expect(ActionJackson.succeeded).to eq 'foo_public!' + end + + it "The action can access protected methods" do + converge <<-EOM, __FILE__, __LINE__+1 + #{resource_dsl} 'hi' do + foo 'foo!' + action :access_protected_method + end + EOM + expect(ActionJackson.ran_action).to eq :access_protected_method + expect(ActionJackson.succeeded).to eq 'foo_protected!' + end + + it "The action cannot access private methods" do + expect { + converge(<<-EOM, __FILE__, __LINE__+1) + #{resource_dsl} 'hi' do + foo 'foo!' + action :access_private_method + end + EOM + }.to raise_error(NameError) + expect(ActionJackson.ran_action).to eq :access_private_method + end + + it "The action cannot access resource instance variables" do + converge <<-EOM, __FILE__, __LINE__+1 + #{resource_dsl} 'hi' do + foo 'foo!' + action :access_instance_variable + end + EOM + expect(ActionJackson.ran_action).to eq :access_instance_variable + expect(ActionJackson.succeeded).to be_nil + end + + it "The action does not compile until the prior resource has converged" do + converge <<-EOM, __FILE__, __LINE__+1 + ruby_block 'wow' do + block do + ActionJackson.ruby_block_converged = 'ruby_block_converged!' + end + end + + #{resource_dsl} 'hi' do + foo 'foo!' + action :access_class_method + end + EOM + expect(ActionJackson.ran_action).to eq :access_class_method + expect(ActionJackson.succeeded).to eq 'ruby_block_converged!' + end + + it "The action's resources converge before the next resource converges" do + converge <<-EOM, __FILE__, __LINE__+1 + #{resource_dsl} 'hi' do + foo 'foo!' + action :access_attribute + end + + ruby_block 'wow' do + block do + ActionJackson.ruby_block_converged = ActionJackson.succeeded + end + end + EOM + expect(ActionJackson.ran_action).to eq :access_attribute + expect(ActionJackson.succeeded).to eq 'foo!' + expect(ActionJackson.ruby_block_converged).to eq 'foo!' + end + end + + context "With resource 'action_jackson'" do + before(:context) { + class ActionJackson < Chef::Resource + use_automatic_resource_name + def foo(value=nil) + @foo = value if value + @foo + end + def blarghle(value=nil) + @blarghle = value if value + @blarghle + end + + class <<self + attr_accessor :ran_action + attr_accessor :succeeded + attr_accessor :ruby_block_converged + end + + public + def foo_public + 'foo_public!' + end + protected + def foo_protected + 'foo_protected!' + end + private + def foo_private + 'foo_private!' + end + + public + action :access_recipe_dsl do + ActionJackson.ran_action = :access_recipe_dsl + ruby_block 'hi there' do + block do + ActionJackson.succeeded = true + end + end + end + action :access_attribute do + ActionJackson.ran_action = :access_attribute + ActionJackson.succeeded = foo + ActionJackson.succeeded += " #{blarghle}" if blarghle + ActionJackson.succeeded += " #{bar}" if respond_to?(:bar) + end + action :access_attribute2 do + ActionJackson.ran_action = :access_attribute2 + ActionJackson.succeeded = foo + ActionJackson.succeeded += " #{blarghle}" if blarghle + ActionJackson.succeeded += " #{bar}" if respond_to?(:bar) + end + action :access_method do + ActionJackson.ran_action = :access_method + ActionJackson.succeeded = foo_public + end + action :access_protected_method do + ActionJackson.ran_action = :access_protected_method + ActionJackson.succeeded = foo_protected + end + action :access_private_method do + ActionJackson.ran_action = :access_private_method + ActionJackson.succeeded = foo_private + end + action :access_instance_variable do + ActionJackson.ran_action = :access_instance_variable + ActionJackson.succeeded = @foo + end + action :access_class_method do + ActionJackson.ran_action = :access_class_method + ActionJackson.succeeded = ActionJackson.ruby_block_converged + end + end + } + before(:each) { + ActionJackson.ran_action = :error + ActionJackson.succeeded = :error + ActionJackson.ruby_block_converged = :error + } + + it_behaves_like "ActionJackson" do + let(:resource_dsl) { :action_jackson } + end + + context "And 'action_jackgrandson' inheriting from ActionJackson and changing nothing" do + before(:context) { + class ActionJackgrandson < ActionJackson + use_automatic_resource_name + end + } + + it_behaves_like "ActionJackson" do + let(:resource_dsl) { :action_jackgrandson } + end + end + + context "And 'action_jackalope' inheriting from ActionJackson with an extra attribute and action" do + before(:context) { + class ActionJackalope < ActionJackson + use_automatic_resource_name + + def foo(value=nil) + @foo = "#{value}alope" if value + @foo + end + def bar(value=nil) + @bar = "#{value}alope" if value + @bar + end + class <<self + attr_accessor :jackalope_ran + end + action :access_jackalope do + ActionJackalope.jackalope_ran = :access_jackalope + ActionJackalope.succeeded = "#{foo} #{blarghle} #{bar}" + end + action :access_attribute do + super() + ActionJackalope.jackalope_ran = :access_attribute + ActionJackalope.succeeded = ActionJackson.succeeded + end + end + } + before do + ActionJackalope.jackalope_ran = nil + end + + context "action_jackson still behaves the same" do + it_behaves_like "ActionJackson" do + let(:resource_dsl) { :action_jackson } + end + end + + it "The default action remains the same even though new actions were specified first" do + converge { + action_jackalope 'hi' do + foo 'foo!' + bar 'bar!' + end + } + expect(ActionJackson.ran_action).to eq :access_recipe_dsl + expect(ActionJackson.succeeded).to eq true + end + + it "new actions run, and can access overridden, new, and overridden attributes" do + converge { + action_jackalope 'hi' do + foo 'foo!' + bar 'bar!' + blarghle 'blarghle!' + action :access_jackalope + end + } + expect(ActionJackalope.jackalope_ran).to eq :access_jackalope + expect(ActionJackalope.succeeded).to eq "foo!alope blarghle! bar!alope" + end + + it "overridden actions run, call super, and can access overridden, new, and overridden attributes" do + converge { + action_jackalope 'hi' do + foo 'foo!' + bar 'bar!' + blarghle 'blarghle!' + action :access_attribute + end + } + expect(ActionJackson.ran_action).to eq :access_attribute + expect(ActionJackson.succeeded).to eq "foo!alope blarghle! bar!alope" + expect(ActionJackalope.jackalope_ran).to eq :access_attribute + expect(ActionJackalope.succeeded).to eq "foo!alope blarghle! bar!alope" + end + + it "non-overridden actions run and can access overridden and non-overridden variables (but not necessarily new ones)" do + converge { + action_jackalope 'hi' do + foo 'foo!' + bar 'bar!' + blarghle 'blarghle!' + action :access_attribute2 + end + } + expect(ActionJackson.ran_action).to eq :access_attribute2 + expect(ActionJackson.succeeded).to eq("foo!alope blarghle! bar!alope").or(eq("foo!alope blarghle!")) + end + end + end + + context "With a resource with no actions" do + before(:context) { + class NoActionJackson < Chef::Resource + use_automatic_resource_name + + def foo(value=nil) + @foo = value if value + @foo + end + + class <<self + attr_accessor :action_was + end + end + } + it "The default action is :nothing" do + converge { + no_action_jackson 'hi' do + foo 'foo!' + NoActionJackson.action_was = action + end + } + expect(NoActionJackson.action_was).to eq :nothing + end + end +end diff --git a/spec/support/shared/shared_examples.rb b/spec/support/shared/shared_examples.rb index b20c65f8b6..550fa2eb68 100644 --- a/spec/support/shared/shared_examples.rb +++ b/spec/support/shared/shared_examples.rb @@ -1,7 +1,7 @@ # For storing any examples shared between multiple tests # Any object which defines a .to_json should import this test -shared_examples "to_json equalivent to Chef::JSONCompat.to_json" do +shared_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { raise "You must define the subject when including this test" diff --git a/spec/unit/api_client_spec.rb b/spec/unit/api_client_spec.rb index ba0eca3284..bc4b38848b 100644 --- a/spec/unit/api_client_spec.rb +++ b/spec/unit/api_client_spec.rb @@ -144,7 +144,7 @@ describe Chef::ApiClient do expect(@json).not_to include("private_key") end - include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { @client } end end diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb index 1e4bbb5c56..8146774764 100644 --- a/spec/unit/client_spec.rb +++ b/spec/unit/client_spec.rb @@ -238,23 +238,24 @@ describe Chef::Client do describe "when converge completes successfully" do include_context "a client run" include_context "converge completed" - - describe "when audit phase errors" do - include_context "audit phase failed with error" - include_examples "a completed run with audit failure" do - let(:run_errors) { [audit_error] } + context 'when audit mode is enabled' do + describe "when audit phase errors" do + include_context "audit phase failed with error" + include_examples "a completed run with audit failure" do + let(:run_errors) { [audit_error] } + end end - end - describe "when audit phase completed" do - include_context "audit phase completed" - include_examples "a completed run" - end + describe "when audit phase completed" do + include_context "audit phase completed" + include_examples "a completed run" + end - describe "when audit phase completed with failed controls" do - include_context "audit phase completed with failed controls" - include_examples "a completed run with audit failure" do - let(:run_errors) { [audit_error] } + describe "when audit phase completed with failed controls" do + include_context "audit phase completed with failed controls" + include_examples "a completed run with audit failure" do + let(:run_errors) { [audit_error] } + end end end end @@ -512,11 +513,26 @@ describe Chef::Client do allow_any_instance_of(Chef::RunLock).to receive(:save_pid).and_raise(NoMethodError) end - it "should run exception handlers on early fail" do - expect(subject).to receive(:run_failed) - expect { subject.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error| - expect(error.wrapped_errors.size).to eq 1 - expect(error.wrapped_errors).to include(NoMethodError) + context 'when audit mode is enabled' do + before do + Chef::Config[:audit_mode] = :enabled + end + it "should run exception handlers on early fail" do + expect(subject).to receive(:run_failed) + expect { subject.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error| + expect(error.wrapped_errors.size).to eq 1 + expect(error.wrapped_errors).to include(NoMethodError) + end + end + end + + context 'when audit mode is disabled' do + before do + Chef::Config[:audit_mode] = :disabled + end + it "should run exception handlers on early fail" do + expect(subject).to receive(:run_failed) + expect { subject.run }.to raise_error(NoMethodError) end end end diff --git a/spec/unit/cookbook_version_spec.rb b/spec/unit/cookbook_version_spec.rb index 4990aef004..2bccddcaec 100644 --- a/spec/unit/cookbook_version_spec.rb +++ b/spec/unit/cookbook_version_spec.rb @@ -336,7 +336,7 @@ describe Chef::CookbookVersion do end - include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { Chef::CookbookVersion.new("tatft", '/tmp/blah') } end diff --git a/spec/unit/data_bag_item_spec.rb b/spec/unit/data_bag_item_spec.rb index 4348252388..497817ecf1 100644 --- a/spec/unit/data_bag_item_spec.rb +++ b/spec/unit/data_bag_item_spec.rb @@ -193,7 +193,7 @@ describe Chef::DataBagItem do expect(deserial["snooze"]).to eq({ "finally" => "world_will" }) end - include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { data_bag_item } end end diff --git a/spec/unit/data_bag_spec.rb b/spec/unit/data_bag_spec.rb index bd9a99a1de..13b835d120 100644 --- a/spec/unit/data_bag_spec.rb +++ b/spec/unit/data_bag_spec.rb @@ -73,7 +73,7 @@ describe Chef::DataBag do expect(@deserial.send(t.to_sym)).to eq(@data_bag.send(t.to_sym)) end - include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { @data_bag } end end diff --git a/spec/unit/environment_spec.rb b/spec/unit/environment_spec.rb index ee3b8b21e1..64617e0888 100644 --- a/spec/unit/environment_spec.rb +++ b/spec/unit/environment_spec.rb @@ -208,7 +208,7 @@ describe Chef::Environment do expect(@json).to match(/"chef_type":"environment"/) end - include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { @environment } end end diff --git a/spec/unit/event_dispatch/dsl_spec.rb b/spec/unit/event_dispatch/dsl_spec.rb index e30b1cb40e..f467ea81ea 100644 --- a/spec/unit/event_dispatch/dsl_spec.rb +++ b/spec/unit/event_dispatch/dsl_spec.rb @@ -20,8 +20,29 @@ require 'spec_helper' require 'chef/event_dispatch/dsl' describe Chef::EventDispatch::DSL do + let(:events) do + Chef::EventDispatch::Dispatcher.new + end + + let(:run_context) do + Chef::RunContext.new(Chef::Node.new, nil, events) + end + + before do + Chef.set_run_context(run_context) + end + + after do + Chef.reset! + end + subject{ described_class.new('test') } + it 'set handler name' do + subject.on(:run_started) {} + expect(events.subscribers.first.name).to eq('test') + end + it 'raise error when invalid event type is supplied' do expect do subject.on(:foo_bar) {} @@ -30,12 +51,10 @@ describe Chef::EventDispatch::DSL do it 'register user hooks against valid event type' do subject.on(:run_failed) {'testhook'} - expect(Chef::Config[:event_handlers].first.run_failed).to eq('testhook') + expect(events.subscribers.first.run_failed).to eq('testhook') end it 'preserve state across event hooks' do - Chef::Config.reset! - node = Chef::Node.new calls = [] Chef.event_handler do on :resource_updated do @@ -45,17 +64,13 @@ describe Chef::EventDispatch::DSL do calls << :started end end - events = Chef::EventDispatch::Dispatcher.new(*Chef::Config[:event_handlers]) - rc = Chef::RunContext.new(node, nil, events) - resource = Chef::Resource::RubyBlock.new('foo', rc) + resource = Chef::Resource::RubyBlock.new('foo', run_context) resource.block { } resource.run_action(:run) expect(calls).to eq([:started, :updated]) end it 'preserve instance variables across handler callbacks' do - Chef::Config.reset! - node = Chef::Node.new Chef.event_handler do on :resource_action_start do @ivar = [1] @@ -64,11 +79,9 @@ describe Chef::EventDispatch::DSL do @ivar << 2 end end - events = Chef::EventDispatch::Dispatcher.new(*Chef::Config[:event_handlers]) - rc = Chef::RunContext.new(node, nil, events) - resource = Chef::Resource::RubyBlock.new('foo', rc) + resource = Chef::Resource::RubyBlock.new('foo', run_context) resource.block { } resource.run_action(:run) - expect(Chef::Config[:event_handlers].first.instance_variable_get(:@ivar)).to eq([1, 2]) + expect(events.subscribers.first.instance_variable_get(:@ivar)).to eq([1, 2]) end end diff --git a/spec/unit/exceptions_spec.rb b/spec/unit/exceptions_spec.rb index fd90aeab71..85c54aa693 100644 --- a/spec/unit/exceptions_spec.rb +++ b/spec/unit/exceptions_spec.rb @@ -76,7 +76,7 @@ describe Chef::Exceptions do end if exception.methods.include?(:to_json) - include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { exception } end end diff --git a/spec/unit/json_compat_spec.rb b/spec/unit/json_compat_spec.rb index 7482ba8a28..aaf0c48806 100644 --- a/spec/unit/json_compat_spec.rb +++ b/spec/unit/json_compat_spec.rb @@ -67,7 +67,7 @@ describe Chef::JSONCompat do expect(Chef::JSONCompat.to_json_pretty(f)).to eql("{\n \"foo\": 1234,\n \"bar\": {\n \"baz\": 5678\n }\n}\n") end - include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { Foo.new } end end diff --git a/spec/unit/lwrp_spec.rb b/spec/unit/lwrp_spec.rb index 34c6f6f1c5..784ff966cd 100644 --- a/spec/unit/lwrp_spec.rb +++ b/spec/unit/lwrp_spec.rb @@ -409,13 +409,13 @@ describe "LWRP" do end end - context "when the child does not defined the methods" do + context "when the child does not define the methods" do let(:child) do Class.new(parent) end it "delegates #actions to the parent" do - expect(child.actions).to eq([:eat, :sleep]) + expect(child.actions).to eq([:nothing, :eat, :sleep]) end it "delegates #default_action to the parent" do @@ -432,7 +432,7 @@ describe "LWRP" do end it "does not delegate #actions to the parent" do - expect(child.actions).to eq([:dont_eat, :dont_sleep]) + expect(child.actions).to eq([:nothing, :dont_eat, :dont_sleep]) end it "does not delegate #default_action to the parent" do @@ -457,11 +457,50 @@ describe "LWRP" do it "amends actions when they are already defined" do raise_if_deprecated! - expect(child.actions).to eq([:eat, :sleep, :drink]) + expect(child.actions).to eq([:nothing, :eat, :sleep, :drink]) end end end + describe "when actions is set to an array" do + let(:resource_class) do + Class.new(Chef::Resource::LWRPBase) do + actions [ :eat, :sleep ] + end + end + let(:resource) do + resource_class.new('blah') + end + it "actions includes those actions" do + expect(resource_class.actions).to eq [ :nothing, :eat, :sleep ] + end + it "allowed_actions includes those actions" do + expect(resource_class.allowed_actions).to eq [ :nothing, :eat, :sleep ] + end + it "resource.allowed_actions includes those actions" do + expect(resource.allowed_actions).to eq [ :nothing, :eat, :sleep ] + end + end + + describe "when allowed_actions is set to an array" do + let(:resource_class) do + Class.new(Chef::Resource::LWRPBase) do + allowed_actions [ :eat, :sleep ] + end + end + let(:resource) do + resource_class.new('blah') + end + it "actions includes those actions" do + expect(resource_class.actions).to eq [ :nothing, :eat, :sleep ] + end + it "allowed_actions includes those actions" do + expect(resource_class.allowed_actions).to eq [ :nothing, :eat, :sleep ] + end + it "resource.allowed_actions includes those actions" do + expect(resource.allowed_actions).to eq [ :nothing, :eat, :sleep ] + end + end end describe "Lightweight Chef::Provider" do diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index 5939403ce6..032cb1accb 100644 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -1106,7 +1106,7 @@ describe Chef::Node do expect(serialized_node.run_list).to eq(node.run_list) end - include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { node.from_file(File.expand_path("nodes/test.example.com.rb", CHEF_SPEC_DATA)) node diff --git a/spec/unit/osc_user_spec.rb b/spec/unit/osc_user_spec.rb index 678486a16d..16731b47bd 100644 --- a/spec/unit/osc_user_spec.rb +++ b/spec/unit/osc_user_spec.rb @@ -160,7 +160,7 @@ describe Chef::OscUser do expect(@json).not_to include("password") end - include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { @user } end end diff --git a/spec/unit/property/state_spec.rb b/spec/unit/property/state_spec.rb new file mode 100644 index 0000000000..80ebe01a41 --- /dev/null +++ b/spec/unit/property/state_spec.rb @@ -0,0 +1,491 @@ +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 + 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 ? @blarghle*3 : nil + end + def custom_property=(x) + @blarghle = x*2 + end + 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 + # resource.custom_property = 1 + # expect(resource.state).to eq({ custom_property: 6 }) + # 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 + resource.custom_property = 1 + expect(resource.custom_property).to eq 6 + end + it "custom_property is returned as the identity" do + expect(resource.identity).to be_nil + resource.custom_property = 1 + expect(resource.identity).to eq 6 + 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..e32147bd38 --- /dev/null +++ b/spec/unit/property/validation_spec.rb @@ -0,0 +1,652 @@ +require 'support/shared/integration/integration_helper' + +describe "Chef::Resource.property validation" do + include IntegrationSupport + + module Namer + @i = 0 + def self.next_resource_name + "chef_resource_property_spec_#{@i += 1}" + end + def self.reset_index + @current_index = 0 + end + def self.current_index + @current_index + end + def self.next_index + @current_index += 1 + end + end + + def lazy(&block) + Chef::DelayedEvaluator.new(&block) + end + + before do + Namer.reset_index + end + + def self.new_resource_name + Namer.next_resource_name + end + + let(:resource_class) do + new_resource_name = self.class.new_resource_name + Class.new(Chef::Resource) do + resource_name new_resource_name + def 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, getter_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 + end + success_values.each do |v| + it "value #{v.inspect} is valid" do + resource.instance_eval { @x = 'default' } + expect(resource.x v).to eq v + expect(resource.x).to eq v + end + end + failure_values.each do |v| + it "value #{v.inspect} is invalid" do + expect { resource.x v }.to raise_error Chef::Exceptions::ValidationFailed + resource.instance_eval { @x = 'default' } + 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 + Chef::Config[:treat_deprecation_warnings_as_errors] = false + resource.instance_eval { @x = 'default' } + expect(resource.x v).to eq 'default' + expect(resource.x).to eq 'default' + end + end + end + end + + context "basic get, set, and nil set" do + with_property ":x, kind_of: String" do + context "when the variable already has a value" do + before do + resource.instance_eval { @x = 'default' } + end + it "get succeeds" do + expect(resource.x).to eq 'default' + end + it "set(nil) = get" do + 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' + expect(resource.x).to eq 'str' + end + it "set to invalid value raises ValidationFailed" do + expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed + end + end + context "when the variable does not have an initial value" do + it "get succeeds" do + expect(resource.x).to be_nil + end + it "set(nil) = get" do + expect(resource.x nil).to be_nil + expect(resource.x).to be_nil + end + it "set to valid value succeeds" do + expect(resource.x 'str').to eq 'str' + expect(resource.x).to eq 'str' + end + it "set to invalid value raises ValidationFailed" do + expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed + end + end + end + with_property ":x, [ String, nil ]" do + context "when the variable already has a value" do + before do + resource.instance_eval { @x = 'default' } + end + 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 + end + it "set to valid value succeeds" do + expect(resource.x 'str').to eq 'str' + expect(resource.x).to eq 'str' + end + it "set to invalid value raises ValidationFailed" do + expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed + end + end + context "when the variable does not have an initial value" do + it "get succeeds" do + expect(resource.x).to be_nil + end + it "set(nil) sets the value" do + expect(resource.x nil).to be_nil + expect(resource.x).to be_nil + end + it "set to valid value succeeds" do + expect(resource.x 'str').to eq 'str' + expect(resource.x).to eq 'str' + end + it "set to invalid value raises ValidationFailed" do + expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed + 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 ], + [ nil ] + end + + # is + context "is" do + # Class + validation_test 'is: String', + [ 'a', '' ], + [ :a, 1 ], + [ nil ] + + # 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 ] + + validation_test 'is: []', + [], + [ :a ], + [ nil ] + 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', + [ ], + [ 'a' ], + [ nil ] + + validation_test 'equal_to: [ "a", nil ]', + [ 'a' ], + [ 'b' ], + [ nil ] + + validation_test 'equal_to: [ nil, "a" ]', + [ 'a' ], + [ 'b' ], + [ nil ] + + validation_test 'equal_to: []', + [], + [ :a ], + [ nil ] + 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', + [ ], + [ 'a' ], + [ nil ] + + validation_test 'kind_of: [ NilClass, String ]', + [ 'a' ], + [ :a ], + [ nil ] + + validation_test 'kind_of: []', + [], + [ :a ], + [ nil ] + + validation_test 'kind_of: nil', + [], + [ :a ], + [ nil ] + 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 ] + + validation_test 'regex: []', + [], + [ :a ], + [ nil ] + + validation_test 'regex: nil', + [], + [ :a ], + [ 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? } }', + [ ], + [ 'a' ], + [ nil ] + + validation_test 'callbacks: {}', + [ :a ], + [], + [ nil ] + 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: :to_s', + [ :a ], + [], + [ 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 ] + + validation_test 'respond_to: []', + [ :a ], + [], + [ nil ] + + validation_test 'respond_to: nil', + [ :a ], + [], + [ nil ] + end + + context "cannot_be" do + validation_test 'cannot_be: :empty', + [ 1, [1,2], { a: 10 } ], + [ [] ], + [ nil ] + + validation_test 'cannot_be: "empty"', + [ 1, [1,2], { a: 10 } ], + [ [] ], + [ nil ] + + 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 ] + + validation_test 'cannot_be: []', + [ :a ], + [], + [ nil ] + + validation_test 'cannot_be: nil', + [ :a ], + [], + [ 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 1 is valid" do + expect(resource.x 1).to eq 1 + expect(resource.x).to eq 1 + end + it "value nil does a get" do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + resource.x 1 + resource.x nil + expect(resource.x).to eq 1 + end + end + + with_property ':x, [String, nil], 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 + it "if x is not specified, retrieval fails" do + expect { resource.x }.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 + it "value nil does a get" do + resource.x 1 + resource.x nil + expect(resource.x).to eq 1 + end + end + + with_property ':x, default: 10, required: true' do + it "if x is not specified, retrieval fails" do + expect { resource.x }.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 + it "value nil does a get" do + resource.x 1 + resource.x nil + 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 does a get" do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + resource.x 1 + resource.x nil + expect(resource.x).to eq 1 + 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 does a get" do + resource.x 1 + resource.x nil + expect(resource.x).to eq 1 + 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..ce0552c564 --- /dev/null +++ b/spec/unit/property_spec.rb @@ -0,0 +1,802 @@ +require 'support/shared/integration/integration_helper' + +describe "Chef::Resource.property" do + include IntegrationSupport + + module Namer + @i = 0 + def self.next_resource_name + "chef_resource_property_spec_#{@i += 1}" + end + def self.reset_index + @current_index = 0 + end + def self.current_index + @current_index + end + def self.next_index + @current_index += 1 + end + end + + def lazy(&block) + Chef::DelayedEvaluator.new(&block) + end + + before do + Namer.reset_index + end + + def self.new_resource_name + Namer.next_resource_name + end + + let(:resource_class) do + new_resource_name = self.class.new_resource_name + Class.new(Chef::Resource) do + resource_name new_resource_name + def next_index + Namer.next_index + end + end + end + + let(:resource) do + resource_class.new("blah") + end + + def self.english_join(values) + return '<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 + 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" 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 + before do + subresource_class.class_eval do + property :x, String + end + end + + it "x is still there" do + expect(subresource.x "10").to eq "10" + expect(subresource.x).to eq "10" + expect(subresource.x = "20").to eq "20" + expect(subresource.x).to eq "20" + # expect(subresource_class.properties[:x]).not_to be_nil + # expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x] + end + + it "x's validation is overwritten" do + expect { subresource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed + expect(subresource.x 'ohno').to eq 'ohno' + expect(subresource.x).to eq 'ohno' + end + + it "the superclass's validation for x is still there" do + expect { resource.x 'ohno' }.to raise_error Chef::Exceptions::ValidationFailed + expect(resource.x 10).to eq 10 + expect(resource.x).to eq 10 + end + end + end + end + + context "Chef::Resource::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 + it "when x is set to nil, it returns nil" do + resource.instance_eval { @x = nil } + expect(resource.x).to be_nil + end + + context "With a subclass" do + let(:subresource_class) do + new_resource_name = self.class.new_resource_name + Class.new(resource_class) do + resource_name new_resource_name + end + end + let(:subresource) { subresource_class.new('blah') } + it "The default is inherited" do + expect(subresource.x).to eq 10 + end + end + end + + with_property ':x, default: 10, identity: true' do + it "when x is not set, it is 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 + expect(resource.x.object_id).to eq(resource_class.new('blah2').x.object_id) + end + it "The default value is frozen" do + expect(resource.x).to be_frozen + end + it "The default value cannot be written to" do + expect { resource.x[:a] = 1 }.to raise_error RuntimeError, /frozen/ + end + end + + with_property ':x, default: lazy { {} }' do + it "when x is not set, it returns {}" do + expect(resource.x).to eq({}) + end + # it "The value is 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 + 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, default: lazy { |x| "#{blah}#{x.blah}" }' do + it "x is run in context of the class (where it was defined) and passed the instance" do + expect(resource.x).to eq "classblah1" + end + it "x is passed the value of each instance it is run in" do + expect(resource.x).to eq "classblah1" + expect(resource_class.new('another').x).to eq "classanother2" + # expect(resource.x).to eq "classblah3" + end + end + 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 + 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, 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 + 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, no validation error is raised" do + expect(resource.x).to eq 1 + expect(Namer.current_index).to eq 1 + 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 "validation is not run at all on the default value" do + expect(resource.x).to eq 1 + expect(Namer.current_index).to eq 1 + 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 + + 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, 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 + expect(Namer.current_index).to eq 0 + expect(resource.x).to eq '101' + expect(Namer.current_index).to eq 1 + expect(resource.x).to eq '101' + expect(Namer.current_index).to eq 1 + end + end + + context "validation and coercion of defaults" do + with_property ':x, String, coerce: proc { |v| "#{v}#{next_index}" }, default: 10' do + it "when x is retrieved, it is coerced before validating and passes" do + expect(resource.x).to eq '101' + end + end + with_property ':x, Integer, coerce: proc { |v| "#{v}#{next_index}" }, default: 10' do + it "when x is retrieved, it is coerced and not validated" do + expect(resource.x).to eq '101' + 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 + expect(resource.x).to eq '101' + end + end + with_property ':x, Integer, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do + it "when x is retrieved, it is coerced and not validated" do + expect(resource.x).to eq '101' + 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 + expect(Namer.current_index).to eq 0 + expect(resource.x).to eq '101' + expect(Namer.current_index).to eq 1 + expect(resource.x).to eq '101' + expect(Namer.current_index).to eq 1 + end + end + end + end + end + + context "Chef::Resource#lazy" do + with_property ':x' do + it "setting x to a lazy value does not run it immediately" do + resource.x lazy { Namer.next_index } + expect(Namer.current_index).to eq 0 + end + it "you can set x to a lazy value in the instance" do + resource.instance_eval do + x lazy { Namer.next_index } + end + expect(resource.x).to eq 1 + expect(Namer.current_index).to eq 1 + end + it "retrieving a lazy value pops it open" do + resource.x lazy { Namer.next_index } + expect(resource.x).to eq 1 + expect(Namer.current_index).to eq 1 + end + it "retrieving a lazy value twice evaluates it twice" do + resource.x lazy { Namer.next_index } + expect(resource.x).to eq 1 + expect(resource.x).to eq 2 + expect(Namer.current_index).to eq 2 + end + it "setting the same lazy value on two different instances runs it on each instancee" do + resource2 = resource_class.new("blah2") + l = lazy { Namer.next_index } + resource.x l + resource2.x l + expect(resource2.x).to eq 1 + expect(resource.x).to eq 2 + expect(resource2.x).to eq 3 + end + + context "when the class has a class and instance method named blah" do + before do + resource_class.class_eval do + def self.blah + "class" + end + def blah + "#{name}#{Namer.next_index}" + end + end + end + def blah + "example" + end + # it "retrieving lazy { blah } gets the instance variable" do + # resource.x lazy { blah } + # expect(resource.x).to eq "blah1" + # end + # it "retrieving lazy { blah } from two different instances gets two different instance variables" do + # resource2 = resource_class.new("another") + # l = lazy { blah } + # resource2.x l + # resource.x l + # expect(resource2.x).to eq "another1" + # expect(resource.x).to eq "blah2" + # expect(resource2.x).to eq "another3" + # end + it 'retrieving lazy { |x| "#{blah}#{x.blah}" } gets the example and instance variables' do + resource.x lazy { |x| "#{blah}#{x.blah}" } + expect(resource.x).to eq "exampleblah1" + end + it 'retrieving lazy { |x| "#{blah}#{x.blah}" } from two different instances gets two different instance variables' do + resource2 = resource_class.new("another") + l = lazy { |x| "#{blah}#{x.blah}" } + resource2.x l + resource.x l + expect(resource2.x).to eq "exampleanother1" + expect(resource.x).to eq "exampleblah2" + expect(resource2.x).to eq "exampleanother3" + end + end + end + + with_property ':x, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do + it "lazy values are not coerced on set" do + resource.x lazy { Namer.next_index } + expect(Namer.current_index).to eq 0 + end + it "lazy values are coerced on get" do + resource.x lazy { Namer.next_index } + expect(resource.x).to eq "12" + expect(Namer.current_index).to eq 2 + end + it "lazy values are coerced on each access" do + resource.x lazy { Namer.next_index } + expect(resource.x).to eq "12" + expect(Namer.current_index).to eq 2 + expect(resource.x).to eq "34" + expect(Namer.current_index).to eq 4 + end + end + + with_property ':x, String' do + it "lazy values are not validated on set" do + resource.x lazy { Namer.next_index } + expect(Namer.current_index).to eq 0 + end + it "lazy values are validated on get" do + resource.x lazy { Namer.next_index } + expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed + expect(Namer.current_index).to eq 1 + end + end + + with_property ':x, is: proc { |v| Namer.next_index; true }' do + it "lazy values are validated on each access" do + resource.x lazy { Namer.next_index } + expect(resource.x).to eq 1 + expect(Namer.current_index).to eq 2 + expect(resource.x).to eq 3 + expect(Namer.current_index).to eq 4 + end + end + + with_property ':x, Integer, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do + it "lazy values are not validated or coerced on set" do + resource.x lazy { Namer.next_index } + expect(Namer.current_index).to eq 0 + end + it "lazy values are coerced before being validated, which fails" do + resource.x lazy { Namer.next_index } + expect(Namer.current_index).to eq 0 + expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed + expect(Namer.current_index).to eq 2 + end + end + + with_property ':x, coerce: proc { |v| "#{v}#{Namer.next_index}" }, is: proc { |v| Namer.next_index; true }' do + it "lazy values are coerced and validated exactly once" do + resource.x lazy { Namer.next_index } + expect(resource.x).to eq "12" + expect(Namer.current_index).to eq 3 + expect(resource.x).to eq "45" + expect(Namer.current_index).to eq 6 + end + end + + with_property ':x, String, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do + it "lazy values are coerced before being validated, which succeeds" do + resource.x lazy { Namer.next_index } + expect(resource.x).to eq "12" + expect(Namer.current_index).to eq 2 + end + end + end + + context "Chef::Resource::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 + 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| + 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 + end + end + end + end +end diff --git a/spec/unit/provider_resolver_spec.rb b/spec/unit/provider_resolver_spec.rb index e18d69bc19..cd3d7713a7 100644 --- a/spec/unit/provider_resolver_spec.rb +++ b/spec/unit/provider_resolver_spec.rb @@ -482,7 +482,6 @@ describe Chef::ProviderResolver do python: Chef::Provider::Script, remote_directory: Chef::Provider::RemoteDirectory, route: Chef::Provider::Route, - rpm_package: Chef::Provider::Package::Rpm, ruby: Chef::Provider::Script, ruby_block: Chef::Provider::RubyBlock, script: Chef::Provider::Script, diff --git a/spec/unit/provider_spec.rb b/spec/unit/provider_spec.rb index d7a34bc21b..97b88b1732 100644 --- a/spec/unit/provider_spec.rb +++ b/spec/unit/provider_spec.rb @@ -114,9 +114,7 @@ describe Chef::Provider do end it "does not re-load recipes when creating the temporary run context" do - # we actually want to test that RunContext#load is never called, but we - # can't stub all instances of an object with rspec's mocks. :/ - allow(Chef::RunContext).to receive(:new).and_raise("not supposed to happen") + expect_any_instance_of(Chef::RunContext).not_to receive(:load) snitch = Proc.new {temporary_collection = @run_context.resource_collection} @provider.send(:recipe_eval, &snitch) end diff --git a/spec/unit/recipe_spec.rb b/spec/unit/recipe_spec.rb index ee98e63c1f..17ea498fe3 100644 --- a/spec/unit/recipe_spec.rb +++ b/spec/unit/recipe_spec.rb @@ -121,6 +121,7 @@ describe Chef::Recipe do it "locate resource for particular platform" do ShaunTheSheep = Class.new(Chef::Resource) + ShaunTheSheep.resource_name :shaun_the_sheep ShaunTheSheep.provides :laughter, :platform => ["television"] node.automatic[:platform] = "television" node.automatic[:platform_version] = "123" @@ -131,6 +132,7 @@ describe Chef::Recipe do it "locate a resource for all platforms" do YourMom = Class.new(Chef::Resource) + YourMom.resource_name :your_mom YourMom.provides :love_and_caring res = recipe.love_and_caring "mommy" expect(res.name).to eql("mommy") @@ -141,7 +143,9 @@ describe Chef::Recipe do before do node.automatic[:platform] = "nbc_sports" Sounders = Class.new(Chef::Resource) + Sounders.resource_name :sounders TottenhamHotspur = Class.new(Chef::Resource) + TottenhamHotspur.resource_name :tottenham_hotspur end after do @@ -149,24 +153,24 @@ describe Chef::Recipe do Object.send(:remove_const, :TottenhamHotspur) end - it "selects one if it is the last declared" do - expect(Chef::Log).not_to receive(:warn) + it "selects the first one alphabetically" do + expect(Chef::Log).to receive(:warn).with("You declared a new resource TottenhamHotspur for resource football, but it comes alphabetically after Sounders and has the same filters ({:platform=>\"nbc_sports\"}), so it will not be used. Use override: true if you want to use it for football.") Sounders.provides :football, platform: "nbc_sports" TottenhamHotspur.provides :football, platform: "nbc_sports" res1 = recipe.football "club world cup" expect(res1.name).to eql("club world cup") - expect(res1).to be_a_kind_of(TottenhamHotspur) + expect(res1).to be_a_kind_of(Sounders) end - it "selects the other one if it is given priority" do - expect(Chef::Log).not_to receive(:warn) + it "selects the first one alphabetically even if the declaration order is reversed" do + expect(Chef::Log).to receive(:warn).with("You are overriding football2 on {:platform=>\"nbc_sports\"} with Sounders: used to be TottenhamHotspur. Use override: true if this is what you intended.") - TottenhamHotspur.provides :football, platform: "nbc_sports" - Sounders.provides :football, platform: "nbc_sports" + TottenhamHotspur.provides :football2, platform: "nbc_sports" + Sounders.provides :football2, platform: "nbc_sports" - res1 = recipe.football "club world cup" + res1 = recipe.football2 "club world cup" expect(res1.name).to eql("club world cup") expect(res1).to be_a_kind_of(Sounders) end diff --git a/spec/unit/resource_collection_spec.rb b/spec/unit/resource_collection_spec.rb index b43b012dfc..d52e7e2c26 100644 --- a/spec/unit/resource_collection_spec.rb +++ b/spec/unit/resource_collection_spec.rb @@ -252,7 +252,7 @@ describe Chef::ResourceCollection do expect(json).to match(/instance_vars/) end - include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { rc } end end diff --git a/spec/unit/resource_resolver_spec.rb b/spec/unit/resource_resolver_spec.rb new file mode 100644 index 0000000000..09ff026575 --- /dev/null +++ b/spec/unit/resource_resolver_spec.rb @@ -0,0 +1,49 @@ +# +# Author:: Ranjib Dey +# Copyright:: Copyright (c) 2015 Ranjib Dey <ranjib@linux.com>. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' +require 'chef/resource_resolver' + + +describe Chef::ResourceResolver do + it '#resolve' do + expect(described_class.resolve(:execute)).to eq(Chef::Resource::Execute) + end + + it '#list' do + expect(described_class.list(:package)).to_not be_empty + end + + context 'instance methods' do + let(:resolver) do + described_class.new(Chef::Node.new, 'execute[echo]') + end + + it '#resolve' do + expect(resolver.resolve).to be_nil + end + + it '#list' do + expect(resolver.list).to be_empty + end + + it '#provided_by?' do + expect(resolver.provided_by?(Chef::Resource::Execute)).to be_truthy + end + end +end diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb index 8ba45d9350..8b0baff921 100644 --- a/spec/unit/resource_spec.rb +++ b/spec/unit/resource_spec.rb @@ -344,6 +344,7 @@ describe Chef::Resource do expect(r.resource_name).to be_nil expect(r.declared_type).to eq :d end + it "and there are no provides lines, @resource_name is used" do c = Class.new(Chef::Resource) do def initialize(*args, &block) @@ -358,6 +359,20 @@ describe Chef::Resource do expect(r.resource_name).to eq :blah expect(r.declared_type).to eq :d end + + it "and the resource class gets a late-bound name, resource_name is nil" do + c = Class.new(Chef::Resource) do + def self.name + "ResourceSpecNameTest" + end + end + + r = c.new('hi') + r.declared_type = :d + expect(c.resource_name).to be_nil + expect(r.resource_name).to be_nil + expect(r.declared_type).to eq :d + end end it "resource_name without provides is honored" do @@ -416,7 +431,7 @@ describe Chef::Resource do expect(json).to match(/instance_vars/) end - include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { @resource } end end @@ -795,21 +810,21 @@ describe Chef::Resource do end it 'adds mappings for a single platform' do - expect(Chef).to receive(:set_resource_priority_array).with( + expect(Chef.resource_priority_map).to receive(:set).with( :dinobot, Chef::Resource::Klz, { platform: ['autobots'] } ) klz.provides :dinobot, platform: ['autobots'] end it 'adds mappings for multiple platforms' do - expect(Chef).to receive(:set_resource_priority_array).with( + expect(Chef.resource_priority_map).to receive(:set).with( :energy, Chef::Resource::Klz, { platform: ['autobots', 'decepticons']} ) klz.provides :energy, platform: ['autobots', 'decepticons'] end it 'adds mappings for all platforms' do - expect(Chef).to receive(:set_resource_priority_array).with( + expect(Chef.resource_priority_map).to receive(:set).with( :tape_deck, Chef::Resource::Klz, {} ) klz.provides :tape_deck diff --git a/spec/unit/role_spec.rb b/spec/unit/role_spec.rb index f120ca6da6..ecc7945a08 100644 --- a/spec/unit/role_spec.rb +++ b/spec/unit/role_spec.rb @@ -217,7 +217,7 @@ describe Chef::Role do end - include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { @role } end end diff --git a/spec/unit/run_context/child_run_context_spec.rb b/spec/unit/run_context/child_run_context_spec.rb new file mode 100644 index 0000000000..63586e459c --- /dev/null +++ b/spec/unit/run_context/child_run_context_spec.rb @@ -0,0 +1,133 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Tim Hinderliter (<tim@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) +# Copyright:: Copyright (c) 2008, 2010 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' +require 'support/lib/library_load_order' + +describe Chef::RunContext::ChildRunContext do + context "with a run context with stuff in it" do + let(:chef_repo_path) { File.expand_path(File.join(CHEF_SPEC_DATA, "run_context", "cookbooks")) } + let(:cookbook_collection) { + cl = Chef::CookbookLoader.new(chef_repo_path) + cl.load_cookbooks + Chef::CookbookCollection.new(cl) + } + let(:node) { + node = Chef::Node.new + node.run_list << "test" << "test::one" << "test::two" + node + } + let(:events) { Chef::EventDispatch::Dispatcher.new } + let(:run_context) { Chef::RunContext.new(node, cookbook_collection, events) } + + context "and a child run context" do + let(:child) { run_context.create_child } + + it "parent_run_context is set to the parent" do + expect(child.parent_run_context).to eq run_context + end + + it "audits is not the same as the parent" do + expect(child.audits.object_id).not_to eq run_context.audits.object_id + child.audits['hi'] = 'lo' + expect(child.audits['hi']).to eq('lo') + expect(run_context.audits['hi']).not_to eq('lo') + end + + it "resource_collection is not the same as the parent" do + expect(child.resource_collection.object_id).not_to eq run_context.resource_collection.object_id + f = Chef::Resource::File.new('hi', child) + child.resource_collection.insert(f) + expect(child.resource_collection).to include f + expect(run_context.resource_collection).not_to include f + end + + it "immediate_notification_collection is not the same as the parent" do + expect(child.immediate_notification_collection.object_id).not_to eq run_context.immediate_notification_collection.object_id + src = Chef::Resource::File.new('hi', child) + dest = Chef::Resource::File.new('argh', child) + notification = Chef::Resource::Notification.new(dest, :create, src) + child.notifies_immediately(notification) + expect(child.immediate_notification_collection['file[hi]']).to eq([notification]) + expect(run_context.immediate_notification_collection['file[hi]']).not_to eq([notification]) + end + + it "immediate_notifications is not the same as the parent" do + src = Chef::Resource::File.new('hi', child) + dest = Chef::Resource::File.new('argh', child) + notification = Chef::Resource::Notification.new(dest, :create, src) + child.notifies_immediately(notification) + expect(child.immediate_notifications(src)).to eq([notification]) + expect(run_context.immediate_notifications(src)).not_to eq([notification]) + end + + it "delayed_notification_collection is not the same as the parent" do + expect(child.delayed_notification_collection.object_id).not_to eq run_context.delayed_notification_collection.object_id + src = Chef::Resource::File.new('hi', child) + dest = Chef::Resource::File.new('argh', child) + notification = Chef::Resource::Notification.new(dest, :create, src) + child.notifies_delayed(notification) + expect(child.delayed_notification_collection['file[hi]']).to eq([notification]) + expect(run_context.delayed_notification_collection['file[hi]']).not_to eq([notification]) + end + + it "delayed_notifications is not the same as the parent" do + src = Chef::Resource::File.new('hi', child) + dest = Chef::Resource::File.new('argh', child) + notification = Chef::Resource::Notification.new(dest, :create, src) + child.notifies_delayed(notification) + expect(child.delayed_notifications(src)).to eq([notification]) + expect(run_context.delayed_notifications(src)).not_to eq([notification]) + end + + it "create_child creates a child-of-child" do + c = child.create_child + expect(c.parent_run_context).to eq child + end + + context "after load('include::default')" do + before do + run_list = Chef::RunList.new('include::default').expand('_default') + # TODO not sure why we had to do this to get everything to work ... + node.automatic_attrs[:recipes] = [] + child.load(run_list) + end + + it "load_recipe loads into the child" do + expect(child.resource_collection).to be_empty + child.load_recipe("include::includee") + expect(child.resource_collection).not_to be_empty + end + + it "include_recipe loads into the child" do + expect(child.resource_collection).to be_empty + child.include_recipe("include::includee") + expect(child.resource_collection).not_to be_empty + end + + it "load_recipe_file loads into the child" do + expect(child.resource_collection).to be_empty + child.load_recipe_file(File.expand_path("include/recipes/includee.rb", chef_repo_path)) + expect(child.resource_collection).not_to be_empty + end + end + end + end +end diff --git a/spec/unit/run_context_spec.rb b/spec/unit/run_context_spec.rb index e20ba63b72..99801575ef 100644 --- a/spec/unit/run_context_spec.rb +++ b/spec/unit/run_context_spec.rb @@ -68,6 +68,9 @@ describe Chef::RunContext do "dependency2" => { "version" => "0.0.0", }, + "include" => { + "version" => "0.0.0", + }, "no-default-attr" => { "version" => "0.0.0", }, @@ -84,6 +87,10 @@ describe Chef::RunContext do ) end + it "has a nil parent_run_context" do + expect(run_context.parent_run_context).to be_nil + end + describe "loading cookbooks for a run list" do before do diff --git a/spec/unit/run_list_spec.rb b/spec/unit/run_list_spec.rb index bf996de8c1..e150579431 100644 --- a/spec/unit/run_list_spec.rb +++ b/spec/unit/run_list_spec.rb @@ -307,7 +307,7 @@ describe Chef::RunList do expect(Chef::JSONCompat.to_json(@run_list)).to eq(Chef::JSONCompat.to_json(["recipe[nagios::client]", "role[production]", "recipe[apache2]"])) end - include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { @run_list } end diff --git a/spec/unit/runner_spec.rb b/spec/unit/runner_spec.rb index 82e57e068c..b30f818da1 100644 --- a/spec/unit/runner_spec.rb +++ b/spec/unit/runner_spec.rb @@ -273,8 +273,8 @@ describe Chef::Runner do expected_message =<<-E Multiple failures occurred: -* FailureProvider::ChefClientFail occurred in delayed notification: failure_resource[explode] (dynamically defined) had an error: FailureProvider::ChefClientFail: chef had an error of some sort -* FailureProvider::ChefClientFail occurred in delayed notification: failure_resource[explode again] (dynamically defined) had an error: FailureProvider::ChefClientFail: chef had an error of some sort +* FailureProvider::ChefClientFail occurred in delayed notification: [explode] (dynamically defined) had an error: FailureProvider::ChefClientFail: chef had an error of some sort +* FailureProvider::ChefClientFail occurred in delayed notification: [explode again] (dynamically defined) had an error: FailureProvider::ChefClientFail: chef had an error of some sort E expect(exception.message).to eq(expected_message) diff --git a/spec/unit/user_spec.rb b/spec/unit/user_spec.rb index 57822df7e3..5222c951b3 100644 --- a/spec/unit/user_spec.rb +++ b/spec/unit/user_spec.rb @@ -244,7 +244,7 @@ describe Chef::User do expect(@json).not_to include("password") end - include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { @user } end end |