diff options
Diffstat (limited to 'spec')
29 files changed, 815 insertions, 129 deletions
diff --git a/spec/functional/resource/group_spec.rb b/spec/functional/resource/group_spec.rb index 31f9933546..7effd386a4 100644 --- a/spec/functional/resource/group_spec.rb +++ b/spec/functional/resource/group_spec.rb @@ -425,6 +425,7 @@ downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreeQQQQQQ" end end it "does not raise an error on manage" do + allow(Etc).to receive(:getpwnam).and_return(double("User")) expect { group_resource.run_action(:manage) }.not_to raise_error end end diff --git a/spec/functional/resource/user/useradd_spec.rb b/spec/functional/resource/user/useradd_spec.rb index 79d62436f5..874155c107 100644 --- a/spec/functional/resource/user/useradd_spec.rb +++ b/spec/functional/resource/user/useradd_spec.rb @@ -637,8 +637,10 @@ describe Chef::Provider::User::Useradd, metadata do context "and has no password" do # TODO: platform_family should be setup in spec_helper w/ tags - if %w{suse opensuse}.include?(OHAI_SYSTEM["platform_family"]) - # suse gets this right: + if %w{opensuse}.include?(OHAI_SYSTEM["platform_family"]) || + (%w{suse}.include?(OHAI_SYSTEM["platform_family"]) && + OHAI_SYSTEM["platform_version"].to_f < 12.1) + # suse 11.x gets this right: it "errors out trying to unlock the user" do expect(@error).to be_a(Mixlib::ShellOut::ShellCommandFailed) expect(@error.message).to include("Cannot unlock the password") diff --git a/spec/integration/knife/data_bag_create_spec.rb b/spec/integration/knife/data_bag_create_spec.rb index 0a07792dbc..dc61d55fd5 100644 --- a/spec/integration/knife/data_bag_create_spec.rb +++ b/spec/integration/knife/data_bag_create_spec.rb @@ -34,12 +34,10 @@ describe "knife data bag create", :workstation do end it "creates a new data bag and item" do - pending "Deprecation warning must get fixed" knife("data bag create foo bar").should_succeed stdout: out, stderr: err end it "adds a new item to an existing bag" do - pending "Deprecation warning must get fixed" knife("data bag create foo").should_succeed stderr: err knife("data bag create foo bar").should_succeed stdout: out, stderr: exists end @@ -50,7 +48,6 @@ describe "knife data bag create", :workstation do end it "fails to add an existing item" do - pending "Deprecation warning must get fixed" knife("data bag create foo bar").should_succeed stdout: out, stderr: err expect { knife("data bag create foo bar") }.to raise_error(Net::HTTPServerException) end diff --git a/spec/integration/knife/environment_show_spec.rb b/spec/integration/knife/environment_show_spec.rb index e74bf6d05d..56422dc1a5 100644 --- a/spec/integration/knife/environment_show_spec.rb +++ b/spec/integration/knife/environment_show_spec.rb @@ -26,7 +26,7 @@ describe "knife environment show", :workstation do when_the_chef_server "has some environments" do before do environment "b", { - "default_attributes" => { "foo" => "bar" }, + "default_attributes" => { "foo" => "bar", "baz" => { "raz.my" => "mataz" } }, } end @@ -36,6 +36,8 @@ describe "knife environment show", :workstation do chef_type: environment cookbook_versions: default_attributes: + baz: + raz.my: mataz foo: bar description: json_class: Chef::Environment @@ -46,11 +48,29 @@ EOM # rubocop:enable Style/TrailingWhitespace it "shows the requested attribute of an environment" do - pending "KnifeSupport doesn't appear to pass this through correctly" - knife("environment show b -a foo").should_succeed <<EOM + knife("environment show b -a default_attributes").should_succeed <<EOM b: - foo: bar + default_attributes: + baz: + raz.my: mataz + foo: bar EOM end + + it "shows the requested nested attribute of an environment" do + knife("environment show b -a default_attributes.baz").should_succeed <<EON +b: + default_attributes.baz: + raz.my: mataz +EON + end + + it "shows the requested attribute of an environment with custom field separator" do + knife("environment show b -S: -a default_attributes:baz").should_succeed <<EOT +b: + default_attributes:baz: + raz.my: mataz +EOT + end end end diff --git a/spec/integration/knife/node_environment_set_spec.rb b/spec/integration/knife/node_environment_set_spec.rb index 6dadecf76a..10fec5723f 100644 --- a/spec/integration/knife/node_environment_set_spec.rb +++ b/spec/integration/knife/node_environment_set_spec.rb @@ -31,7 +31,10 @@ describe "knife node environment set", :workstation do it "sets an environment on a node" do knife("node environment set cons lisp").should_succeed /chef_environment:.*lisp/ - knife("node show cons -a chef_environment").should_succeed /Environment:.*lisp/ + knife("node show cons -a chef_environment").should_succeed <<EOM +cons: + chef_environment: lisp +EOM end it "with no environment" do diff --git a/spec/integration/recipes/accumulator_spec.rb b/spec/integration/recipes/accumulator_spec.rb new file mode 100644 index 0000000000..e6afe09b8c --- /dev/null +++ b/spec/integration/recipes/accumulator_spec.rb @@ -0,0 +1,232 @@ +require "support/shared/integration/integration_helper" +require "chef/mixin/shell_out" + +describe "Accumulators" do + include IntegrationSupport + include Chef::Mixin::ShellOut + + let(:chef_dir) { File.expand_path("../../../../bin", __FILE__) } + + # Invoke `chef-client` as `ruby PATH/TO/chef-client`. This ensures the + # following constraints are satisfied: + # * Windows: windows can only run batch scripts as bare executables. Rubygems + # creates batch wrappers for installed gems, but we don't have batch wrappers + # in the source tree. + # * Other `chef-client` in PATH: A common case is running the tests on a + # machine that has omnibus chef installed. In that case we need to ensure + # we're running `chef-client` from the source tree and not the external one. + # cf. CHEF-4914 + let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" } + + let(:aliases_temppath) do + t = Tempfile.new("chef_accumulator_test") + path = t.path + t.close + t.unlink + path + end + + when_the_repository "edit_resource-based delayed accumulators work" do + before do + directory "cookbooks/x" do + file "resources/email_alias.rb", <<-EOM + provides :email_alias + resource_name :email_alias + + property :address, String, name_property: true, identity: true + property :recipients, Array + + default_action :create + + action :create do + with_run_context :root do + edit_resource(:template, "#{aliases_temppath}") do |new_resource| + source "aliases.erb" + variables[:aliases] ||= {} + variables[:aliases][new_resource.address] ||= [] + variables[:aliases][new_resource.address] += new_resource.recipients + action :nothing + delayed_action :create + end + end + end + EOM + + file "resources/nested.rb", <<-EOM + provides :nested + resource_name :nested + + property :address, String, name_property: true, identity: true + property :recipients, Array + + default_action :create + + action :create do + email_alias address do + recipients new_resource.recipients + end + end + EOM + + file "resources/doubly_nested.rb", <<-EOM + provides :doubly_nested + resource_name :doubly_nested + + property :address, String, name_property: true, identity: true + property :recipients, Array + + default_action :create + + action :create do + nested address do + recipients new_resource.recipients + end + end + EOM + + file "recipes/default.rb", <<-EOM + email_alias "outer1" do + recipients [ "out1a", "out1b" ] + end + + nested "nested1" do + recipients [ "nested1a", "nested1b" ] + end + + email_alias "outer2" do + recipients [ "out2a", "out2b" ] + end + + doubly_nested "nested2" do + recipients [ "nested2a", "nested2b" ] + end + + email_alias "outer3" do + recipients [ "out3a", "out3b" ] + end + EOM + + file "templates/aliases.erb", <<-EOM.gsub(/^\s+/, "") + <%= pp @aliases %> + EOM + end # directory 'cookbooks/x' + end + + it "should complete with success" do + file "config/client.rb", <<-EOM + local_mode true + cookbook_path "#{path_to('cookbooks')}" + log_level :warn + EOM + + result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" --no-color -F doc -o 'x::default'", :cwd => chef_dir) + result.error! + # runs only a single template resource (in the outer run context, as a delayed resource) + expect(result.stdout.scan(/template\S+ action create/).size).to eql(1) + # hash order is insertion order in ruby >= 1.9, so this next line does test that all calls were in the correct order + expect(IO.read(aliases_temppath).chomp).to eql('{"outer1"=>["out1a", "out1b"], "nested1"=>["nested1a", "nested1b"], "outer2"=>["out2a", "out2b"], "nested2"=>["nested2a", "nested2b"], "outer3"=>["out3a", "out3b"]}') + end + end + + when_the_repository "find_resource-based delayed accumulators work" do + before do + directory "cookbooks/x" do + file "resources/email_alias.rb", <<-EOM + provides :email_alias + resource_name :email_alias + + property :address, String, name_property: true, identity: true + property :recipients, Array + + default_action :create + + action :create do + r = with_run_context :root do + find_resource(:template, "#{aliases_temppath}") do + source "aliases.erb" + variables[:aliases] = {} + action :nothing + delayed_action :create + end + end + r.variables[:aliases][address] ||= [] + r.variables[:aliases][address] += new_resource.recipients + end + EOM + + file "resources/nested.rb", <<-EOM + provides :nested + resource_name :nested + + property :address, String, name_property: true, identity: true + property :recipients, Array + + default_action :create + + action :create do + email_alias address do + recipients new_resource.recipients + end + end + EOM + + file "resources/doubly_nested.rb", <<-EOM + provides :doubly_nested + resource_name :doubly_nested + + property :address, String, name_property: true, identity: true + property :recipients, Array + + default_action :create + + action :create do + nested address do + recipients new_resource.recipients + end + end + EOM + + file "recipes/default.rb", <<-EOM + email_alias "outer1" do + recipients [ "out1a", "out1b" ] + end + + nested "nested1" do + recipients [ "nested1a", "nested1b" ] + end + + email_alias "outer2" do + recipients [ "out2a", "out2b" ] + end + + doubly_nested "nested2" do + recipients [ "nested2a", "nested2b" ] + end + + email_alias "outer3" do + recipients [ "out3a", "out3b" ] + end + EOM + + file "templates/aliases.erb", <<-EOM.gsub(/^\s+/, "") + <%= pp @aliases %> + EOM + end # directory 'cookbooks/x' + end + + it "should complete with success" do + file "config/client.rb", <<-EOM + local_mode true + cookbook_path "#{path_to('cookbooks')}" + log_level :warn + EOM + + result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" --no-color -F doc -o 'x::default'", :cwd => chef_dir) + result.error! + # runs only a single template resource (in the outer run context, as a delayed resource) + expect(result.stdout.scan(/template\S+ action create/).size).to eql(1) + # hash order is insertion order in ruby >= 1.9, so this next line does test that all calls were in the correct order + expect(IO.read(aliases_temppath).chomp).to eql('{"outer1"=>["out1a", "out1b"], "nested1"=>["nested1a", "nested1b"], "outer2"=>["out2a", "out2b"], "nested2"=>["nested2a", "nested2b"], "outer3"=>["out3a", "out3b"]}') + end + end +end diff --git a/spec/integration/recipes/resource_action_spec.rb b/spec/integration/recipes/resource_action_spec.rb index 60d5831a50..f11696bae4 100644 --- a/spec/integration/recipes/resource_action_spec.rb +++ b/spec/integration/recipes/resource_action_spec.rb @@ -498,7 +498,7 @@ module ResourceActionSpec it "Raises an error when attempting to use a template in the action" do expect_converge do has_property_named_template "hi" - end.to raise_error(/Property template of has_property_named_template\[hi\] cannot be passed a block! If you meant to create a resource named template instead, you'll need to first rename the property./) + end.to raise_error(/Property `template` of `has_property_named_template\[hi\]` was incorrectly passed a block. Possible property-resource collision. To call a resource named `template` either rename the property or else use `declare_resource\(:template, ...\)`/) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 47a5ec7f9f..7559e797bc 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -174,13 +174,13 @@ RSpec.configure do |config| running_platform_arch = `uname -m`.strip unless windows? - config.filter_run_excluding :arch => lambda {|target_arch| + config.filter_run_excluding :arch => lambda { |target_arch| running_platform_arch != target_arch } # Functional Resource tests that are provider-specific: # context "on platforms that use useradd", :provider => {:user => Chef::Provider::User::Useradd}} do #... - config.filter_run_excluding :provider => lambda {|criteria| + config.filter_run_excluding :provider => lambda { |criteria| type, target_provider = criteria.first node = TEST_NODE.dup diff --git a/spec/support/shared/integration/app_server_support.rb b/spec/support/shared/integration/app_server_support.rb index 4dfa3fa155..e2bb3812ea 100644 --- a/spec/support/shared/integration/app_server_support.rb +++ b/spec/support/shared/integration/app_server_support.rb @@ -1,7 +1,7 @@ # # Author:: John Keiser (<jkeiser@chef.io>) # Author:: Ho-Sheng Hsiao (<hosh@chef.io>) -# Copyright:: Copyright 2012-2016, 2013-2015 Chef Software, Inc. +# Copyright:: Copyright 2012-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/spec/support/shared/integration/knife_support.rb b/spec/support/shared/integration/knife_support.rb index 1a374e1b84..4efa30a003 100644 --- a/spec/support/shared/integration/knife_support.rb +++ b/spec/support/shared/integration/knife_support.rb @@ -64,8 +64,11 @@ module KnifeSupport subcommand_class.load_deps instance = subcommand_class.new(args) + # Load configs + instance.merge_configs + # Capture stdout/stderr - instance.ui = Chef::Knife::UI.new(stdout, stderr, stdin, disable_editing: true) + instance.ui = Chef::Knife::UI.new(stdout, stderr, stdin, instance.config.merge(disable_editing: true)) # Don't print stuff Chef::Config[:verbosity] = ( DEBUG ? 2 : 0 ) diff --git a/spec/unit/http_spec.rb b/spec/unit/http_spec.rb index d99f03aac9..d58f07c417 100644 --- a/spec/unit/http_spec.rb +++ b/spec/unit/http_spec.rb @@ -74,7 +74,7 @@ describe Chef::HTTP do expect(http.create_url("///api/endpoint?url=http://foo.bar")).to eql(URI.parse("http://www.getchef.com/organization/org/api/endpoint?url=http://foo.bar")) end - # As per: https://github.com/opscode/chef/issues/2500 + # As per: https://github.com/chef/chef/issues/2500 it "should treat scheme part of the URI in a case-insensitive manner" do http = Chef::HTTP.allocate # Calling Chef::HTTP::new sets @url, don't want that. expect { http.create_url("HTTP://www1.chef.io/") }.not_to raise_error diff --git a/spec/unit/knife/core/gem_glob_loader_spec.rb b/spec/unit/knife/core/gem_glob_loader_spec.rb index 69a40ebaed..2f9e04769e 100644 --- a/spec/unit/knife/core/gem_glob_loader_spec.rb +++ b/spec/unit/knife/core/gem_glob_loader_spec.rb @@ -78,7 +78,7 @@ describe Chef::Knife::SubcommandLoader::GemGlobLoader do expect(loader.site_subcommands).to include(expected_command) end - # https://github.com/opscode/chef-dk/issues/227 + # https://github.com/chef/chef-dk/issues/227 # # `knife` in ChefDK isn't from a gem install, it's directly run from a clone # of the source, but there can be one or more versions of chef also installed diff --git a/spec/unit/knife/core/ui_spec.rb b/spec/unit/knife/core/ui_spec.rb index be77fd8501..38c72161e5 100644 --- a/spec/unit/knife/core/ui_spec.rb +++ b/spec/unit/knife/core/ui_spec.rb @@ -28,6 +28,7 @@ describe Chef::Knife::UI do :verbosity => 0, :yes => nil, :format => "summary", + :field_separator => ".", } @ui = Chef::Knife::UI.new(@out, @err, @in, @config) Chef::Config[:treat_deprecation_warnings_as_errors] = false @@ -410,6 +411,15 @@ EOM @ui.config[:attribute] = non_existing_path expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { non_existing_path => nil } }) end + + describe "when --field-separator is passed" do + it "honors that separator" do + input = { "keys" => { "with spaces" => { "open" => { "doors" => { "with many.dots" => "when asked" } } } } } + @ui.config[:field_separator] = ";" + @ui.config[:attribute] = "keys;with spaces;open;doors;with many.dots" + expect(@ui.format_for_display(input)).to eq({ nil => { "keys;with spaces;open;doors;with many.dots" => "when asked" } }) + end + end end describe "with --run-list passed" do diff --git a/spec/unit/knife_spec.rb b/spec/unit/knife_spec.rb index f0ec45d59a..9569526b2a 100644 --- a/spec/unit/knife_spec.rb +++ b/spec/unit/knife_spec.rb @@ -349,6 +349,37 @@ describe Chef::Knife do expect { knife.run_with_pretty_exceptions }.to raise_error(Exception) end end + + describe "setting arbitrary configuration with --config-option" do + + let(:stdout) { StringIO.new } + + let(:stderr) { StringIO.new } + + let(:stdin) { StringIO.new } + + let(:ui) { Chef::Knife::UI.new(stdout, stderr, stdin, disable_editing: true) } + + let(:subcommand) do + KnifeSpecs::TestYourself.options = Chef::Application::Knife.options.merge(KnifeSpecs::TestYourself.options) + KnifeSpecs::TestYourself.new(%w{--config-option badly_formatted_arg}).tap do |cmd| + cmd.ui = ui + end + end + + it "sets arbitrary configuration via --config-option" do + Chef::Knife.run(%w{test yourself --config-option arbitrary_config_thing=hello}, Chef::Application::Knife.options) + expect(Chef::Config[:arbitrary_config_thing]).to eq("hello") + end + + it "handles errors in arbitrary configuration" do + expect(subcommand).to receive(:exit).with(1) + subcommand.configure_chef + expect(stderr.string).to include("ERROR: Unparsable config option \"badly_formatted_arg\"") + expect(stdout.string).to include(subcommand.opt_parser.to_s) + end + end + end describe "when first created" do diff --git a/spec/unit/mixin/powershell_out_spec.rb b/spec/unit/mixin/powershell_out_spec.rb index 8e5f3588ce..4ecdcb8325 100644 --- a/spec/unit/mixin/powershell_out_spec.rb +++ b/spec/unit/mixin/powershell_out_spec.rb @@ -18,7 +18,7 @@ require "spec_helper" require "chef/mixin/powershell_out" -describe Chef::Mixin::PowershellOut do +describe Chef::Mixin::PowershellOut, :windows_only do let(:shell_out_class) { Class.new { include Chef::Mixin::PowershellOut } } subject(:object) { shell_out_class.new } let(:architecture) { "something" } @@ -44,6 +44,18 @@ describe Chef::Mixin::PowershellOut do ).and_return(ret) expect(object.powershell_out("Get-Process", timeout: 600)).to eql(ret) end + + context "when double quote is passed in the powershell command" do + it "passes if double quote is appended with single escape" do + result = object.powershell_out("Write-Verbose \"Some String\" -Verbose") + expect(result.stderr).to be == "" + expect(result.stdout).to be == "VERBOSE: Some String\n" + end + + it "suppresses error if double quote is passed with double escape characters" do + expect { object.powershell_out("Write-Verbose \\\"Some String\\\" -Verbose") }.not_to raise_error + end + end end describe "#powershell_out!" do @@ -66,5 +78,17 @@ describe Chef::Mixin::PowershellOut do expect(mixlib_shellout).to receive(:error!) expect(object.powershell_out!("Get-Process", timeout: 600)).to eql(mixlib_shellout) end + + context "when double quote is passed in the powershell command" do + it "passes if double quote is appended with single escape" do + result = object.powershell_out!("Write-Verbose \"Some String\" -Verbose") + expect(result.stderr).to be == "" + expect(result.stdout).to be == "VERBOSE: Some String\n" + end + + it "raises error if double quote is passed with double escape characters" do + expect { object.powershell_out!("Write-Verbose \\\"Some String\\\" -Verbose") }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) + end + end end end diff --git a/spec/unit/node/vivid_mash_spec.rb b/spec/unit/node/vivid_mash_spec.rb index 206b15ef6c..017e6206fc 100644 --- a/spec/unit/node/vivid_mash_spec.rb +++ b/spec/unit/node/vivid_mash_spec.rb @@ -19,36 +19,46 @@ require "spec_helper" require "chef/node/attribute_collections" describe Chef::Node::VividMash do - class Root - attr_accessor :top_level_breadcrumb - end - - let(:root) { Root.new } + let(:root) { instance_double(Chef::Node::Attribute) } let(:vivid) do - expect(root).to receive(:reset_cache).at_least(:once).with(nil) - Chef::Node::VividMash.new(root, - { "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil } + Chef::Node::VividMash.new( + { "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }, + root ) end - def with_breadcrumb(key) - expect(root).to receive(:top_level_breadcrumb=).with(nil).at_least(:once).and_call_original - expect(root).to receive(:top_level_breadcrumb=).with(key).at_least(:once).and_call_original + context "without a root node" do + let(:vivid) do + Chef::Node::VividMash.new( + { "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil } + ) + end + + it "sets the root to the root object" do + expect(vivid["one"]["two"].__root__).to eql(vivid) + end + + it "does not send reset cache" do + # if we setup the expectation here then the object winds up responding to :reset_cache and then it fails... + # expect(vivid).not_to receive(:reset_cache) + # but even so we expect to blow up here with NoMethodError if we screw up and send :reset_cache to a root VividMash + vivid["one"]["foo"] = "bar" + end end context "#[]=" do it "deep converts values through arrays" do - allow(root).to receive(:reset_cache) - vivid[:foo] = [ { :bar => true } ] + expect(root).to receive(:reset_cache).with("foo") + vivid["foo"] = [ { :bar => true } ] expect(vivid["foo"].class).to eql(Chef::Node::AttrArray) expect(vivid["foo"][0].class).to eql(Chef::Node::VividMash) expect(vivid["foo"][0]["bar"]).to be true end it "deep converts values through nested arrays" do - allow(root).to receive(:reset_cache) - vivid[:foo] = [ [ { :bar => true } ] ] + expect(root).to receive(:reset_cache).with("foo") + vivid["foo"] = [ [ { :bar => true } ] ] expect(vivid["foo"].class).to eql(Chef::Node::AttrArray) expect(vivid["foo"][0].class).to eql(Chef::Node::AttrArray) expect(vivid["foo"][0][0].class).to eql(Chef::Node::VividMash) @@ -56,8 +66,8 @@ describe Chef::Node::VividMash do end it "deep converts values through hashes" do - allow(root).to receive(:reset_cache) - vivid[:foo] = { baz: { :bar => true } } + expect(root).to receive(:reset_cache).with("foo") + vivid["foo"] = { baz: { :bar => true } } expect(vivid["foo"]).to be_an_instance_of(Chef::Node::VividMash) expect(vivid["foo"]["baz"]).to be_an_instance_of(Chef::Node::VividMash) expect(vivid["foo"]["baz"]["bar"]).to be true @@ -66,182 +76,144 @@ describe Chef::Node::VividMash do context "#read" do before do - # vivify the vividmash, then we're read-only so the cache should never be cleared afterwards - vivid expect(root).not_to receive(:reset_cache) end it "reads hashes deeply" do - with_breadcrumb("one") expect(vivid.read("one", "two", "three")).to eql("four") end it "does not trainwreck when hitting hash keys that do not exist" do - with_breadcrumb("one") expect(vivid.read("one", "five", "six")).to eql(nil) end it "does not trainwreck when hitting an array with an out of bounds index" do - with_breadcrumb("array") expect(vivid.read("array", 5, "one")).to eql(nil) end it "does not trainwreck when hitting an array with a string key" do - with_breadcrumb("array") expect(vivid.read("array", "one", "two")).to eql(nil) end it "does not trainwreck when traversing a nil" do - with_breadcrumb("nil") expect(vivid.read("nil", "one", "two")).to eql(nil) end end context "#exist?" do before do - # vivify the vividmash, then we're read-only so the cache should never be cleared afterwards - vivid expect(root).not_to receive(:reset_cache) end it "true if there's a hash key there" do - with_breadcrumb("one") expect(vivid.exist?("one", "two", "three")).to be true end it "true for intermediate hashes" do - with_breadcrumb("one") expect(vivid.exist?("one")).to be true end it "true for arrays that exist" do - with_breadcrumb("array") expect(vivid.exist?("array", 1)).to be true end it "true when the value of the key is nil" do - with_breadcrumb("nil") expect(vivid.exist?("nil")).to be true end it "false when attributes don't exist" do - with_breadcrumb("one") expect(vivid.exist?("one", "five", "six")).to be false end it "false when traversing a non-container" do - with_breadcrumb("one") expect(vivid.exist?("one", "two", "three", "four")).to be false end it "false when an array index does not exist" do - with_breadcrumb("array") expect(vivid.exist?("array", 3)).to be false end it "false when traversing a nil" do - with_breadcrumb("nil") expect(vivid.exist?("nil", "foo", "bar")).to be false end end context "#read!" do before do - # vivify the vividmash, then we're read-only so the cache should never be cleared afterwards - vivid expect(root).not_to receive(:reset_cache) end it "reads hashes deeply" do - with_breadcrumb("one") expect(vivid.read!("one", "two", "three")).to eql("four") end it "reads arrays deeply" do - with_breadcrumb("array") expect(vivid.read!("array", 1)).to eql(1) end it "throws an exception when attributes do not exist" do - with_breadcrumb("one") expect { vivid.read!("one", "five", "six") }.to raise_error(Chef::Exceptions::NoSuchAttribute) end it "throws an exception when traversing a non-container" do - with_breadcrumb("one") expect { vivid.read!("one", "two", "three", "four") }.to raise_error(Chef::Exceptions::NoSuchAttribute) end it "throws an exception when an array element does not exist" do - with_breadcrumb("array") expect { vivid.read!("array", 3) }.to raise_error(Chef::Exceptions::NoSuchAttribute) end end context "#write" do - before do - vivid - expect(root).not_to receive(:reset_cache).with(nil) - end - it "should write into hashes" do - with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") vivid.write("one", "five", "six") expect(vivid["one"]["five"]).to eql("six") end it "should deeply autovivify" do - with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") vivid.write("one", "five", "six", "seven", "eight", "nine", "ten") expect(vivid["one"]["five"]["six"]["seven"]["eight"]["nine"]).to eql("ten") end it "should raise an exception if you overwrite an array with a hash" do - with_breadcrumb("array") expect(root).to receive(:reset_cache).at_least(:once).with("array") vivid.write("array", "five", "six") expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => { "five" => "six" }, "nil" => nil }) end it "should raise an exception if you traverse through an array with a hash" do - with_breadcrumb("array") expect(root).to receive(:reset_cache).at_least(:once).with("array") vivid.write("array", "five", "six", "seven") expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => { "five" => { "six" => "seven" } }, "nil" => nil }) end it "should raise an exception if you overwrite a string with a hash" do - with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") vivid.write("one", "two", "three", "four", "five") expect(vivid).to eql({ "one" => { "two" => { "three" => { "four" => "five" } } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should raise an exception if you traverse through a string with a hash" do - with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") vivid.write("one", "two", "three", "four", "five", "six") expect(vivid).to eql({ "one" => { "two" => { "three" => { "four" => { "five" => "six" } } } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should raise an exception if you overwrite a nil with a hash" do - with_breadcrumb("nil") expect(root).to receive(:reset_cache).at_least(:once).with("nil") vivid.write("nil", "one", "two") expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => { "one" => "two" } }) end it "should raise an exception if you traverse through a nil with a hash" do - with_breadcrumb("nil") expect(root).to receive(:reset_cache).at_least(:once).with("nil") vivid.write("nil", "one", "two", "three") expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => { "one" => { "two" => "three" } } }) end it "writes with a block" do - with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") vivid.write("one", "five") { "six" } expect(vivid["one"]["five"]).to eql("six") @@ -249,69 +221,55 @@ describe Chef::Node::VividMash do end context "#write!" do - before do - vivid - expect(root).not_to receive(:reset_cache).with(nil) - end - it "should write into hashes" do - with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") vivid.write!("one", "five", "six") expect(vivid["one"]["five"]).to eql("six") end it "should deeply autovivify" do - with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") vivid.write!("one", "five", "six", "seven", "eight", "nine", "ten") expect(vivid["one"]["five"]["six"]["seven"]["eight"]["nine"]).to eql("ten") end it "should raise an exception if you overwrite an array with a hash" do - with_breadcrumb("array") expect(root).not_to receive(:reset_cache) expect { vivid.write!("array", "five", "six") }.to raise_error(Chef::Exceptions::AttributeTypeMismatch) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should raise an exception if you traverse through an array with a hash" do - with_breadcrumb("array") expect(root).not_to receive(:reset_cache) expect { vivid.write!("array", "five", "six", "seven") }.to raise_error(Chef::Exceptions::AttributeTypeMismatch) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should raise an exception if you overwrite a string with a hash" do - with_breadcrumb("one") expect(root).not_to receive(:reset_cache) expect { vivid.write!("one", "two", "three", "four", "five") }.to raise_error(Chef::Exceptions::AttributeTypeMismatch) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should raise an exception if you traverse through a string with a hash" do - with_breadcrumb("one") expect(root).not_to receive(:reset_cache) expect { vivid.write!("one", "two", "three", "four", "five", "six") }.to raise_error(Chef::Exceptions::AttributeTypeMismatch) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should raise an exception if you overwrite a nil with a hash" do - with_breadcrumb("nil") expect(root).not_to receive(:reset_cache) expect { vivid.write!("nil", "one", "two") }.to raise_error(Chef::Exceptions::AttributeTypeMismatch) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should raise an exception if you traverse through a nil with a hash" do - with_breadcrumb("nil") expect(root).not_to receive(:reset_cache) expect { vivid.write!("nil", "one", "two", "three") }.to raise_error(Chef::Exceptions::AttributeTypeMismatch) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "writes with a block" do - with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") vivid.write!("one", "five") { "six" } expect(vivid["one"]["five"]).to eql("six") @@ -319,41 +277,31 @@ describe Chef::Node::VividMash do end context "#unlink" do - before do - vivid - expect(root).not_to receive(:reset_cache).with(nil) - end - it "should return nil if the keys don't already exist" do - expect(root).to receive(:top_level_breadcrumb=).with(nil).at_least(:once).and_call_original expect(root).not_to receive(:reset_cache) expect(vivid.unlink("five", "six", "seven", "eight")).to eql(nil) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should unlink hashes" do - with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") expect( vivid.unlink("one") ).to eql({ "two" => { "three" => "four" } }) expect(vivid).to eql({ "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should unlink array elements" do - with_breadcrumb("array") expect(root).to receive(:reset_cache).at_least(:once).with("array") expect(vivid.unlink("array", 2)).to eql(2) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1 ], "nil" => nil }) end it "should unlink nil" do - with_breadcrumb("nil") expect(root).to receive(:reset_cache).at_least(:once).with("nil") expect(vivid.unlink("nil")).to eql(nil) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ] }) end it "should traverse a nil and safely do nothing" do - with_breadcrumb("nil") expect(root).not_to receive(:reset_cache) expect(vivid.unlink("nil", "foo")).to eql(nil) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) @@ -361,41 +309,31 @@ describe Chef::Node::VividMash do end context "#unlink!" do - before do - vivid - expect(root).not_to receive(:reset_cache).with(nil) - end - it "should raise an exception if the keys don't already exist" do - expect(root).to receive(:top_level_breadcrumb=).with(nil).at_least(:once).and_call_original expect(root).not_to receive(:reset_cache) expect { vivid.unlink!("five", "six", "seven", "eight") }.to raise_error(Chef::Exceptions::NoSuchAttribute) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should unlink! hashes" do - with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") expect( vivid.unlink!("one") ).to eql({ "two" => { "three" => "four" } }) expect(vivid).to eql({ "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should unlink! array elements" do - with_breadcrumb("array") expect(root).to receive(:reset_cache).at_least(:once).with("array") expect(vivid.unlink!("array", 2)).to eql(2) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1 ], "nil" => nil }) end it "should unlink! nil" do - with_breadcrumb("nil") expect(root).to receive(:reset_cache).at_least(:once).with("nil") expect(vivid.unlink!("nil")).to eql(nil) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ] }) end it "should raise an exception if it traverses a nil" do - with_breadcrumb("nil") expect(root).not_to receive(:reset_cache) expect { vivid.unlink!("nil", "foo") }.to raise_error(Chef::Exceptions::NoSuchAttribute) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index 2c8fc4408b..cfc19db480 100644 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -243,6 +243,34 @@ describe Chef::Node do expect { node.sunshine = "is bright" }.to raise_error(Chef::Exceptions::ImmutableAttributeModification) end + it "does not allow modification of node attributes via hash methods" do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + node.default["h4sh"] = { foo: "bar" } + expect { node["h4sh"].delete("foo") }.to raise_error(Chef::Exceptions::ImmutableAttributeModification) + expect { node.h4sh.delete("foo") }.to raise_error(Chef::Exceptions::ImmutableAttributeModification) + end + + it "does not allow modification of node attributes via array methods" do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + node.default["array"] = [] + expect { node["array"] << "boom" }.to raise_error(Chef::Exceptions::ImmutableAttributeModification) + expect { node.array << "boom" }.to raise_error(Chef::Exceptions::ImmutableAttributeModification) + end + + it "returns merged immutable attributes for arrays" do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + node.default["array"] = [] + expect( node["array"].class ).to eql(Chef::Node::ImmutableArray) + expect( node.array.class ).to eql(Chef::Node::ImmutableArray) + end + + it "returns merged immutable attributes for hashes" do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + node.default["h4sh"] = {} + expect( node["h4sh"].class ).to eql(Chef::Node::ImmutableMash) + expect( node.h4sh.class ).to eql(Chef::Node::ImmutableMash) + end + it "should allow you get get an attribute via method_missing" do Chef::Config[:treat_deprecation_warnings_as_errors] = false node.default.sunshine = "is bright" @@ -756,9 +784,9 @@ describe Chef::Node do # In Chef-12.0 there is a deep_merge cache on the top level attribute which had a bug # where it cached node[:foo] separate from node['foo']. These tests exercise those edge conditions. # - # https://github.com/opscode/chef/issues/2700 - # https://github.com/opscode/chef/issues/2712 - # https://github.com/opscode/chef/issues/2745 + # https://github.com/chef/chef/issues/2700 + # https://github.com/chef/chef/issues/2712 + # https://github.com/chef/chef/issues/2745 # describe "deep merge attribute cache edge conditions" do it "does not error with complicated attribute substitution" do @@ -1681,4 +1709,107 @@ describe Chef::Node do end end + describe "path tracking via __path__" do + it "works through hash keys" do + node.default["foo"] = { "bar" => { "baz" => "qux" } } + expect(node["foo"]["bar"].__path__).to eql(%w{foo bar}) + end + + it "works through the default level" do + node.default["foo"] = { "bar" => { "baz" => "qux" } } + expect(node.default["foo"]["bar"].__path__).to eql(%w{foo bar}) + end + + it "works through arrays" do + node.default["foo"] = [ { "bar" => { "baz" => "qux" } } ] + expect(node["foo"][0].__path__).to eql(["foo", 0]) + expect(node["foo"][0]["bar"].__path__).to eql(["foo", 0, "bar"]) + end + + it "works through arrays at the default level" do + node.default["foo"] = [ { "bar" => { "baz" => "qux" } } ] + expect(node.default["foo"][0].__path__).to eql(["foo", 0]) + expect(node.default["foo"][0]["bar"].__path__).to eql(["foo", 0, "bar"]) + end + + # if we set __path__ in the initializer we'd get this wrong, this is why we + # update the path on every #[] or #[]= operator + it "works on access when the node has been rearranged" do + node.default["foo"] = { "bar" => { "baz" => "qux" } } + a = node.default["foo"] + node.default["fizz"] = a + expect(node["fizz"]["bar"].__path__).to eql(%w{fizz bar}) + expect(node["foo"]["bar"].__path__).to eql(%w{foo bar}) + end + + # We have a problem because the __path__ is stored on in each node, but the + # node can be wired up at multiple locations in the tree via pointers. One + # solution would be to deep-dup the value in `#[]=(key, value)` and fix the + # __path__ on all the dup'd nodes. The problem is that this would create an + # unusual situation where after assignment, you couldn't mutate the thing you + # hand a handle on. I'm not entirely positive this behavior is the correct + # thing to support, but it is more hash-like (although if we start with a hash + # then convert_value does its thing and we *do* get dup'd on assignment). This + # behavior likely makes any implementation of a deep merge cache built over the + # top of __path__ tracking have edge conditions where it will fail. + # + # Removing this support would be a breaking change. The test is included here + # because it seems most likely that someone would break this behavior while trying + # to fix __path__ behavior. + it "does not dup in the background when a node is assigned" do + # get a handle on a vividmash (can't be a hash or else we convert_value it) + node.default["foo"] = { "bar" => { "baz" => "qux" } } + a = node.default["foo"] + # assign that somewhere else in the tree + node.default["fizz"] = a + # now upate the source + a["duptest"] = true + # the tree should have been updated + expect(node.default["fizz"]["duptest"]).to be true + expect(node["fizz"]["duptest"]).to be true + end + end + + describe "root tracking via __root__" do + it "works through hash keys" do + node.default["foo"] = { "bar" => { "baz" => "qux" } } + expect(node["foo"]["bar"].__root__).to eql(node.attributes) + end + + it "works through the default level" do + node.default["foo"] = { "bar" => { "baz" => "qux" } } + expect(node.default["foo"]["bar"].__root__).to eql(node.attributes) + end + + it "works through arrays" do + node.default["foo"] = [ { "bar" => { "baz" => "qux" } } ] + expect(node["foo"][0].__root__).to eql(node.attributes) + expect(node["foo"][0]["bar"].__root__).to eql(node.attributes) + end + + it "works through arrays at the default level" do + node.default["foo"] = [ { "bar" => { "baz" => "qux" } } ] + expect(node.default["foo"][0].__root__).to eql(node.attributes) + expect(node.default["foo"][0]["bar"].__root__).to eql(node.attributes) + end + end + + describe "ways of abusing Chef 12 node state" do + # these tests abuse the top_level_breadcrumb state in Chef 12 + it "derived attributes work correctly" do + node.default["v1"] = 1 + expect(node["a"]).to eql(nil) + node.default["a"] = node["v1"] + expect(node["a"]).to eql(1) + end + + it "works when saving nodes to variables" do + a = node.default["a"] + expect(node["a"]).to eql({}) + node.default["b"] = 0 + a["key"] = 1 + + expect(node["a"]["key"]).to eql(1) + end + end end diff --git a/spec/unit/provider/deploy_spec.rb b/spec/unit/provider/deploy_spec.rb index e69714280c..b30ddb736a 100644 --- a/spec/unit/provider/deploy_spec.rb +++ b/spec/unit/provider/deploy_spec.rb @@ -556,7 +556,7 @@ describe Chef::Provider::Deploy do @resource.deploy_to("/my/app") expect(mock_execution).to receive(:user).with("notCoolMan") expect(mock_execution).to receive(:group).with("Ggroup") - expect(mock_execution).to receive(:cwd) {|*args| + expect(mock_execution).to receive(:cwd) { |*args| if args.empty? nil else diff --git a/spec/unit/provider/group/suse_spec.rb b/spec/unit/provider/group/suse_spec.rb new file mode 100644 index 0000000000..da4e8e9155 --- /dev/null +++ b/spec/unit/provider/group/suse_spec.rb @@ -0,0 +1,90 @@ +# +# Author:: Tom Duffield (<tom@chef.io>) +# Copyright:: Copyright 2016 Chef Software, 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" + +describe Chef::Provider::Group::Suse do + let(:node) { Chef::Node.new } + let(:events) { Chef::EventDispatch::Dispatcher.new } + let(:run_context) { Chef::RunContext.new(node, {}, events) } + let(:new_members) { %w{root new_user} } + let(:new_resource) do + Chef::Resource::Group.new("new_group").tap do |r| + r.gid 50 + r.members new_members + r.system false + r.non_unique false + end + end + let(:current_resource) do + Chef::Resource::Group.new("new_group").tap do |r| + r.gid 50 + r.members %w{root} + r.system false + r.non_unique false + end + end + let(:provider) do + described_class.new(new_resource, run_context).tap do |p| + p.current_resource = current_resource + end + end + + describe "when determining the current group state" do + before(:each) do + allow(File).to receive(:exists?).and_return(true) + provider.action = :create + provider.define_resource_requirements + end + + # Checking for required binaries is already done in the spec + # for Chef::Provider::Group - no need to repeat it here. We'll + # include only what's specific to this provider. + it "should raise an error if the required binary /usr/sbin/groupmod doesn't exist" do + expect(File).to receive(:exists?).with("/usr/sbin/groupmod").and_return(false) + expect { provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Group) + end + + it "should raise error if one of the member users does not exist" do + expect(Etc).to receive(:getpwnam).with("new_user").and_raise ArgumentError + expect { provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Group) + end + end + + describe "#set_members" do + it "should add missing members and remove deleted members" do + expect(provider).not_to receive(:remove_member) + expect(provider).to receive(:add_member).with("new_user") + provider.set_members(new_members) + end + end + + describe "#add_member" do + it "should call out to groupmod to add user" do + expect(provider).to receive(:shell_out!).with("groupmod -A new_user new_group") + provider.add_member("new_user") + end + end + + describe "#remove_member" do + it "should call out to groupmod to remove user" do + expect(provider).to receive(:shell_out!).with("groupmod -R new_user new_group") + provider.remove_member("new_user") + end + end +end diff --git a/spec/unit/provider/package/apt_spec.rb b/spec/unit/provider/package/apt_spec.rb index a549ecc72e..cf31c3d7cc 100644 --- a/spec/unit/provider/package/apt_spec.rb +++ b/spec/unit/provider/package/apt_spec.rb @@ -395,6 +395,28 @@ mpg123 1.12.1-0ubuntu1 end end + describe "when locking a package" do + it "should run apt-mark hold package" do + expect(@provider).to receive(:shell_out!).with( + "apt-mark hold irssi", + :env => { "DEBIAN_FRONTEND" => "noninteractive" }, + :timeout => @timeout + ) + @provider.lock_package("irssi", "0.8.12-7") + end + end + + describe "when unlocking a package" do + it "should run apt-mark unhold package" do + expect(@provider).to receive(:shell_out!).with( + "apt-mark unhold irssi", + :env => { "DEBIAN_FRONTEND" => "noninteractive" }, + :timeout => @timeout + ) + @provider.unlock_package("irssi", "0.8.12-7") + end + end + describe "when installing a virtual package" do it "should install the package without specifying a version" do @provider.package_data["libmysqlclient15-dev"][:virtual] = true diff --git a/spec/unit/provider/package/windows/msi_spec.rb b/spec/unit/provider/package/windows/msi_spec.rb index e29508ca7b..c8099c38d0 100644 --- a/spec/unit/provider/package/windows/msi_spec.rb +++ b/spec/unit/provider/package/windows/msi_spec.rb @@ -121,7 +121,7 @@ describe Chef::Provider::Package::Windows::MSI do end it "removes installed package" do - expect(provider).to receive(:shell_out!).with(/MsiExec.exe \/X{guid} \/Q/, kind_of(Hash)) + expect(provider).to receive(:shell_out!).with(/msiexec \/x {guid} \/q/, kind_of(Hash)) provider.remove_package end @@ -140,8 +140,8 @@ describe Chef::Provider::Package::Windows::MSI do end it "removes both installed package" do - expect(provider).to receive(:shell_out!).with(/MsiExec.exe \/X{guid} \/Q/, kind_of(Hash)) - expect(provider).to receive(:shell_out!).with(/MsiExec.exe \/X{guid2} \/Q/, kind_of(Hash)) + expect(provider).to receive(:shell_out!).with(/msiexec \/x {guid} \/q/, kind_of(Hash)) + expect(provider).to receive(:shell_out!).with(/msiexec \/x {guid2} \/q/, kind_of(Hash)) provider.remove_package end end @@ -150,7 +150,16 @@ describe Chef::Provider::Package::Windows::MSI do before { new_resource.options("/Q") } it "does not duplicate quiet switch" do - expect(provider).to receive(:shell_out!).with(/MsiExec.exe \/X{guid} \/Q/, kind_of(Hash)) + expect(provider).to receive(:shell_out!).with(/msiexec \/x {guid} \/Q/, kind_of(Hash)) + provider.remove_package + end + end + + context "custom options includes /qn" do + before { new_resource.options("/qn") } + + it "does not duplicate quiet switch" do + expect(provider).to receive(:shell_out!).with(/msiexec \/x {guid} \/qn/, kind_of(Hash)) provider.remove_package end end diff --git a/spec/unit/provider/package/windows_spec.rb b/spec/unit/provider/package/windows_spec.rb index d1d717bdbe..53cbbc1da1 100644 --- a/spec/unit/provider/package/windows_spec.rb +++ b/spec/unit/provider/package/windows_spec.rb @@ -26,9 +26,9 @@ describe Chef::Provider::Package::Windows, :windows_only do allow(Chef::FileCache).to receive(:create_cache_path).with("package/").and_return(cache_path) end - let(:node) { double("Chef::Node") } - let(:events) { double("Chef::Events").as_null_object } # mock all the methods - let(:run_context) { double("Chef::RunContext", :node => node, :events => events) } + let(:node) { Chef::Node.new } + let(:events) { Chef::EventDispatch::Dispatcher.new } + let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:resource_source) { "calculator.msi" } let(:resource_name) { "calculator" } let(:installer_type) { nil } diff --git a/spec/unit/provider/package/yum_spec.rb b/spec/unit/provider/package/yum_spec.rb index e9aec933e2..23a28c6d37 100644 --- a/spec/unit/provider/package/yum_spec.rb +++ b/spec/unit/provider/package/yum_spec.rb @@ -785,6 +785,24 @@ describe Chef::Provider::Package::Yum do end end + describe "when locking a package" do + it "should run yum versionlock add with the package name" do + expect(@provider).to receive(:yum_command).with( + "-d0 -e0 -y versionlock add emacs" + ) + @provider.lock_package("emacs", nil) + end + end + + describe "when unlocking a package" do + it "should run yum versionlock delete with the package name" do + expect(@provider).to receive(:yum_command).with( + "-d0 -e0 -y versionlock delete emacs" + ) + @provider.unlock_package("emacs", nil) + end + end + describe "when running yum" do it "should run yum once if it exits with a return code of 0" do @status = double("Status", :exitstatus => 0, :stdout => "", :stderr => "") diff --git a/spec/unit/provider/package/zypper_spec.rb b/spec/unit/provider/package/zypper_spec.rb index 8838c26b71..d2800575bc 100644 --- a/spec/unit/provider/package/zypper_spec.rb +++ b/spec/unit/provider/package/zypper_spec.rb @@ -231,6 +231,70 @@ describe Chef::Provider::Package::Zypper do end end + describe "lock_package" do + it "should run zypper addlock with the package name" do + allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true) + shell_out_expectation!( + "zypper --non-interactive addlock emacs" + ) + provider.lock_package(["emacs"], [nil]) + end + it "should run zypper addlock without gpg checks" do + allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) + shell_out_expectation!( + "zypper --non-interactive --no-gpg-checks addlock emacs" + ) + provider.lock_package(["emacs"], [nil]) + end + it "should warn about gpg checks on zypper addlock" do + expect(Chef::Log).to receive(:warn).with( + /All packages will be installed without gpg signature checks/ + ) + shell_out_expectation!( + "zypper --non-interactive --no-gpg-checks addlock emacs" + ) + provider.lock_package(["emacs"], [nil]) + end + it "should run zypper addlock without gpg checks" do + shell_out_expectation!( + "zypper --non-interactive --no-gpg-checks addlock emacs" + ) + provider.lock_package(["emacs"], [nil]) + end + end + + describe "unlock_package" do + it "should run zypper removelock with the package name" do + allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true) + shell_out_expectation!( + "zypper --non-interactive removelock emacs" + ) + provider.unlock_package(["emacs"], [nil]) + end + it "should run zypper removelock without gpg checks" do + allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) + shell_out_expectation!( + "zypper --non-interactive --no-gpg-checks removelock emacs" + ) + provider.unlock_package(["emacs"], [nil]) + end + it "should warn about gpg checks on zypper removelock" do + expect(Chef::Log).to receive(:warn).with( + /All packages will be installed without gpg signature checks/ + ) + shell_out_expectation!( + "zypper --non-interactive --no-gpg-checks removelock emacs" + ) + provider.unlock_package(["emacs"], [nil]) + end + it "should run zypper removelock without gpg checks" do + shell_out_expectation!( + "zypper --non-interactive --no-gpg-checks removelock emacs" + ) + provider.unlock_package(["emacs"], [nil]) + end + end + describe "on an older zypper" do before(:each) do allow(provider).to receive(:`).and_return("0.11.6") diff --git a/spec/unit/provider/package_spec.rb b/spec/unit/provider/package_spec.rb index 40b7516b5c..27d28c698c 100644 --- a/spec/unit/provider/package_spec.rb +++ b/spec/unit/provider/package_spec.rb @@ -321,6 +321,56 @@ describe Chef::Provider::Package do end end + describe "When locking the package" do + before(:each) do + allow(provider).to receive(:lock_package).and_return(true) + end + + it "should lock the package if it is unlocked" do + allow(provider).to receive(:package_locked).and_return(false) + expect(provider).to receive(:lock_package) + provider.run_action(:lock) + end + + it "should not lock the package if it is already locked" do + allow(provider).to receive(:package_locked).and_return(true) + expect(provider).not_to receive(:lock_package) + provider.run_action(:lock) + expect(new_resource).not_to be_updated_by_last_action + end + + it "should set the resource to updated if it locks the package" do + allow(provider).to receive(:package_locked).and_return(false) + provider.run_action(:lock) + expect(new_resource).to be_updated + end + end + + describe "When unlocking the package" do + before(:each) do + allow(provider).to receive(:unlock_package).and_return(true) + end + + it "should unlock the package if it is locked" do + allow(provider).to receive(:package_locked).and_return(true) + expect(provider).to receive(:unlock_package) + provider.run_action(:unlock) + end + + it "should not unlock the package if it is already unlocked" do + allow(provider).to receive(:package_locked).and_return(false) + expect(provider).not_to receive(:unlock_package) + provider.run_action(:unlock) + expect(new_resource).not_to be_updated_by_last_action + end + + it "should set the resource to updated if it unlocks the package" do + allow(provider).to receive(:package_locked).and_return(true) + provider.run_action(:unlock) + expect(new_resource).to be_updated + end + end + describe "when running commands to be implemented by subclasses" do it "should raises UnsupportedAction for install" do expect { provider.install_package("emacs", "1.4.2") }.to raise_error(Chef::Exceptions::UnsupportedAction) @@ -346,6 +396,14 @@ describe Chef::Provider::Package do it "should raise UnsupportedAction for reconfig" do expect { provider.reconfig_package("emacs", "1.4.2") }.to raise_error(Chef::Exceptions::UnsupportedAction) end + + it "should raise UnsupportedAction for lock" do + expect { provider.lock_package("emacs", nil) }.to raise_error(Chef::Exceptions::UnsupportedAction) + end + + it "should raise UnsupportedAction for unlock" do + expect { provider.unlock_package("emacs", nil) }.to raise_error(Chef::Exceptions::UnsupportedAction) + end end describe "when given a response file" do diff --git a/spec/unit/provider/remote_file/content_spec.rb b/spec/unit/provider/remote_file/content_spec.rb index db9a75458d..307eb98187 100644 --- a/spec/unit/provider/remote_file/content_spec.rb +++ b/spec/unit/provider/remote_file/content_spec.rb @@ -159,7 +159,7 @@ describe Chef::Provider::RemoteFile::Content do describe "when there is an array of sources and the first fails" do - # https://github.com/opscode/chef/pull/1358#issuecomment-40853299 + # https://github.com/chef/chef/pull/1358#issuecomment-40853299 def create_exception(exception_class) if [ Net::HTTPServerException, Net::HTTPFatalError ].include? exception_class exception_class.new("message", { "something" => 1 }) diff --git a/spec/unit/provider/user/solaris_spec.rb b/spec/unit/provider/user/solaris_spec.rb index 8a5e654a0d..1935336308 100644 --- a/spec/unit/provider/user/solaris_spec.rb +++ b/spec/unit/provider/user/solaris_spec.rb @@ -99,20 +99,43 @@ describe Chef::Provider::User::Solaris do end describe "when managing user locked status" do + let(:user_lock) { "adam:FOO:::::::" } + let(:shadow_file_contents) do + %W{ + user1:LK::::::: + #{user_lock} + user2:NP::::::: + } + end + describe "when determining if the user is locked" do + before do + allow(IO).to receive(:read).and_return(shadow_file_contents.join("\n")) + end + + context "when user does not exist" do + let(:user_lock) { "other_user:FOO:::::::" } + + it "should raise a sensible error" do + expect { provider.check_lock }.to raise_error(Chef::Exceptions::User) + end + end # locked shadow lines [ - "adam:LK:::::::", "adam:*LK*:::::::", "adam:*LK*foobar:::::::", "adam:*LK*bahamas10:::::::", + "adam:*LK*goonawaLK:::::::", + "adam:*LK*LKgir:::::::", "adam:*LK*L....:::::::", ].each do |shadow| - it "should return true if user is locked with #{shadow}" do - shell_return = shellcmdresult.new(shadow + "\n", "", 0) - expect(provider).to receive(:shell_out!).with("getent", "shadow", "adam").and_return(shell_return) - expect(provider.check_lock).to eql(true) + context "for user 'adam' with entry '#{shadow}'" do + let(:user_lock) { shadow } + + it "should return true" do + expect(provider.check_lock).to eql(true) + end end end @@ -122,12 +145,16 @@ describe Chef::Provider::User::Solaris do "adam:*NP*:::::::", "adam:foobar:::::::", "adam:bahamas10:::::::", + "adam:goonawaLK:::::::", + "adam:LKgir:::::::", "adam:L...:::::::", ].each do |shadow| - it "should return false if user is unlocked with #{shadow}" do - shell_return = shellcmdresult.new(shadow + "\n", "", 0) - expect(provider).to receive(:shell_out!).with("getent", "shadow", "adam").and_return(shell_return) - expect(provider.check_lock).to eql(false) + context "for user 'adam' with entry '#{shadow}'" do + let(:user_lock) { shadow } + + it "should return false" do + expect(provider.check_lock).to eql(false) + end end end end diff --git a/spec/unit/provider/user_spec.rb b/spec/unit/provider/user_spec.rb index 1a8ad6ad1b..719dc8d492 100644 --- a/spec/unit/provider/user_spec.rb +++ b/spec/unit/provider/user_spec.rb @@ -221,6 +221,12 @@ describe Chef::Provider::User do it "should return false if the objects are identical" do expect(@provider.compare_user).to eql(false) end + + it "should ignore differences in trailing slash in home paths" do + @new_resource.home "/home/adam" + @current_resource.home "/home/adam/" + expect(@provider.compare_user).to eql(false) + end end describe "action_create" do diff --git a/spec/unit/resource/apt_repository_spec.rb b/spec/unit/resource/apt_repository_spec.rb index 0b0c0c5d26..69cf94ae56 100644 --- a/spec/unit/resource/apt_repository_spec.rb +++ b/spec/unit/resource/apt_repository_spec.rb @@ -24,7 +24,7 @@ describe Chef::Resource::AptRepository do let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:resource) { Chef::Resource::AptRepository.new("multiverse", run_context) } - it "should create a new Chef::Resource::AptUpdate" do + it "should create a new Chef::Resource::AptRepository" do expect(resource).to be_a_kind_of(Chef::Resource) expect(resource).to be_a_kind_of(Chef::Resource::AptRepository) end |