summaryrefslogtreecommitdiff
path: root/spec/unit/provider
diff options
context:
space:
mode:
authorSeth Chisamore <schisamo@opscode.com>2012-10-30 10:39:35 -0400
committerSeth Chisamore <schisamo@opscode.com>2012-10-30 10:39:35 -0400
commit24dc69a9a97e82a6e4207de68d6dcc664178249b (patch)
tree19bb289c9f88b4bbab066bc56b95d6d222fd5c35 /spec/unit/provider
parent9348c1c9c80ee757354d624b7dc1b78ebc7605c4 (diff)
downloadchef-24dc69a9a97e82a6e4207de68d6dcc664178249b.tar.gz
[OC-3564] move core Chef to the repo root \o/ \m/
The opscode/chef repository now only contains the core Chef library code used by chef-client, knife and chef-solo!
Diffstat (limited to 'spec/unit/provider')
-rw-r--r--spec/unit/provider/breakpoint_spec.rb54
-rw-r--r--spec/unit/provider/cookbook_file_spec.rb220
-rw-r--r--spec/unit/provider/cron/solaris_spec.rb121
-rw-r--r--spec/unit/provider/cron_spec.rb812
-rw-r--r--spec/unit/provider/deploy/revision_spec.rb109
-rw-r--r--spec/unit/provider/deploy/timestamped_spec.rb40
-rw-r--r--spec/unit/provider/deploy_spec.rb654
-rw-r--r--spec/unit/provider/directory_spec.rb147
-rw-r--r--spec/unit/provider/env_spec.rb232
-rw-r--r--spec/unit/provider/erl_call_spec.rb88
-rw-r--r--spec/unit/provider/execute_spec.rb63
-rw-r--r--spec/unit/provider/file_spec.rb498
-rw-r--r--spec/unit/provider/git_spec.rb352
-rw-r--r--spec/unit/provider/group/dscl_spec.rb294
-rw-r--r--spec/unit/provider/group/gpasswd_spec.rb108
-rw-r--r--spec/unit/provider/group/groupadd_spec.rb161
-rw-r--r--spec/unit/provider/group/groupmod_spec.rb134
-rw-r--r--spec/unit/provider/group/pw_spec.rb140
-rw-r--r--spec/unit/provider/group/usermod_spec.rb95
-rw-r--r--spec/unit/provider/group/windows_spec.rb94
-rw-r--r--spec/unit/provider/group_spec.rb259
-rw-r--r--spec/unit/provider/http_request_spec.rb178
-rw-r--r--spec/unit/provider/ifconfig_spec.rb213
-rw-r--r--spec/unit/provider/link_spec.rb252
-rw-r--r--spec/unit/provider/log_spec.rb81
-rw-r--r--spec/unit/provider/mdadm_spec.rb128
-rw-r--r--spec/unit/provider/mount/mount_spec.rb398
-rw-r--r--spec/unit/provider/mount/windows_spec.rb134
-rw-r--r--spec/unit/provider/mount_spec.rb160
-rw-r--r--spec/unit/provider/ohai_spec.rb85
-rw-r--r--spec/unit/provider/package/apt_spec.rb351
-rw-r--r--spec/unit/provider/package/dpkg_spec.rb216
-rw-r--r--spec/unit/provider/package/easy_install_spec.rb112
-rw-r--r--spec/unit/provider/package/freebsd_spec.rb259
-rw-r--r--spec/unit/provider/package/ips_spec.rb209
-rw-r--r--spec/unit/provider/package/macports_spec.rb203
-rw-r--r--spec/unit/provider/package/pacman_spec.rb206
-rw-r--r--spec/unit/provider/package/portage_spec.rb276
-rw-r--r--spec/unit/provider/package/rpm_spec.rb152
-rw-r--r--spec/unit/provider/package/rubygems_spec.rb614
-rw-r--r--spec/unit/provider/package/smartos_spec.rb83
-rw-r--r--spec/unit/provider/package/solaris_spec.rb181
-rw-r--r--spec/unit/provider/package/yum_spec.rb1795
-rw-r--r--spec/unit/provider/package/zypper_spec.rb159
-rw-r--r--spec/unit/provider/package_spec.rb429
-rw-r--r--spec/unit/provider/remote_directory_spec.rb204
-rw-r--r--spec/unit/provider/remote_file_spec.rb324
-rw-r--r--spec/unit/provider/route_spec.rb230
-rw-r--r--spec/unit/provider/ruby_block_spec.rb46
-rw-r--r--spec/unit/provider/script_spec.rb96
-rw-r--r--spec/unit/provider/service/arch_service_spec.rb330
-rw-r--r--spec/unit/provider/service/debian_service_spec.rb254
-rw-r--r--spec/unit/provider/service/freebsd_service_spec.rb379
-rw-r--r--spec/unit/provider/service/gentoo_service_spec.rb144
-rw-r--r--spec/unit/provider/service/init_service_spec.rb212
-rw-r--r--spec/unit/provider/service/insserv_service_spec.rb76
-rw-r--r--spec/unit/provider/service/invokercd_service_spec.rb212
-rw-r--r--spec/unit/provider/service/macosx_spec.rb229
-rw-r--r--spec/unit/provider/service/redhat_spec.rb156
-rw-r--r--spec/unit/provider/service/simple_service_spec.rb171
-rw-r--r--spec/unit/provider/service/solaris_smf_service_spec.rb140
-rw-r--r--spec/unit/provider/service/systemd_service_spec.rb239
-rw-r--r--spec/unit/provider/service/upstart_service_spec.rb314
-rw-r--r--spec/unit/provider/service/windows_spec.rb235
-rw-r--r--spec/unit/provider/service_spec.rb169
-rw-r--r--spec/unit/provider/subversion_spec.rb281
-rw-r--r--spec/unit/provider/template_spec.rb198
-rw-r--r--spec/unit/provider/user/dscl_spec.rb480
-rw-r--r--spec/unit/provider/user/pw_spec.rb235
-rw-r--r--spec/unit/provider/user/useradd_spec.rb386
-rw-r--r--spec/unit/provider/user/windows_spec.rb178
-rw-r--r--spec/unit/provider/user_spec.rb477
72 files changed, 17944 insertions, 0 deletions
diff --git a/spec/unit/provider/breakpoint_spec.rb b/spec/unit/provider/breakpoint_spec.rb
new file mode 100644
index 0000000000..977624597a
--- /dev/null
+++ b/spec/unit/provider/breakpoint_spec.rb
@@ -0,0 +1,54 @@
+#
+# Author:: Daniel DeLeo (<dan@kallistec.com>)
+# Copyright:: Copyright (c) 2008 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'
+describe Chef::Provider::Breakpoint do
+
+ before do
+ @resource = Chef::Resource::Breakpoint.new
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @collection = mock("resource collection")
+ @run_context.stub!(:resource_collection).and_return(@collection)
+ @provider = Chef::Provider::Breakpoint.new(@resource, @run_context)
+ end
+
+ it "responds to load_current_resource" do
+ @provider.should respond_to(:load_current_resource)
+ end
+
+ it "gets the iterator from @collection and pauses it" do
+ Shell.stub!(:running?).and_return(true)
+ @iterator = mock("stepable_iterator")
+ @collection.stub!(:iterator).and_return(@iterator)
+ @iterator.should_receive(:pause)
+ @provider.action_break
+ @resource.should be_updated
+ end
+
+ it "doesn't pause the iterator if chef-shell isn't running" do
+ Shell.stub!(:running?).and_return(false)
+ @iterator = mock("stepable_iterator")
+ @collection.stub!(:iterator).and_return(@iterator)
+ @iterator.should_not_receive(:pause)
+ @provider.action_break
+ end
+
+end
diff --git a/spec/unit/provider/cookbook_file_spec.rb b/spec/unit/provider/cookbook_file_spec.rb
new file mode 100644
index 0000000000..c70a7d852c
--- /dev/null
+++ b/spec/unit/provider/cookbook_file_spec.rb
@@ -0,0 +1,220 @@
+#
+# Author:: Daniel DeLeo (<dan@opscode.com>)
+# Copyright:: Copyright (c) 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 'ostruct'
+
+describe Chef::Provider::CookbookFile do
+ before do
+ Chef::FileAccessControl.any_instance.stub(:set_all)
+ Chef::FileAccessControl.any_instance.stub(:modified?).and_return(true)
+ @cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks"))
+ Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, @cookbook_repo) }
+
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ cl = Chef::CookbookLoader.new(@cookbook_repo)
+ cl.load_cookbooks
+ @cookbook_collection = Chef::CookbookCollection.new(cl)
+ @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+
+ @new_resource = Chef::Resource::CookbookFile.new('apache2_module_conf_generate.pl', @run_context)
+ @new_resource.cookbook_name = 'apache2'
+ @provider = Chef::Provider::CookbookFile.new(@new_resource, @run_context)
+
+ @file_content=<<-EXPECTED
+# apache2_module_conf_generate.pl
+# this is just here for show.
+EXPECTED
+
+ end
+
+ it "prefers the explicit cookbook name on the resource to the implicit one" do
+ @new_resource.cookbook('nginx')
+ @provider.resource_cookbook.should == 'nginx'
+ end
+
+ it "falls back to the implicit cookbook name on the resource" do
+ @provider.resource_cookbook.should == 'apache2'
+ end
+
+ describe "when loading the current file state" do
+
+ it "converts windows-y filenames to unix-y ones" do
+ @new_resource.path('windows\stuff')
+ @provider.load_current_resource
+ @new_resource.path.should == 'windows/stuff'
+ end
+
+ it "sets the current resources path to the same as the new resource" do
+ @new_resource.path('/tmp/file')
+ @provider.load_current_resource
+ @provider.current_resource.path.should == '/tmp/file'
+ end
+ end
+
+ describe "when the enclosing directory of the target file location doesn't exist" do
+ before do
+ @new_resource.path("/tmp/no/such/intermediate/path/file.txt")
+ end
+
+ it "raises a specific error alerting the user to the problem" do
+ lambda {@provider.run_action(:create)}.should raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+ end
+ end
+ describe "when the file doesn't yet exist" do
+ before do
+ @install_to = Dir.tmpdir + '/apache2_modconf.pl'
+
+ @current_resource = @new_resource.dup
+ @provider.current_resource = @current_resource
+ end
+
+ after { ::File.exist?(@install_to) && FileUtils.rm(@install_to) }
+
+ it "loads the current file state" do
+ @provider.load_current_resource
+ @provider.current_resource.checksum.should be_nil
+ end
+
+ it "looks up a file from the cookbook cache" do
+ expected = CHEF_SPEC_DATA + "/cookbooks/apache2/files/default/apache2_module_conf_generate.pl"
+ @provider.file_cache_location.should == expected
+ end
+
+ it "stages the cookbook to a temporary file" do
+ @new_resource.path(@install_to)
+ @provider.should_receive(:deploy_tempfile)
+ @provider.run_action(:create)
+ end
+
+ it "installs the file from the cookbook cache" do
+ @new_resource.path(@install_to)
+ @provider.should_receive(:backup_new_resource)
+ @provider.stub!(:update_new_file_state)
+ @provider.run_action(:create)
+ actual = IO.read(@install_to)
+ actual.should == @file_content
+ end
+
+ it "installs the file for create_if_missing --> from Provider::File" do
+ @new_resource.path(@install_to)
+ @provider.should_receive(:backup_new_resource)
+ @provider.stub!(:update_new_file_state)
+ @provider.run_action(:create_if_missing)
+ actual = IO.read(@install_to)
+ actual.should == @file_content
+ end
+
+ it "marks the resource as updated by the last action --> being tested in the converge framework" do
+ @new_resource.path(@install_to)
+ @provider.stub!(:backup_new_resource)
+ @provider.stub!(:set_file_access_controls)
+ @provider.stub!(:update_new_file_state)
+ @provider.run_action(:create)
+ @new_resource.should be_updated
+ @new_resource.should be_updated_by_last_action
+ end
+
+ end
+
+ describe "when the file exists but has incorrect content" do
+ before do
+ @tempfile = Tempfile.open('cookbook_file_spec')
+ @new_resource.path(@target_file = @tempfile.path)
+ @tempfile.puts "the wrong content"
+ @tempfile.close
+ @current_resource = @new_resource.dup
+ @provider.current_resource = @current_resource
+ end
+
+ it "stages the cookbook to a temporary file" do
+ # prevents file backups where we might not have write access
+ @provider.should_receive(:backup_new_resource)
+ @new_resource.path(@install_to)
+ @provider.should_receive(:deploy_tempfile)
+ @provider.run_action(:create)
+ end
+
+ it "overwrites it when the create action is called" do
+ @provider.should_receive(:backup_new_resource)
+ @provider.run_action(:create)
+ actual = IO.read(@target_file)
+ actual.should == @file_content
+ end
+
+ it "marks the resource as updated by the last action" do
+ @provider.should_receive(:backup_new_resource)
+ @provider.run_action(:create)
+ @new_resource.should be_updated
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "doesn't overwrite when the create if missing action is called" do
+ @provider.should_not_receive(:set_file_access_controls)
+ @provider.run_action(:create_if_missing)
+ actual = IO.read(@target_file)
+ actual.should == "the wrong content\n"
+ end
+
+ it "doesn't mark the resource as updated by the action for create_if_missing" do
+ @provider.run_action(:create_if_missing)
+ @new_resource.should_not be_updated
+ @new_resource.should_not be_updated_by_last_action
+ end
+
+ after { @tempfile && @tempfile.close! }
+ end
+
+ describe "when the file has the correct content" do
+ before do
+ Chef::FileAccessControl.any_instance.stub(:modified?).and_return(false)
+ @tempfile = Tempfile.open('cookbook_file_spec')
+ # CHEF-2991: We handle CRLF very poorly and we don't know what line endings
+ # our source file is going to have, so we use binary mode to preserve CRLF if needed.
+ source_file = CHEF_SPEC_DATA + "/cookbooks/apache2/files/default/apache2_module_conf_generate.pl"
+ @tempfile.binmode unless File.open(source_file, "rb") { |f| f.read =~ /\r/ }
+ @new_resource.path(@target_file = @tempfile.path)
+ @tempfile.write(@file_content)
+ @tempfile.close
+ @current_resource = @new_resource.dup
+ @provider.current_resource = @current_resource
+ end
+
+ after { @tempfile && @tempfile.unlink}
+
+ it "checks access control but does not alter content when action is create" do
+ @provider.should_receive(:set_all_access_controls)
+ @provider.should_not_receive(:stage_file_to_tmpdir)
+ @provider.run_action(:create)
+ end
+
+ it "does not mark the resource as updated by the last action" do
+ @provider.run_action(:create)
+ @new_resource.should_not be_updated
+ @new_resource.should_not be_updated_by_last_action
+ end
+
+ it "does not alter content or access control when action is create if missing" do
+ @provider.should_not_receive(:set_all_access_controls)
+ @provider.should_not_receive(:stage_file_to_tmpdir)
+ @provider.run_action(:create_if_missing)
+ end
+
+ end
+end
diff --git a/spec/unit/provider/cron/solaris_spec.rb b/spec/unit/provider/cron/solaris_spec.rb
new file mode 100644
index 0000000000..55516d59e9
--- /dev/null
+++ b/spec/unit/provider/cron/solaris_spec.rb
@@ -0,0 +1,121 @@
+#
+# Author:: Bryan McLellan (btm@loftninjas.org)
+# Author:: Toomas Pelberg (toomasp@gmx.net)
+# Copyright:: Copyright (c) 2009 Bryan McLellan
+# Copyright:: Copyright (c) 2010 Toomas Pelberg
+# 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::Cron::Solaris do
+ before do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Cron.new("cronhole some stuff")
+ @new_resource.user "root"
+ @new_resource.minute "30"
+ @new_resource.command "/bin/true"
+
+ @provider = Chef::Provider::Cron::Solaris.new(@new_resource, @run_context)
+ end
+
+ it "should inherit from Chef::Provider:Cron" do
+ @provider.should be_a(Chef::Provider::Cron)
+ end
+
+ describe "read_crontab" do
+ before :each do
+ @status = mock("Status", :exitstatus => 0)
+ @stdout = StringIO.new(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+* 5 * * * /bin/true
+
+# Another comment
+ CRONTAB
+ @provider.stub!(:popen4).and_yield(1234, StringIO.new, @stdout, StringIO.new).and_return(@status)
+ end
+
+ it "should call crontab -l with the user" do
+ @provider.should_receive(:popen4).with("crontab -l #{@new_resource.user}").and_return(@status)
+ @provider.send(:read_crontab)
+ end
+
+ it "should return the contents of the crontab" do
+ crontab = @provider.send(:read_crontab)
+ crontab.should == <<-CRONTAB
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+* 5 * * * /bin/true
+
+# Another comment
+CRONTAB
+ end
+
+ it "should return nil if the user has no crontab" do
+ status = mock("Status", :exitstatus => 1)
+ @provider.stub!(:popen4).and_return(status)
+ @provider.send(:read_crontab).should == nil
+ end
+
+ it "should raise an exception if another error occurs" do
+ status = mock("Status", :exitstatus => 2)
+ @provider.stub!(:popen4).and_return(status)
+ lambda do
+ @provider.send(:read_crontab)
+ end.should raise_error(Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: 2")
+ end
+ end
+
+ describe "write_crontab" do
+ before :each do
+ @status = mock("Status", :exitstatus => 0)
+ @provider.stub!(:run_command).and_return(@status)
+ @tempfile = mock("foo", :path => "/tmp/foo", :close => true, :binmode => nil)
+ Tempfile.stub!(:new).and_return(@tempfile)
+ @tempfile.should_receive(:flush)
+ @tempfile.should_receive(:chmod).with(420)
+ @tempfile.should_receive(:close!)
+ end
+
+ it "should call crontab for the user" do
+ @provider.should_receive(:run_command).with(hash_including(:user => @new_resource.user))
+ @tempfile.should_receive(:<<).with("Foo")
+ @provider.send(:write_crontab, "Foo")
+ end
+
+ it "should call crontab with a file containing the crontab" do
+ @provider.should_receive(:run_command) do |args|
+ (args[:command] =~ %r{\A/usr/bin/crontab (/\S+)\z}).should be_true
+ $1.should == "/tmp/foo"
+ @status
+ end
+ @tempfile.should_receive(:<<).with("Foo\n# wibble\n wah!!")
+ @provider.send(:write_crontab, "Foo\n# wibble\n wah!!")
+ end
+
+ it "should raise an exception if the command returns non-zero" do
+ @tempfile.should_receive(:<<).with("Foo")
+ @status.stub!(:exitstatus).and_return(1)
+ lambda do
+ @provider.send(:write_crontab, "Foo")
+ end.should raise_error(Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: 1")
+ end
+ end
+end
diff --git a/spec/unit/provider/cron_spec.rb b/spec/unit/provider/cron_spec.rb
new file mode 100644
index 0000000000..5a848a30e6
--- /dev/null
+++ b/spec/unit/provider/cron_spec.rb
@@ -0,0 +1,812 @@
+#
+# Author:: Bryan McLellan (btm@loftninjas.org)
+# Copyright:: Copyright (c) 2009 Bryan McLellan
+# 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::Cron do
+ before do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Cron.new("cronhole some stuff", @run_context)
+ @new_resource.user "root"
+ @new_resource.minute "30"
+ @new_resource.command "/bin/true"
+
+ @provider = Chef::Provider::Cron.new(@new_resource, @run_context)
+ end
+
+ describe "when examining the current system state" do
+ context "with no crontab for the user" do
+ before :each do
+ @provider.stub!(:read_crontab).and_return(nil)
+ end
+
+ it "should set cron_empty" do
+ @provider.load_current_resource
+ @provider.cron_empty.should == true
+ @provider.cron_exists.should == false
+ end
+
+ it "should report an empty crontab" do
+ Chef::Log.should_receive(:debug).with("Cron empty for '#{@new_resource.user}'")
+ @provider.load_current_resource
+ end
+ end
+
+ context "with no matching entry in the user's crontab" do
+ before :each do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+* 5 * * * /bin/true
+
+# Another comment
+CRONTAB
+ end
+
+ it "should not set cron_exists or cron_empty" do
+ @provider.load_current_resource
+ @provider.cron_exists.should == false
+ @provider.cron_empty.should == false
+ end
+
+ it "should report no entry found" do
+ Chef::Log.should_receive(:debug).with("Cron '#{@new_resource.name}' not found")
+ @provider.load_current_resource
+ end
+
+ it "should not fail if there's an existing cron with a numerical argument" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+# Chef Name: foo[bar] (baz)
+21 */4 * * * some_prog 1234567
+CRONTAB
+ lambda {
+ @provider.load_current_resource
+ }.should_not raise_error
+ end
+ end
+
+ context "with a matching entry in the user's crontab" do
+ before :each do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+* 5 * 1 * /bin/true param1 param2
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+CRONTAB
+ end
+
+ it "should set cron_exists" do
+ @provider.load_current_resource
+ @provider.cron_exists.should == true
+ @provider.cron_empty.should == false
+ end
+
+ it "should pull the details out of the cron line" do
+ cron = @provider.load_current_resource
+ cron.minute.should == '*'
+ cron.hour.should == '5'
+ cron.day.should == '*'
+ cron.month.should == '1'
+ cron.weekday.should == '*'
+ cron.command.should == '/bin/true param1 param2'
+ end
+
+ it "should pull env vars out" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+MAILTO=foo@example.com
+SHELL=/bin/foosh
+PATH=/bin:/foo
+HOME=/home/foo
+* 5 * 1 * /bin/true param1 param2
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+CRONTAB
+ cron = @provider.load_current_resource
+ cron.mailto.should == 'foo@example.com'
+ cron.shell.should == '/bin/foosh'
+ cron.path.should == '/bin:/foo'
+ cron.home.should == '/home/foo'
+ cron.minute.should == '*'
+ cron.hour.should == '5'
+ cron.day.should == '*'
+ cron.month.should == '1'
+ cron.weekday.should == '*'
+ cron.command.should == '/bin/true param1 param2'
+ end
+
+ it "should parse and load generic and standard environment variables from cron entry" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+# Chef Name: cronhole some stuff
+MAILTO=warn@example.com
+TEST=lol
+FLAG=1
+* 5 * * * /bin/true
+CRONTAB
+ cron = @provider.load_current_resource
+
+ cron.mailto.should == "warn@example.com"
+ cron.environment.should == {"TEST" => "lol", "FLAG" => "1"}
+ end
+
+ it "should not break with variabels that match the cron resource internals" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+# Chef Name: cronhole some stuff
+MINUTE=40
+HOUR=midnight
+TEST=lol
+ENVIRONMENT=production
+* 5 * * * /bin/true
+CRONTAB
+ cron = @provider.load_current_resource
+
+ cron.minute.should == '*'
+ cron.hour.should == '5'
+ cron.environment.should == {"MINUTE" => "40", "HOUR" => "midnight", "TEST" => "lol", "ENVIRONMENT" => "production"}
+ end
+
+ it "should report the match" do
+ Chef::Log.should_receive(:debug).with("Found cron '#{@new_resource.name}'")
+ @provider.load_current_resource
+ end
+ end
+
+ context "with a matching entry in the user's crontab using month names and weekday names (#CHEF-3178)" do
+ before :each do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+* 5 * Jan Mon /bin/true param1 param2
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+CRONTAB
+ end
+
+ it "should set cron_exists" do
+ @provider.load_current_resource
+ @provider.cron_exists.should == true
+ @provider.cron_empty.should == false
+ end
+
+ it "should pull the details out of the cron line" do
+ cron = @provider.load_current_resource
+ cron.minute.should == '*'
+ cron.hour.should == '5'
+ cron.day.should == '*'
+ cron.month.should == 'Jan'
+ cron.weekday.should == 'Mon'
+ cron.command.should == '/bin/true param1 param2'
+ end
+
+ it "should report the match" do
+ Chef::Log.should_receive(:debug).with("Found cron '#{@new_resource.name}'")
+ @provider.load_current_resource
+ end
+ end
+
+ context "with a matching entry without a crontab line" do
+ it "should set cron_exists and leave current_resource values at defaults" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+CRONTAB
+ cron = @provider.load_current_resource
+ @provider.cron_exists.should == true
+ cron.minute.should == '*'
+ cron.hour.should == '*'
+ cron.day.should == '*'
+ cron.month.should == '*'
+ cron.weekday.should == '*'
+ cron.command.should == nil
+ end
+
+ it "should not pick up a commented out crontab line" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+#* 5 * 1 * /bin/true param1 param2
+CRONTAB
+ cron = @provider.load_current_resource
+ @provider.cron_exists.should == true
+ cron.minute.should == '*'
+ cron.hour.should == '*'
+ cron.day.should == '*'
+ cron.month.should == '*'
+ cron.weekday.should == '*'
+ cron.command.should == nil
+ end
+
+ it "should not pick up a later crontab entry" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+#* 5 * 1 * /bin/true param1 param2
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+CRONTAB
+ cron = @provider.load_current_resource
+ @provider.cron_exists.should == true
+ cron.minute.should == '*'
+ cron.hour.should == '*'
+ cron.day.should == '*'
+ cron.month.should == '*'
+ cron.weekday.should == '*'
+ cron.command.should == nil
+ end
+ end
+ end
+
+ describe "cron_different?" do
+ before :each do
+ @current_resource = Chef::Resource::Cron.new("cronhole some stuff")
+ @current_resource.user "root"
+ @current_resource.minute "30"
+ @current_resource.command "/bin/true"
+ @provider.current_resource = @current_resource
+ end
+
+ [:minute, :hour, :day, :month, :weekday, :command, :mailto, :path, :shell, :home].each do |attribute|
+ it "should return true if #{attribute} doesn't match" do
+ @new_resource.send(attribute, "something_else")
+ @provider.cron_different?.should eql(true)
+ end
+ end
+
+ it "should return true if environment doesn't match" do
+ @new_resource.environment "FOO" => "something_else"
+ @provider.cron_different?.should eql(true)
+ end
+
+ it "should return false if the objects are identical" do
+ @provider.cron_different?.should == false
+ end
+ end
+
+ describe "action_create" do
+ before :each do
+ @provider.stub!(:write_crontab)
+ @provider.stub!(:read_crontab).and_return(nil)
+ end
+
+ context "when there is no existing crontab" do
+ before :each do
+ @provider.cron_exists = false
+ @provider.cron_empty = true
+ end
+
+ it "should create a crontab with the entry" do
+ @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+# Chef Name: cronhole some stuff
+30 * * * * /bin/true
+ ENDCRON
+ @provider.run_action(:create)
+ end
+
+ it "should include env variables that are set" do
+ @new_resource.mailto 'foo@example.com'
+ @new_resource.path '/usr/bin:/my/custom/path'
+ @new_resource.shell '/bin/foosh'
+ @new_resource.home '/home/foo'
+ @new_resource.environment "TEST" => "LOL"
+ @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+# Chef Name: cronhole some stuff
+MAILTO=foo@example.com
+PATH=/usr/bin:/my/custom/path
+SHELL=/bin/foosh
+HOME=/home/foo
+TEST=LOL
+30 * * * * /bin/true
+ ENDCRON
+ @provider.run_action(:create)
+ end
+
+ it "should mark the resource as updated" do
+ @provider.run_action(:create)
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should log the action" do
+ Chef::Log.should_receive(:info).with("cron[cronhole some stuff] added crontab entry")
+ @provider.run_action(:create)
+ end
+ end
+
+ context "when there is a crontab with no matching section" do
+ before :each do
+ @provider.cron_exists = false
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+ CRONTAB
+ end
+
+ it "should add the entry to the crontab" do
+ @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+# Chef Name: cronhole some stuff
+30 * * * * /bin/true
+ ENDCRON
+ @provider.run_action(:create)
+ end
+
+ it "should include env variables that are set" do
+ @new_resource.mailto 'foo@example.com'
+ @new_resource.path '/usr/bin:/my/custom/path'
+ @new_resource.shell '/bin/foosh'
+ @new_resource.home '/home/foo'
+ @new_resource.environment "TEST" => "LOL"
+ @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+# Chef Name: cronhole some stuff
+MAILTO=foo@example.com
+PATH=/usr/bin:/my/custom/path
+SHELL=/bin/foosh
+HOME=/home/foo
+TEST=LOL
+30 * * * * /bin/true
+ ENDCRON
+ @provider.run_action(:create)
+ end
+
+ it "should mark the resource as updated" do
+ @provider.run_action(:create)
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should log the action" do
+ Chef::Log.should_receive(:info).with("cron[cronhole some stuff] added crontab entry")
+ @provider.run_action(:create)
+ end
+ end
+
+ context "when there is a crontab with a matching but different section" do
+ before :each do
+ @provider.cron_exists = true
+ @provider.stub!(:cron_different?).and_return(true)
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+30 * * 3 * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+ CRONTAB
+ end
+
+ it "should update the crontab entry" do
+ @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+30 * * * * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+ ENDCRON
+ @provider.run_action(:create)
+ end
+
+ it "should include env variables that are set" do
+ @new_resource.mailto 'foo@example.com'
+ @new_resource.path '/usr/bin:/my/custom/path'
+ @new_resource.shell '/bin/foosh'
+ @new_resource.home '/home/foo'
+ @new_resource.environment "TEST" => "LOL"
+ @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+MAILTO=foo@example.com
+PATH=/usr/bin:/my/custom/path
+SHELL=/bin/foosh
+HOME=/home/foo
+TEST=LOL
+30 * * * * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+ ENDCRON
+ @provider.run_action(:create)
+ end
+
+ it "should mark the resource as updated" do
+ @provider.run_action(:create)
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should log the action" do
+ Chef::Log.should_receive(:info).with("cron[cronhole some stuff] updated crontab entry")
+ @provider.run_action(:create)
+ end
+ end
+
+ context "when there is a crontab with a matching section with no crontab line in it" do
+ before :each do
+ @provider.cron_exists = true
+ @provider.stub!(:cron_different?).and_return(true)
+ end
+
+ it "should add the crontab to the entry" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+ CRONTAB
+ @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+30 * * * * /bin/true
+ ENDCRON
+ @provider.run_action(:create)
+ end
+
+ it "should not blat any following entries" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+#30 * * * * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+ CRONTAB
+ @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+30 * * * * /bin/true
+#30 * * * * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+ ENDCRON
+ @provider.run_action(:create)
+ end
+
+ it "should handle env vars with no crontab" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+MAILTO=bar@example.com
+PATH=/usr/bin:/my/custom/path
+SHELL=/bin/barsh
+HOME=/home/foo
+
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+ CRONTAB
+ @new_resource.mailto 'foo@example.com'
+ @new_resource.path '/usr/bin:/my/custom/path'
+ @new_resource.shell '/bin/foosh'
+ @new_resource.home '/home/foo'
+ @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+MAILTO=foo@example.com
+PATH=/usr/bin:/my/custom/path
+SHELL=/bin/foosh
+HOME=/home/foo
+30 * * * * /bin/true
+
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+ ENDCRON
+ @provider.run_action(:create)
+ end
+ end
+
+ context "when there is a crontab with a matching and identical section" do
+ before :each do
+ @provider.cron_exists = true
+ @provider.stub!(:cron_different?).and_return(false)
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+* 5 * * * /bin/true
+
+# Another comment
+CRONTAB
+ end
+
+ it "should not update the crontab" do
+ @provider.should_not_receive(:write_crontab)
+ @provider.run_action(:create)
+ end
+
+ it "should not mark the resource as updated" do
+ @provider.run_action(:create)
+ @new_resource.should_not be_updated_by_last_action
+ end
+
+ it "should log nothing changed" do
+ Chef::Log.should_receive(:debug).with("Skipping existing cron entry '#{@new_resource.name}'")
+ @provider.run_action(:create)
+ end
+ end
+ end
+
+ describe "action_delete" do
+ before :each do
+ @provider.stub!(:write_crontab)
+ @provider.stub!(:read_crontab).and_return(nil)
+ end
+
+ context "when the user's crontab has no matching section" do
+ before :each do
+ @provider.cron_exists = false
+ end
+
+ it "should do nothing" do
+ @provider.should_not_receive(:write_crontab)
+ Chef::Log.should_not_receive(:info)
+ @provider.run_action(:delete)
+ end
+
+ it "should not mark the resource as updated" do
+ @provider.run_action(:delete)
+ @new_resource.should_not be_updated_by_last_action
+ end
+ end
+
+ context "when the user has a crontab with a matching section" do
+ before :each do
+ @provider.cron_exists = true
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+30 * * 3 * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+ CRONTAB
+ end
+
+ it "should remove the entry" do
+ @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+ ENDCRON
+ @provider.run_action(:delete)
+ end
+
+ it "should remove any env vars with the entry" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+MAILTO=foo@example.com
+FOO=test
+30 * * 3 * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+ CRONTAB
+ @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+ ENDCRON
+ @provider.run_action(:delete)
+ end
+
+ it "should mark the resource as updated" do
+ @provider.run_action(:delete)
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should log the action" do
+ Chef::Log.should_receive(:info).with("#{@new_resource} deleted crontab entry")
+ @provider.run_action(:delete)
+ end
+ end
+
+ context "when the crontab has a matching section with no crontab line" do
+ before :each do
+ @provider.cron_exists = true
+ end
+
+ it "should remove the section" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+ CRONTAB
+ @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+ ENDCRON
+ @provider.run_action(:delete)
+ end
+
+ it "should not blat following sections" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+#30 * * 3 * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+ CRONTAB
+ @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+#30 * * 3 * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+ ENDCRON
+ @provider.run_action(:delete)
+ end
+
+ it "should remove any envvars with the section" do
+ @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+MAILTO=foo@example.com
+#30 * * 3 * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+ CRONTAB
+ @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+#30 * * 3 * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+ ENDCRON
+ @provider.run_action(:delete)
+ end
+ end
+ end
+
+ describe "read_crontab" do
+ before :each do
+ @status = mock("Status", :exitstatus => 0)
+ @stdout = StringIO.new(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+* 5 * * * /bin/true
+
+# Another comment
+ CRONTAB
+ @provider.stub!(:popen4).and_yield(1234, StringIO.new, @stdout, StringIO.new).and_return(@status)
+ end
+
+ it "should call crontab -l with the user" do
+ @provider.should_receive(:popen4).with("crontab -l -u #{@new_resource.user}").and_return(@status)
+ @provider.send(:read_crontab)
+ end
+
+ it "should return the contents of the crontab" do
+ crontab = @provider.send(:read_crontab)
+ crontab.should == <<-CRONTAB
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+* 5 * * * /bin/true
+
+# Another comment
+ CRONTAB
+ end
+
+ it "should return nil if the user has no crontab" do
+ status = mock("Status", :exitstatus => 1)
+ @provider.stub!(:popen4).and_return(status)
+ @provider.send(:read_crontab).should == nil
+ end
+
+ it "should raise an exception if another error occurs" do
+ status = mock("Status", :exitstatus => 2)
+ @provider.stub!(:popen4).and_return(status)
+ lambda do
+ @provider.send(:read_crontab)
+ end.should raise_error(Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: 2")
+ end
+ end
+
+ describe "write_crontab" do
+ before :each do
+ @status = mock("Status", :exitstatus => 0)
+ @stdin = StringIO.new
+ @provider.stub!(:popen4).and_yield(1234, @stdin, StringIO.new, StringIO.new).and_return(@status)
+ end
+
+ it "should call crontab for the user" do
+ @provider.should_receive(:popen4).with("crontab -u #{@new_resource.user} -", :waitlast => true).and_return(@status)
+ @provider.send(:write_crontab, "Foo")
+ end
+
+ it "should write the given string to the crontab command" do
+ @provider.send(:write_crontab, "Foo\n# wibble\n wah!!")
+ @stdin.string.should == "Foo\n# wibble\n wah!!"
+ end
+
+ it "should raise an exception if the command returns non-zero" do
+ @status.stub!(:exitstatus).and_return(1)
+ lambda do
+ @provider.send(:write_crontab, "Foo")
+ end.should raise_error(Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: 1")
+ end
+ end
+end
diff --git a/spec/unit/provider/deploy/revision_spec.rb b/spec/unit/provider/deploy/revision_spec.rb
new file mode 100644
index 0000000000..396dd09a8e
--- /dev/null
+++ b/spec/unit/provider/deploy/revision_spec.rb
@@ -0,0 +1,109 @@
+#
+# Author:: Daniel DeLeo (<dan@kallistec.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::Deploy::Revision do
+
+ before do
+ @temp_dir = Dir.mktmpdir
+ Chef::Config[:file_cache_path] = @temp_dir
+ @resource = Chef::Resource::Deploy.new("/my/deploy/dir")
+ @resource.revision("8a3195bf3efa246f743c5dfa83683201880f935c")
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @provider = Chef::Provider::Deploy::Revision.new(@resource, @run_context)
+ @provider.load_current_resource
+ @runner = mock("runnah")
+ Chef::Runner.stub!(:new).and_return(@runner)
+ @expected_release_dir = "/my/deploy/dir/releases/8a3195bf3efa246f743c5dfa83683201880f935c"
+ end
+
+ after do
+ # Make sure we don't keep any state in our tests
+ FileUtils.rspec_reset
+ FileUtils.rm_rf @temp_dir if File.directory?( @temp_dir )
+ end
+
+
+ it "uses the resolved revision from the SCM as the release slug" do
+ @provider.scm_provider.stub!(:revision_slug).and_return("uglySlugly")
+ @provider.send(:release_slug).should == "uglySlugly"
+ end
+
+ it "deploys to a dir named after the revision" do
+ @provider.release_path.should == @expected_release_dir
+ end
+
+ it "stores the release dir in the file cache when copying the cached repo" do
+ FileUtils.stub!(:mkdir_p)
+ @provider.stub!(:run_command).and_return(true)
+ @provider.copy_cached_repo
+ @provider.converge
+ @provider.stub!(:release_slug).and_return("73219b87e977d9c7ba1aa57e9ad1d88fa91a0ec2")
+ @provider.load_current_resource
+ @provider.copy_cached_repo
+ @provider.converge
+ second_release = "/my/deploy/dir/releases/73219b87e977d9c7ba1aa57e9ad1d88fa91a0ec2"
+
+ @provider.all_releases.should == [@expected_release_dir,second_release]
+ end
+
+ it "removes a release from the file cache when it's used again in another release and append it to the end" do
+ FileUtils.stub!(:mkdir_p)
+ @provider.stub!(:run_command).and_return(true)
+ @provider.copy_cached_repo
+ @provider.converge
+ @provider.stub!(:release_slug).and_return("73219b87e977d9c7ba1aa57e9ad1d88fa91a0ec2")
+ @provider.load_current_resource
+ @provider.copy_cached_repo
+ @provider.converge
+ second_release = "/my/deploy/dir/releases/73219b87e977d9c7ba1aa57e9ad1d88fa91a0ec2"
+ @provider.all_releases.should == [@expected_release_dir,second_release]
+ @provider.copy_cached_repo
+ @provider.converge
+
+ @provider.stub!(:release_slug).and_return("8a3195bf3efa246f743c5dfa83683201880f935c")
+ @provider.load_current_resource
+ @provider.copy_cached_repo
+ @provider.converge
+ @provider.all_releases.should == [second_release, @expected_release_dir]
+ end
+
+ it "removes a release from the file cache when it's deleted by :cleanup!" do
+ %w{first second third fourth fifth latest}.each do |release_name|
+ @provider.send(:release_created, release_name)
+ end
+ @provider.all_releases.should == %w{first second third fourth fifth latest}
+
+ FileUtils.stub!(:rm_rf)
+ @provider.cleanup!
+ @provider.all_releases.should == %w{second third fourth fifth latest}
+ end
+
+ it "regenerates the file cache if it's not available" do
+ oldest = "/my/deploy/dir/releases/oldest"
+ latest = "/my/deploy/dir/releases/latest"
+ Dir.should_receive(:glob).with("/my/deploy/dir/releases/*").and_return([latest, oldest])
+ ::File.should_receive(:ctime).with(oldest).and_return(Time.now - 10)
+ ::File.should_receive(:ctime).with(latest).and_return(Time.now - 1)
+ @provider.all_releases.should == [oldest, latest]
+ end
+
+end
diff --git a/spec/unit/provider/deploy/timestamped_spec.rb b/spec/unit/provider/deploy/timestamped_spec.rb
new file mode 100644
index 0000000000..b891a03ce2
--- /dev/null
+++ b/spec/unit/provider/deploy/timestamped_spec.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Daniel DeLeo (<dan@kallistec.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::Deploy::Timestamped do
+
+ before do
+ @release_time = Time.utc( 2004, 8, 15, 16, 23, 42)
+ Time.stub!(:now).and_return(@release_time)
+ @expected_release_dir = "/my/deploy/dir/releases/20040815162342"
+ @resource = Chef::Resource::Deploy.new("/my/deploy/dir")
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @timestamped_deploy = Chef::Provider::Deploy::Timestamped.new(@resource, @run_context)
+ @runner = mock("runnah")
+ Chef::Runner.stub!(:new).and_return(@runner)
+ end
+
+ it "gives a timestamp for release_slug" do
+ @timestamped_deploy.send(:release_slug).should == "20040815162342"
+ end
+
+end
diff --git a/spec/unit/provider/deploy_spec.rb b/spec/unit/provider/deploy_spec.rb
new file mode 100644
index 0000000000..6bcd64fbfb
--- /dev/null
+++ b/spec/unit/provider/deploy_spec.rb
@@ -0,0 +1,654 @@
+#
+# Author:: Daniel DeLeo (<dan@kallistec.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::Deploy do
+
+ before do
+ @release_time = Time.utc( 2004, 8, 15, 16, 23, 42)
+ Time.stub!(:now).and_return(@release_time)
+ @expected_release_dir = "/my/deploy/dir/releases/20040815162342"
+ @resource = Chef::Resource::Deploy.new("/my/deploy/dir")
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @provider = Chef::Provider::Deploy.new(@resource, @run_context)
+ @provider.stub!(:release_slug)
+ @provider.stub!(:release_path).and_return(@expected_release_dir)
+ end
+
+ it "loads scm resource" do
+ @provider.scm_provider.should_receive(:load_current_resource)
+ @provider.load_current_resource
+ end
+
+ it "supports :deploy and :rollback actions" do
+ @provider.should respond_to(:action_deploy)
+ @provider.should respond_to(:action_rollback)
+ end
+
+ context "when the deploy_to dir does not exist yet" do
+ before do
+ FileUtils.should_receive(:mkdir_p).with(@resource.deploy_to).ordered
+ FileUtils.should_receive(:mkdir_p).with(@resource.shared_path).ordered
+ ::File.stub!(:directory?).and_return(false)
+ @provider.stub(:symlink)
+ @provider.stub(:migrate)
+ @provider.stub(:copy_cached_repo)
+ end
+
+ it "creates deploy_to dir" do
+ @provider.stub(:update_cached_repo)
+ @provider.deploy
+ end
+
+ it "creates deploy_to dir before calling update_cached_repo (CHEF-3435)" do
+ @provider.send(:converge_actions).should_receive(:empty?).and_return(false)
+ @provider.should_receive(:update_cached_repo).ordered
+ @provider.deploy
+ end
+ end
+
+ it "does not create deploy_to dir if it exists" do
+ ::File.stub!(:directory?).and_return(true)
+ ::Dir.should_receive(:chdir).with(@expected_release_dir).exactly(4).times
+ FileUtils.should_not_receive(:mkdir_p).with(@resource.deploy_to)
+ FileUtils.should_not_receive(:mkdir_p).with(@resource.shared_path)
+ @provider.stub(:copy_cached_repo)
+ @provider.stub(:update_cached_repo)
+ @provider.stub(:symlink)
+ @provider.stub(:migrate)
+ @provider.deploy
+ @provider.converge
+ end
+
+ it "ensures the deploy_to dir ownership after the verfication that it exists" do
+ @provider.should_receive(:verify_directories_exist).ordered
+ @provider.should_receive(:enforce_ownership).ordered
+ @provider.stub(:copy_cached_repo)
+ @provider.stub(:update_cached_repo)
+ @provider.stub(:install_gems)
+ @provider.stub(:enforce_ownership)
+ @provider.stub(:symlink)
+ @provider.stub(:migrate)
+ @provider.deploy
+ end
+
+ it "updates and copies the repo, then does a migrate, symlink, restart, restart, cleanup on deploy" do
+ FileUtils.stub(:mkdir_p).with("/my/deploy/dir")
+ FileUtils.stub(:mkdir_p).with("/my/deploy/dir/shared")
+ @provider.should_receive(:enforce_ownership).twice
+ @provider.should_receive(:update_cached_repo)
+ @provider.should_receive(:copy_cached_repo)
+ @provider.should_receive(:install_gems)
+ @provider.should_receive(:callback).with(:before_migrate, nil)
+ @provider.should_receive(:migrate)
+ @provider.should_receive(:callback).with(:before_symlink, nil)
+ @provider.should_receive(:symlink)
+ @provider.should_receive(:callback).with(:before_restart, nil)
+ @provider.should_receive(:restart)
+ @provider.should_receive(:callback).with(:after_restart, nil)
+ @provider.should_receive(:cleanup!)
+ @provider.deploy
+ @provider.converge
+ end
+
+ it "should not deploy if there is already a deploy at release_path, and it is the current release" do
+ @provider.stub!(:all_releases).and_return([@expected_release_dir])
+ @provider.stub!(:current_release?).with(@expected_release_dir).and_return(true)
+ @provider.should_not_receive(:deploy)
+ @provider.run_action(:deploy)
+ end
+
+ it "should call action_rollback if there is already a deploy of this revision at release_path, and it is not the current release" do
+ @provider.stub!(:all_releases).and_return([@expected_release_dir, "102021"])
+ @provider.stub!(:current_release?).with(@expected_release_dir).and_return(false)
+ @provider.should_receive(:rollback_to).with(@expected_release_dir)
+ @provider.should_receive(:current_release?)
+ @provider.run_action(:deploy)
+ end
+
+ it "calls deploy when deploying a new release" do
+ @provider.stub!(:all_releases).and_return([])
+ @provider.should_receive(:deploy)
+ @provider.run_action(:deploy)
+ end
+
+ it "runs action svn_force_export when new_resource.svn_force_export is true" do
+ @resource.svn_force_export true
+ @provider.scm_provider.should_receive(:run_action).with(:force_export)
+ @provider.update_cached_repo
+ @provider.converge
+ end
+
+ it "Removes the old release before deploying when force deploying over it" do
+ @provider.stub!(:all_releases).and_return([@expected_release_dir])
+ FileUtils.should_receive(:rm_rf).with(@expected_release_dir)
+ @provider.should_receive(:deploy)
+ @provider.run_action(:force_deploy)
+ end
+
+ it "deploys as normal when force deploying and there's no prior release at the same path" do
+ @provider.stub!(:all_releases).and_return([])
+ @provider.should_receive(:deploy)
+ @provider.run_action(:force_deploy)
+ end
+
+ it "dont care by default if error happens on deploy" do
+ @provider.stub!(:all_releases).and_return(['previous_release'])
+ @provider.stub!(:deploy).and_return{ raise "Unexpected error" }
+ @provider.stub!(:previous_release_path).and_return('previous_release')
+ @provider.should_not_receive(:rollback)
+ lambda {
+ @provider.run_action(:deploy)
+ }.should raise_exception(RuntimeError, "Unexpected error")
+ end
+
+ it "rollbacks to previous release if error happens on deploy" do
+ @resource.rollback_on_error true
+ @provider.stub!(:all_releases).and_return(['previous_release'])
+ @provider.stub!(:deploy).and_return{ raise "Unexpected error" }
+ @provider.stub!(:previous_release_path).and_return('previous_release')
+ @provider.should_receive(:rollback)
+ lambda {
+ @provider.run_action(:deploy)
+ }.should raise_exception(RuntimeError, "Unexpected error")
+ end
+
+ describe "on systems without broken Dir.glob results" do
+ it "sets the release path to the penultimate release when one is not specified, symlinks, and rm's the last release on rollback" do
+ @provider.stub!(:release_path).and_return("/my/deploy/dir/releases/3")
+ all_releases = ["/my/deploy/dir/releases/1", "/my/deploy/dir/releases/2", "/my/deploy/dir/releases/3", "/my/deploy/dir/releases/4", "/my/deploy/dir/releases/5"]
+ Dir.stub!(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases)
+ @provider.should_receive(:symlink)
+ FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/releases/4")
+ FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/releases/5")
+ @provider.run_action(:rollback)
+ @provider.release_path.should eql("/my/deploy/dir/releases/3")
+ end
+
+ it "sets the release path to the specified release, symlinks, and rm's any newer releases on rollback" do
+ @provider.unstub!(:release_path)
+ all_releases = ["/my/deploy/dir/releases/20040815162342", "/my/deploy/dir/releases/20040700000000",
+ "/my/deploy/dir/releases/20040600000000", "/my/deploy/dir/releases/20040500000000"].sort!
+ Dir.stub!(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases)
+ @provider.should_receive(:symlink)
+ FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/releases/20040815162342")
+ @provider.run_action(:rollback)
+ @provider.release_path.should eql("/my/deploy/dir/releases/20040700000000")
+ end
+
+ it "sets the release path to the penultimate release, symlinks, and rm's the last release on rollback" do
+ @provider.unstub!(:release_path)
+ all_releases = [ "/my/deploy/dir/releases/20040815162342",
+ "/my/deploy/dir/releases/20040700000000",
+ "/my/deploy/dir/releases/20040600000000",
+ "/my/deploy/dir/releases/20040500000000"]
+ Dir.stub!(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases)
+ @provider.should_receive(:symlink)
+ FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/releases/20040815162342")
+ @provider.run_action(:rollback)
+ @provider.release_path.should eql("/my/deploy/dir/releases/20040700000000")
+ end
+
+ describe "if there are no releases to fallback to" do
+
+ it "an exception is raised when there is only 1 release" do
+ #@provider.unstub!(:release_path) -- unstub the release path on top to feed our own release path
+ all_releases = [ "/my/deploy/dir/releases/20040815162342"]
+ Dir.stub!(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases)
+ #@provider.should_receive(:symlink)
+ #FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/releases/20040815162342")
+ #@provider.run_action(:rollback)
+ #@provider.release_path.should eql(NIL) -- no check needed since assertions will fail
+ lambda {
+ @provider.run_action(:rollback)
+ }.should raise_exception(RuntimeError, "There is no release to rollback to!")
+ end
+
+ it "an exception is raised when there are no releases" do
+ all_releases = []
+ Dir.stub!(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases)
+ lambda {
+ @provider.run_action(:rollback)
+ }.should raise_exception(RuntimeError, "There is no release to rollback to!")
+ end
+ end
+ end
+
+ describe "CHEF-628: on systems with broken Dir.glob results" do
+ it "sets the release path to the penultimate release, symlinks, and rm's the last release on rollback" do
+ @provider.unstub!(:release_path)
+ all_releases = [ "/my/deploy/dir/releases/20040500000000",
+ "/my/deploy/dir/releases/20040600000000",
+ "/my/deploy/dir/releases/20040700000000",
+ "/my/deploy/dir/releases/20040815162342" ]
+ Dir.stub!(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases)
+ @provider.should_receive(:symlink)
+ FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/releases/20040815162342")
+ @provider.run_action(:rollback)
+ @provider.release_path.should eql("/my/deploy/dir/releases/20040700000000")
+ end
+ end
+
+ it "raises a runtime error when there's no release to rollback to" do
+ all_releases = []
+ Dir.stub!(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases)
+ lambda {@provider.run_action(:rollback)}.should raise_error(RuntimeError)
+ end
+
+ it "runs the new resource collection in the runner during a callback" do
+ @runner = mock("Runner")
+ Chef::Runner.stub!(:new).and_return(@runner)
+ @runner.should_receive(:converge)
+ callback_code = Proc.new { :noop }
+ @provider.callback(:whatevs, callback_code)
+ @provider.converge
+ end
+
+ it "loads callback files from the release/ dir if the file exists" do
+ foo_callback = @expected_release_dir + "/deploy/foo.rb"
+ ::File.should_receive(:exist?).with(foo_callback).once.and_return(true)
+ ::Dir.should_receive(:chdir).with(@expected_release_dir).and_yield
+ @provider.should_receive(:from_file).with(foo_callback)
+ @provider.callback(:foo, "deploy/foo.rb")
+ @provider.converge
+ end
+
+ it "raises a runtime error if a callback file is explicitly specified but does not exist" do
+ baz_callback = "/deploy/baz.rb"
+ ::File.should_receive(:exist?).with("#{@expected_release_dir}/#{baz_callback}").and_return(false)
+ @resource.before_migrate baz_callback
+ @provider.define_resource_requirements
+ @provider.action = :deploy
+ lambda {@provider.process_resource_requirements}.should raise_error(RuntimeError)
+ end
+
+ it "runs a default callback if the callback code is nil" do
+ bar_callback = @expected_release_dir + "/deploy/bar.rb"
+ ::File.should_receive(:exist?).with(bar_callback).and_return(true)
+ ::Dir.should_receive(:chdir).with(@expected_release_dir).and_yield
+ @provider.should_receive(:from_file).with(bar_callback)
+ @provider.callback(:bar, nil)
+ @provider.converge
+ end
+
+ it "skips an eval callback if the file doesn't exist" do
+ barbaz_callback = @expected_release_dir + "/deploy/barbaz.rb"
+ ::File.should_receive(:exist?).with(barbaz_callback).and_return(false)
+ ::Dir.should_receive(:chdir).with(@expected_release_dir).and_yield
+ @provider.should_not_receive(:from_file)
+ @provider.callback(:barbaz, nil)
+ @provider.converge
+ end
+
+ # CHEF-3449 #converge_by is called in #recipe_eval and must happen in sequence
+ # with the other calls to #converge_by to keep the train on the tracks
+ it "evaluates a callback file before the corresponding step" do
+ @provider.should_receive(:verify_directories_exist)
+ @provider.should_receive(:update_cached_repo)
+ @provider.should_receive(:enforce_ownership)
+ @provider.should_receive(:copy_cached_repo)
+ @provider.should_receive(:install_gems)
+ @provider.should_receive(:enforce_ownership)
+ @provider.should_receive(:converge_by).ordered # before_migrate
+ @provider.should_receive(:migrate).ordered
+ @provider.should_receive(:converge_by).ordered # before_symlink
+ @provider.should_receive(:symlink).ordered
+ @provider.should_receive(:converge_by).ordered # before_restart
+ @provider.should_receive(:restart).ordered
+ @provider.should_receive(:converge_by).ordered # after_restart
+ @provider.should_receive(:cleanup!)
+ @provider.deploy
+ end
+
+ it "gets a SCM provider as specified by its resource" do
+ @provider.scm_provider.should be_an_instance_of(Chef::Provider::Git)
+ @provider.scm_provider.new_resource.destination.should eql("/my/deploy/dir/shared/cached-copy")
+ end
+
+ it "syncs the cached copy of the repo" do
+ @provider.scm_provider.should_receive(:run_action).with(:sync)
+ @provider.update_cached_repo
+ @provider.converge
+ end
+
+ it "makes a copy of the cached repo in releases dir" do
+ FileUtils.should_receive(:mkdir_p).with("/my/deploy/dir/releases")
+ @provider.should_receive(:run_command).with({:command => "cp -RPp /my/deploy/dir/shared/cached-copy/. #{@expected_release_dir}"})
+ @provider.copy_cached_repo
+ @provider.converge
+ end
+
+ it "calls the internal callback :release_created when copying the cached repo" do
+ FileUtils.stub!(:mkdir_p)
+ @provider.stub!(:run_command).and_return(true)
+ @provider.should_receive(:release_created)
+ @provider.copy_cached_repo
+ @provider.converge
+ end
+
+ it "chowns the whole release dir to user and group specified in the resource" do
+ @resource.user "foo"
+ @resource.group "bar"
+ FileUtils.should_receive(:chown_R).with("foo", "bar", "/my/deploy/dir")
+ @provider.enforce_ownership
+ @provider.converge
+ end
+
+ it "skips the migration when resource.migrate => false but runs symlinks before migration" do
+ @resource.migrate false
+ @provider.should_not_receive :run_command
+ @provider.should_receive :run_symlinks_before_migrate
+ @provider.migrate
+ @provider.converge
+ end
+
+ it "links the database.yml and runs resource.migration_command when resource.migrate #=> true" do
+ @resource.migrate true
+ @resource.migration_command "migration_foo"
+ @resource.user "deployNinja"
+ @resource.group "deployNinjas"
+ @resource.environment "RAILS_ENV" => "production"
+ FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/config/database.yml", @expected_release_dir + "/config/database.yml")
+ @provider.should_receive(:enforce_ownership)
+
+ STDOUT.stub!(:tty?).and_return(true)
+ Chef::Log.stub!(:info?).and_return(true)
+ @provider.should_receive(:run_command).with(:command => "migration_foo", :cwd => @expected_release_dir,
+ :user => "deployNinja", :group => "deployNinjas",
+ :log_level => :info, :live_stream => STDOUT,
+ :log_tag => "deploy[/my/deploy/dir]",
+ :environment => {"RAILS_ENV"=>"production"})
+ @provider.migrate
+ @provider.converge
+ end
+
+ it "purges the current release's /log /tmp/pids/ and /public/system directories" do
+ FileUtils.should_receive(:rm_rf).with(@expected_release_dir + "/log")
+ FileUtils.should_receive(:rm_rf).with(@expected_release_dir + "/tmp/pids")
+ FileUtils.should_receive(:rm_rf).with(@expected_release_dir + "/public/system")
+ @provider.purge_tempfiles_from_current_release
+ @provider.converge
+ end
+
+ it "symlinks temporary files and logs from the shared dir into the current release" do
+ FileUtils.stub(:mkdir_p).with(@resource.shared_path + "/system")
+ FileUtils.stub(:mkdir_p).with(@resource.shared_path + "/pids")
+ FileUtils.stub(:mkdir_p).with(@resource.shared_path + "/log")
+ FileUtils.should_receive(:mkdir_p).with(@expected_release_dir + "/tmp")
+ FileUtils.should_receive(:mkdir_p).with(@expected_release_dir + "/public")
+ FileUtils.should_receive(:mkdir_p).with(@expected_release_dir + "/config")
+ FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/system", @expected_release_dir + "/public/system")
+ FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/pids", @expected_release_dir + "/tmp/pids")
+ FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/log", @expected_release_dir + "/log")
+ FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/config/database.yml", @expected_release_dir + "/config/database.yml")
+ @provider.should_receive(:enforce_ownership)
+ @provider.link_tempfiles_to_current_release
+ @provider.converge
+ end
+
+ it "symlinks the current release dir into production" do
+ FileUtils.should_receive(:rm_f).with("/my/deploy/dir/current")
+ FileUtils.should_receive(:ln_sf).with(@expected_release_dir, "/my/deploy/dir/current")
+ @provider.should_receive(:enforce_ownership)
+ @provider.link_current_release_to_production
+ @provider.converge
+ end
+
+ context "with a customized app layout" do
+
+ before do
+ @resource.purge_before_symlink(%w{foo bar})
+ @resource.create_dirs_before_symlink(%w{baz qux})
+ @resource.symlinks "foo/bar" => "foo/bar", "baz" => "qux/baz"
+ @resource.symlink_before_migrate "radiohead/in_rainbows.yml" => "awesome"
+ @provider.converge
+ end
+
+ it "purges the purge_before_symlink directories" do
+ FileUtils.should_receive(:rm_rf).with(@expected_release_dir + "/foo")
+ FileUtils.should_receive(:rm_rf).with(@expected_release_dir + "/bar")
+ @provider.purge_tempfiles_from_current_release
+ @provider.converge
+ end
+
+ it "symlinks files from the shared directory to the current release directory" do
+ FileUtils.should_receive(:mkdir_p).with(@expected_release_dir + "/baz")
+ FileUtils.should_receive(:mkdir_p).with(@expected_release_dir + "/qux")
+ FileUtils.stub(:mkdir_p).with(@resource.shared_path + "/foo/bar")
+ FileUtils.stub(:mkdir_p).with(@resource.shared_path + "/baz")
+ FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/foo/bar", @expected_release_dir + "/foo/bar")
+ FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/baz", @expected_release_dir + "/qux/baz")
+ FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/radiohead/in_rainbows.yml", @expected_release_dir + "/awesome")
+ @provider.should_receive(:enforce_ownership)
+ @provider.link_tempfiles_to_current_release
+ @provider.converge
+ end
+
+ end
+
+ it "does nothing for restart if restart_command is empty" do
+ @provider.should_not_receive(:run_command)
+ @provider.restart
+ @provider.converge
+ end
+
+ it "runs the restart command in the current application dir when the resource has a restart_command" do
+ @resource.restart_command "restartcmd"
+ @provider.should_receive(:run_command).with(:command => "restartcmd", :cwd => "/my/deploy/dir/current", :log_tag => "deploy[/my/deploy/dir]", :log_level => :debug)
+ @provider.restart
+ @provider.converge
+ end
+
+ it "lists all available releases" do
+ all_releases = ["/my/deploy/dir/20040815162342", "/my/deploy/dir/20040700000000",
+ "/my/deploy/dir/20040600000000", "/my/deploy/dir/20040500000000"].sort!
+ Dir.should_receive(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases)
+ @provider.all_releases.should eql(all_releases)
+ end
+
+ it "removes all but the 5 newest releases" do
+ all_releases = ["/my/deploy/dir/20040815162342", "/my/deploy/dir/20040700000000",
+ "/my/deploy/dir/20040600000000", "/my/deploy/dir/20040500000000",
+ "/my/deploy/dir/20040400000000", "/my/deploy/dir/20040300000000",
+ "/my/deploy/dir/20040200000000", "/my/deploy/dir/20040100000000"].sort!
+ @provider.stub!(:all_releases).and_return(all_releases)
+ FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/20040100000000")
+ FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/20040200000000")
+ FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/20040300000000")
+ @provider.cleanup!
+ @provider.converge
+ end
+
+ it "removes all but a certain number of releases when the resource has a keep_releases" do
+ @resource.keep_releases 7
+ all_releases = ["/my/deploy/dir/20040815162342", "/my/deploy/dir/20040700000000",
+ "/my/deploy/dir/20040600000000", "/my/deploy/dir/20040500000000",
+ "/my/deploy/dir/20040400000000", "/my/deploy/dir/20040300000000",
+ "/my/deploy/dir/20040200000000", "/my/deploy/dir/20040100000000"].sort!
+ @provider.stub!(:all_releases).and_return(all_releases)
+ FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/20040100000000")
+ @provider.cleanup!
+ @provider.converge
+ end
+
+ it "fires a callback for :release_deleted when deleting an old release" do
+ all_releases = ["/my/deploy/dir/20040815162342", "/my/deploy/dir/20040700000000",
+ "/my/deploy/dir/20040600000000", "/my/deploy/dir/20040500000000",
+ "/my/deploy/dir/20040400000000", "/my/deploy/dir/20040300000000"].sort!
+ @provider.stub!(:all_releases).and_return(all_releases)
+ FileUtils.stub!(:rm_rf)
+ @provider.should_receive(:release_deleted).with("/my/deploy/dir/20040300000000")
+ @provider.cleanup!
+ @provider.converge
+ end
+
+ it "puts resource.to_hash in @configuration for backwards compat with capistano-esque deploy hooks" do
+ @provider.instance_variable_get(:@configuration).should == @resource.to_hash
+ end
+
+ it "sets @configuration[:environment] to the value of RAILS_ENV for backwards compat reasons" do
+ resource = Chef::Resource::Deploy.new("/my/deploy/dir")
+ resource.environment "production"
+ provider = Chef::Provider::Deploy.new(resource, @run_context)
+ provider.instance_variable_get(:@configuration)[:environment].should eql("production")
+ @provider.converge
+ end
+
+ it "shouldn't give a no method error on migrate if the environment is nil" do
+ @provider.stub!(:enforce_ownership)
+ @provider.stub!(:run_symlinks_before_migrate)
+ @provider.stub!(:run_command)
+ @provider.migrate
+ @provider.converge
+ end
+
+ context "using inline recipes for callbacks" do
+
+ it "runs an inline recipe with the provided block for :callback_name == {:recipe => &block} " do
+ snitch = nil
+ recipe_code = Proc.new {snitch = 42}
+ #@provider.should_receive(:instance_eval).with(&recipe_code)
+ @provider.callback(:whateverz, recipe_code)
+ @provider.converge
+ snitch.should == 42
+ end
+
+ it "loads a recipe file from the specified path and from_file evals it" do
+ ::File.should_receive(:exist?).with(@expected_release_dir + "/chefz/foobar_callback.rb").once.and_return(true)
+ ::Dir.should_receive(:chdir).with(@expected_release_dir).and_yield
+ @provider.should_receive(:from_file).with(@expected_release_dir + "/chefz/foobar_callback.rb")
+ @provider.callback(:whateverz, "chefz/foobar_callback.rb")
+ @provider.converge
+ end
+
+ it "instance_evals a block/proc for restart command" do
+ snitch = nil
+ restart_cmd = Proc.new {snitch = 42}
+ @resource.restart(&restart_cmd)
+ @provider.restart
+ @provider.converge
+ snitch.should == 42
+ end
+
+ end
+
+ describe "API bridge to capistrano" do
+ it "defines sudo as a forwarder to execute" do
+ @provider.should_receive(:execute).with("the moon, fool")
+ @provider.sudo("the moon, fool")
+ @provider.converge
+ end
+
+ it "defines run as a forwarder to execute, setting the user, group, cwd and environment to new_resource.user" do
+ mock_execution = mock("Resource::Execute")
+ @provider.should_receive(:execute).with("iGoToHell4this").and_return(mock_execution)
+ @resource.user("notCoolMan")
+ @resource.group("Ggroup")
+ @resource.environment("APP_ENV" => 'staging')
+ @resource.deploy_to("/my/app")
+ mock_execution.should_receive(:user).with("notCoolMan")
+ mock_execution.should_receive(:group).with("Ggroup")
+ mock_execution.should_receive(:cwd){|*args|
+ if args.empty?
+ nil
+ else
+ args.size.should == 1
+ args.first.should == @provider.release_path
+ end
+ }.twice
+ mock_execution.should_receive(:environment){ |*args|
+ if args.empty?
+ nil
+ else
+ args.size.should == 1
+ args.first.should == {"APP_ENV" => "staging"}
+ end
+ }.twice
+ @provider.run("iGoToHell4this")
+ @provider.converge
+ end
+
+ it "defines run as a forwarder to execute, setting cwd and environment but not override" do
+ mock_execution = mock("Resource::Execute")
+ @provider.should_receive(:execute).with("iGoToHell4this").and_return(mock_execution)
+ @resource.user("notCoolMan")
+ mock_execution.should_receive(:user).with("notCoolMan")
+ mock_execution.should_receive(:cwd).with(no_args()).and_return("/some/value")
+ mock_execution.should_receive(:environment).with(no_args()).and_return({})
+ @provider.run("iGoToHell4this")
+ @provider.converge
+ end
+
+
+ it "converts sudo and run to exec resources in hooks" do
+ runner = mock("tehRunner")
+ Chef::Runner.stub!(:new).and_return(runner)
+
+ snitch = nil
+ @resource.user("tehCat")
+
+ callback_code = Proc.new do
+ snitch = 42
+ temp_collection = self.resource_collection
+ run("tehMice")
+ snitch = temp_collection.lookup("execute[tehMice]")
+ end
+
+ runner.should_receive(:converge)
+ #
+ @provider.callback(:phony, callback_code)
+ @provider.converge
+ snitch.should be_an_instance_of(Chef::Resource::Execute)
+ snitch.user.should == "tehCat"
+ end
+ end
+
+ describe "installing gems from a gems.yml" do
+
+ before do
+ ::File.stub!(:exist?).with("#{@expected_release_dir}/gems.yml").and_return(true)
+ @gem_list = [{:name=>"eventmachine", :version=>"0.12.9"}]
+ end
+
+ it "reads a gems.yml file, creating gem providers for each with action :upgrade" do
+ IO.should_receive(:read).with("#{@expected_release_dir}/gems.yml").and_return("cookie")
+ YAML.should_receive(:load).with("cookie").and_return(@gem_list)
+
+ gems = @provider.send(:gem_packages)
+
+ gems.map { |g| g.action }.should == [[:install]]
+ gems.map { |g| g.name }.should == %w{eventmachine}
+ gems.map { |g| g.version }.should == %w{0.12.9}
+ end
+
+ it "takes a list of gem providers converges them" do
+ IO.stub!(:read)
+ YAML.stub!(:load).and_return(@gem_list)
+ expected_gem_resources = @provider.send(:gem_packages).map { |r| [r.name, r.version] }
+ gem_runner = @provider.send(:gem_resource_collection_runner)
+ # no one has heard of defining == to be meaningful so I have use this monstrosity
+ actual = gem_runner.run_context.resource_collection.all_resources.map { |r| [r.name, r.version] }
+ actual.should == expected_gem_resources
+ end
+
+ end
+
+end
diff --git a/spec/unit/provider/directory_spec.rb b/spec/unit/provider/directory_spec.rb
new file mode 100644
index 0000000000..4f297e0115
--- /dev/null
+++ b/spec/unit/provider/directory_spec.rb
@@ -0,0 +1,147 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 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 'ostruct'
+
+require 'spec_helper'
+
+describe Chef::Provider::Directory do
+ before(:each) do
+ @new_resource = Chef::Resource::Directory.new('/tmp')
+ @new_resource.owner(500)
+ @new_resource.group(500)
+ @new_resource.mode(0644)
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @directory = Chef::Provider::Directory.new(@new_resource, @run_context)
+ end
+
+ it "should load the current resource based on the new resource" do
+ File.stub!(:exist?).and_return(true)
+ cstats = mock("stats")
+ cstats.stub!(:uid).and_return(500)
+ cstats.stub!(:gid).and_return(500)
+ cstats.stub!(:mode).and_return(0755)
+ File.should_receive(:stat).twice.and_return(cstats)
+ @directory.load_current_resource
+ @directory.current_resource.path.should eql(@new_resource.path)
+ @directory.current_resource.owner.should eql(500)
+ @directory.current_resource.group.should eql(500)
+ @directory.current_resource.mode.should == 00755
+ end
+
+ it "should create a new directory on create, setting updated to true" do
+ @new_resource.path "/tmp/foo"
+
+ File.should_receive(:exist?).exactly(3).and_return(false)
+ Dir.should_receive(:mkdir).with(@new_resource.path).once.and_return(true)
+
+ @directory.should_receive(:set_all_access_controls)
+ @directory.stub!(:update_new_file_state)
+ @directory.run_action(:create)
+ @directory.new_resource.should be_updated
+ end
+
+ it "should raise an exception if the parent directory does not exist and recursive is false" do
+ @new_resource.path "/tmp/some/dir"
+ @new_resource.recursive false
+ lambda { @directory.run_action(:create) }.should raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+ end
+
+ it "should create a new directory when parent directory does not exist if recursive is true and permissions are correct" do
+ @new_resource.path "/path/to/dir"
+ @new_resource.recursive true
+ File.should_receive(:exist?).with(@new_resource.path).ordered.and_return(false)
+ File.should_receive(:exist?).with(@new_resource.path).ordered.and_return(false)
+
+ File.should_receive(:exist?).with('/path/to').ordered.and_return(false)
+ File.should_receive(:exist?).with('/path').ordered.and_return(true)
+ File.should_receive(:writable?).with('/path').ordered.and_return(true)
+ File.should_receive(:exist?).with(@new_resource.path).ordered.and_return(false)
+
+ FileUtils.should_receive(:mkdir_p).with(@new_resource.path).and_return(true)
+ @directory.should_receive(:set_all_access_controls)
+ @directory.stub!(:update_new_file_state)
+ @directory.run_action(:create)
+ @new_resource.should be_updated
+ end
+
+ # it "should raise an error when creating a directory recursively and permissions do not allow creation" do
+
+ # end
+
+ it "should raise an error when creating a directory when parent directory is a file" do
+ File.should_receive(:directory?).and_return(false)
+ Dir.should_not_receive(:mkdir).with(@new_resource.path)
+ lambda { @directory.run_action(:create) }.should raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+ @directory.new_resource.should_not be_updated
+ end
+
+ it "should not create the directory if it already exists" do
+ stub_file_cstats
+ @new_resource.path "/tmp/foo"
+ File.should_receive(:exist?).exactly(3).and_return(true)
+ Dir.should_not_receive(:mkdir).with(@new_resource.path)
+ @directory.should_receive(:set_all_access_controls)
+ @directory.run_action(:create)
+ end
+
+ it "should delete the directory if it exists, and is writable with action_delete" do
+ File.should_receive(:directory?).and_return(true)
+ File.should_receive(:writable?).once.and_return(true)
+ Dir.should_receive(:delete).with(@new_resource.path).once.and_return(true)
+ @directory.run_action(:delete)
+ end
+
+ it "should raise an exception if it cannot delete the directory due to bad permissions" do
+ File.stub!(:exist?).and_return(true)
+ File.stub!(:writable?).and_return(false)
+ lambda { @directory.run_action(:delete) }.should raise_error(RuntimeError)
+ end
+
+ it "should take no action when deleting a target directory that does not exist" do
+ @new_resource.path "/an/invalid/path"
+ File.stub!(:exist?).and_return(false)
+ Dir.should_not_receive(:delete).with(@new_resource.path)
+ @directory.run_action(:delete)
+ @directory.new_resource.should_not be_updated
+ end
+
+ it "should raise an exception when deleting a directory when target directory is a file" do
+ stub_file_cstats
+ @new_resource.path "/an/invalid/path"
+ File.stub!(:exist?).and_return(true)
+ File.should_receive(:directory?).and_return(false)
+ Dir.should_not_receive(:delete).with(@new_resource.path)
+ lambda { @directory.run_action(:delete) }.should raise_error(RuntimeError)
+ @directory.new_resource.should_not be_updated
+ end
+
+ def stub_file_cstats
+ cstats = mock("stats")
+ cstats.stub!(:uid).and_return(500)
+ cstats.stub!(:gid).and_return(500)
+ cstats.stub!(:mode).and_return(0755)
+ # File.stat is called in:
+ # - Chef::Provider::File.load_current_resource_attrs
+ # - Chef::ScanAccessControl via Chef::Provider::File.setup_acl
+ File.stub!(:stat).and_return(cstats)
+ end
+end
diff --git a/spec/unit/provider/env_spec.rb b/spec/unit/provider/env_spec.rb
new file mode 100644
index 0000000000..77aea42b43
--- /dev/null
+++ b/spec/unit/provider/env_spec.rb
@@ -0,0 +1,232 @@
+#
+# Author:: Doug MacEachern (<dougm@vmware.com>)
+# Copyright:: Copyright (c) 2010 VMware, 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::Env do
+
+ before do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Env.new("FOO")
+ @new_resource.value("bar")
+ @provider = Chef::Provider::Env.new(@new_resource, @run_context)
+ end
+
+ it "assumes the key_name exists by default" do
+ @provider.key_exists.should be_true
+ end
+
+ describe "when loading the current status" do
+ before do
+ #@current_resource = @new_resource.clone
+ #Chef::Resource::Env.stub!(:new).and_return(@current_resource)
+ @provider.current_resource = @current_resource
+ @provider.stub!(:env_value).with("FOO").and_return("bar")
+ @provider.stub!(:env_key_exists).and_return(true)
+ end
+
+ it "should create a current resource with the same name as the new resource" do
+ @provider.load_current_resource
+ @provider.new_resource.name.should == "FOO"
+ end
+
+ it "should set the key_name to the key name of the new resource" do
+ @provider.load_current_resource
+ @provider.current_resource.key_name.should == "FOO"
+ end
+
+ it "should check if the key_name exists" do
+ @provider.should_receive(:env_key_exists).with("FOO").and_return(true)
+ @provider.load_current_resource
+ @provider.key_exists.should be_true
+ end
+
+ it "should flip the value of exists if the key does not exist" do
+ @provider.should_receive(:env_key_exists).with("FOO").and_return(false)
+ @provider.load_current_resource
+ @provider.key_exists.should be_false
+ end
+
+ it "should return the current resource" do
+ @provider.load_current_resource.should be_a_kind_of(Chef::Resource::Env)
+ end
+ end
+
+ describe "action_create" do
+ before do
+ @provider.key_exists = false
+ @provider.stub!(:create_env).and_return(true)
+ @provider.stub!(:modify_env).and_return(true)
+ end
+
+ it "should call create_env if the key does not exist" do
+ @provider.should_receive(:create_env).and_return(true)
+ @provider.action_create
+ end
+
+ it "should set the the new_resources updated flag when it creates the key" do
+ @provider.action_create
+ @new_resource.should be_updated
+ end
+
+ it "should check to see if the values are the same if the key exists" do
+ @provider.key_exists = true
+ @provider.should_receive(:compare_value).and_return(false)
+ @provider.action_create
+ end
+
+ it "should call modify_env if the key exists and values are not equal" do
+ @provider.key_exists = true
+ @provider.stub!(:compare_value).and_return(true)
+ @provider.should_receive(:modify_env).and_return(true)
+ @provider.action_create
+ end
+
+ it "should set the the new_resources updated flag when it updates an existing value" do
+ @provider.key_exists = true
+ @provider.stub!(:compare_value).and_return(true)
+ @provider.stub!(:modify_env).and_return(true)
+ @provider.action_create
+ @new_resource.should be_updated
+ end
+ end
+
+ describe "action_delete" do
+ before(:each) do
+ @provider.current_resource = @current_resource
+ @provider.key_exists = false
+ @provider.stub!(:delete_element).and_return(false)
+ @provider.stub!(:delete_env).and_return(true)
+ end
+
+ it "should not call delete_env if the key does not exist" do
+ @provider.should_not_receive(:delete_env)
+ @provider.action_delete
+ end
+
+ it "should not call delete_element if the key does not exist" do
+ @provider.should_not_receive(:delete_element)
+ @provider.action_delete
+ end
+
+ it "should call delete_env if the key exists" do
+ @provider.key_exists = true
+ @provider.should_receive(:delete_env)
+ @provider.action_delete
+ end
+
+ it "should set the new_resources updated flag to true if the key is deleted" do
+ @provider.key_exists = true
+ @provider.action_delete
+ @new_resource.should be_updated
+ end
+ end
+
+ describe "action_modify" do
+ before(:each) do
+ @provider.current_resource = @current_resource
+ @provider.key_exists = true
+ @provider.stub!(:modify_env).and_return(true)
+ end
+
+ it "should call modify_group if the key exists and values are not equal" do
+ @provider.should_receive(:compare_value).and_return(true)
+ @provider.should_receive(:modify_env).and_return(true)
+ @provider.action_modify
+ end
+
+ it "should set the new resources updated flag to true if modify_env is called" do
+ @provider.stub!(:compare_value).and_return(true)
+ @provider.stub!(:modify_env).and_return(true)
+ @provider.action_modify
+ @new_resource.should be_updated
+ end
+
+ it "should not call modify_env if the key exists but the values are equal" do
+ @provider.should_receive(:compare_value).and_return(false)
+ @provider.should_not_receive(:modify_env)
+ @provider.action_modify
+ end
+
+ it "should raise a Chef::Exceptions::Env if the key doesn't exist" do
+ @provider.key_exists = false
+ lambda { @provider.action_modify }.should raise_error(Chef::Exceptions::Env)
+ end
+ end
+
+ describe "delete_element" do
+ before(:each) do
+ @current_resource = Chef::Resource::Env.new("FOO")
+
+ @new_resource.delim ";"
+ @new_resource.value "C:/bar/bin"
+
+ @current_resource.value "C:/foo/bin;C:/bar/bin"
+ @provider.current_resource = @current_resource
+ end
+
+ it "should return true if the element is not found" do
+ @new_resource.stub!(:value).and_return("C:/baz/bin")
+ @provider.delete_element.should eql(true)
+ end
+
+ it "should return false if the delim not defined" do
+ @new_resource.stub!(:delim).and_return(nil)
+ @provider.delete_element.should eql(false)
+ end
+
+ it "should return true if the element is deleted" do
+ @new_resource.value("C:/foo/bin")
+ @provider.should_receive(:create_env)
+ @provider.delete_element.should eql(true)
+ @new_resource.should be_updated
+ end
+ end
+
+ describe "compare_value" do
+ before(:each) do
+ @new_resource.value("C:/bar")
+ @current_resource = @new_resource.clone
+ @provider.current_resource = @current_resource
+ end
+
+ it "should return false if the values are equal" do
+ @provider.compare_value.should be_false
+ end
+
+ it "should return true if the values not are equal" do
+ @new_resource.value("C:/elsewhere")
+ @provider.compare_value.should be_true
+ end
+
+ it "should return false if the current value contains the element" do
+ @new_resource.delim(";")
+ @current_resource.value("C:/bar;C:/foo;C:/baz")
+
+ @provider.compare_value.should be_false
+ end
+
+ it "should return true if the current value does not contain the element" do
+ @new_resource.delim(";")
+ @current_resource.value("C:/biz;C:/foo/bin;C:/baz")
+ @provider.compare_value.should be_true
+ end
+ end
+end
diff --git a/spec/unit/provider/erl_call_spec.rb b/spec/unit/provider/erl_call_spec.rb
new file mode 100644
index 0000000000..df7910424b
--- /dev/null
+++ b/spec/unit/provider/erl_call_spec.rb
@@ -0,0 +1,88 @@
+#
+# Author:: Joe Williams (<joe@joetify.com>)
+# Copyright:: Copyright (c) 2009 Joe Williams
+# 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::ErlCall do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::ErlCall.new("test", @node)
+ @new_resource.code("io:format(\"burritos\", []).")
+ @new_resource.node_name("chef@localhost")
+ @new_resource.name("test")
+
+ @provider = Chef::Provider::ErlCall.new(@new_resource, @run_context)
+
+ @provider.stub!(:popen4).and_return(@status)
+ @stdin = StringIO.new
+ @stdout = StringIO.new('{ok, woohoo}')
+ @stderr = StringIO.new
+ @pid = 2342999
+ end
+
+ it "should return a Chef::Provider::ErlCall object" do
+ provider = Chef::Provider::ErlCall.new(@new_resource, @run_context)
+ provider.should be_a_kind_of(Chef::Provider::ErlCall)
+ end
+
+ it "should return true" do
+ @provider.load_current_resource.should eql(true)
+ end
+
+ describe "when running a distributed erl call resource" do
+ before do
+ @new_resource.cookie("nomnomnom")
+ @new_resource.distributed(true)
+ @new_resource.name_type("sname")
+ end
+
+ it "should write to stdin of the erl_call command" do
+ expected_cmd = "erl_call -e -s -sname chef@localhost -c nomnomnom"
+ @provider.should_receive(:popen4).with(expected_cmd, :waitlast => true).and_return([@pid, @stdin, @stdout, @stderr])
+ Process.should_receive(:wait).with(@pid)
+
+ @provider.action_run
+ @provider.converge
+
+ @stdin.string.should == "#{@new_resource.code}\n"
+ end
+ end
+
+ describe "when running a local erl call resource" do
+ before do
+ @new_resource.cookie(nil)
+ @new_resource.distributed(false)
+ @new_resource.name_type("name")
+ end
+
+ it "should write to stdin of the erl_call command" do
+ @provider.should_receive(:popen4).with("erl_call -e -name chef@localhost ", :waitlast => true).and_return([@pid, @stdin, @stdout, @stderr])
+ Process.should_receive(:wait).with(@pid)
+
+ @provider.action_run
+ @provider.converge
+
+ @stdin.string.should == "#{@new_resource.code}\n"
+ end
+ end
+
+end
+
diff --git a/spec/unit/provider/execute_spec.rb b/spec/unit/provider/execute_spec.rb
new file mode 100644
index 0000000000..a944793a89
--- /dev/null
+++ b/spec/unit/provider/execute_spec.rb
@@ -0,0 +1,63 @@
+#
+# Author:: Prajakta Purohit (<prajakta@opscode.com>)
+# Copyright:: Copyright (c) 2008 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+#require 'spec_helper'
+
+describe Chef::Provider::Execute do
+ before do
+ @node = Chef::Node.new
+ @cookbook_collection = Chef::CookbookCollection.new([])
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+ @new_resource = Chef::Resource::Execute.new("foo_resource", @run_context)
+ @new_resource.timeout 3600
+ @new_resource.returns 0
+ @new_resource.creates "foo_resource"
+ @provider = Chef::Provider::Execute.new(@new_resource, @run_context)
+ @current_resource = Chef::Resource::Ifconfig.new("foo_resource", @run_context)
+ @provider.current_resource = @current_resource
+ Chef::Log.level = :info
+ # FIXME: There should be a test for how STDOUT.tty? changes the live_stream option being passed
+ STDOUT.stub!(:tty?).and_return(true)
+ end
+
+
+ it "should execute foo_resource" do
+ @provider.stub!(:load_current_resource)
+ opts = {}
+ opts[:timeout] = @new_resource.timeout
+ opts[:returns] = @new_resource.returns
+ opts[:log_level] = :info
+ opts[:log_tag] = @new_resource.to_s
+ opts[:live_stream] = STDOUT
+ @provider.should_receive(:shell_out!).with(@new_resource.command, opts)
+
+ @provider.run_action(:run)
+ @new_resource.should be_updated
+ end
+
+ it "should do nothing if the sentinel file exists" do
+ @provider.stub!(:load_current_resource)
+ File.should_receive(:exists?).with(@new_resource.creates).and_return(true)
+ @provider.should_not_receive(:shell_out!)
+
+ @provider.run_action(:run)
+ @new_resource.should_not be_updated
+ end
+end
+
diff --git a/spec/unit/provider/file_spec.rb b/spec/unit/provider/file_spec.rb
new file mode 100644
index 0000000000..13b79e4bd6
--- /dev/null
+++ b/spec/unit/provider/file_spec.rb
@@ -0,0 +1,498 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 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 'tmpdir'
+
+describe Chef::Provider::File do
+ before(:each) do
+ @node = Chef::Node.new
+ @node.name "latte"
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @resource = Chef::Resource::File.new("seattle")
+ @resource.path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates", "seattle.txt")))
+
+ @provider = Chef::Provider::File.new(@resource, @run_context)
+ end
+
+ it "should return a Chef::Provider::File" do
+ @provider.should be_a_kind_of(Chef::Provider::File)
+ end
+
+ it "should store the resource passed to new as new_resource" do
+ @provider.new_resource.should eql(@resource)
+ end
+
+ it "should store the node passed to new as node" do
+ @provider.node.should eql(@node)
+ end
+
+ it "should load a current resource based on the one specified at construction" do
+ @provider.load_current_resource
+ @provider.current_resource.should be_a_kind_of(Chef::Resource::File)
+ @provider.current_resource.name.should eql(@resource.name)
+ @provider.current_resource.path.should eql(@resource.path)
+ @provider.current_resource.content.should eql(nil)
+ end
+
+ it "should collect the current state of the file on the filesystem and populate current_resource" do
+ # test setup
+ stat_struct = mock("::File.stat", :mode => 0600, :uid => 0, :gid => 0, :mtime => 10000)
+ ::File.should_receive(:stat).exactly(3).with(@resource.path).and_return(stat_struct)
+
+ # test execution
+ @provider.load_current_resource
+
+ # post-condition checks
+ @provider.current_resource.mode.should == 0600
+ @provider.current_resource.owner.should == 0
+ @provider.current_resource.group.should == 0
+ end
+
+ it "should NOT update the new_resource state with the current_resourse state if new_resource state is already specified" do
+ # test setup
+ stat_struct = mock("::File.stat", :mode => 0600, :uid => 0, :gid => 0, :mtime => 10000)
+ ::File.should_receive(:stat).exactly(3).with(@resource.path).and_return(stat_struct)
+
+ @provider.new_resource.group(1)
+ @provider.new_resource.owner(1)
+ @provider.new_resource.mode(0644)
+
+ # test execution
+ @provider.load_current_resource
+
+ # post-condition checks
+ @provider.new_resource.group.should == 1
+ @provider.new_resource.owner.should == 1
+ @provider.new_resource.mode.should == 0644
+ end
+
+ it "should update the new_resource state with the current_resource state if the new_resource state is not specified." do
+ # test setup
+ stat_struct = mock("::File.stat", :mode => 0600, :uid => 0, :gid => 0, :mtime => 10000)
+ ::File.should_receive(:stat).exactly(3).with(@resource.path).and_return(stat_struct)
+
+ @provider.new_resource.group(nil)
+ @provider.new_resource.owner(nil)
+ @provider.new_resource.mode(nil)
+
+ # test execution
+ @provider.load_current_resource
+
+ # post-condition checks
+ @provider.new_resource.group.should eql(@provider.current_resource.group)
+ @provider.new_resource.owner.should eql(@provider.current_resource.owner)
+ @provider.new_resource.mode.should eql(@provider.current_resource.mode)
+ end
+
+ it "should update the new_resource when attempting to set the new state" do
+ # test setup
+ stat_struct = mock("::File.stat", :mode => 0600, :uid => 0, :gid => 0, :mtime => 10000)
+ # called once in update_new_file_state and once in checksum
+ ::File.should_receive(:stat).twice.with(@provider.new_resource.path).and_return(stat_struct)
+ ::File.should_receive(:directory?).once.with(@provider.new_resource.path).and_return(false)
+
+ @provider.new_resource.group(nil)
+ @provider.new_resource.owner(nil)
+ @provider.new_resource.mode(nil)
+
+ # test exectution
+ @provider.update_new_file_state
+
+ # post-condition checks
+ @provider.new_resource.group.should == 0
+ @provider.new_resource.owner.should == 0
+ @provider.new_resource.mode.should == 0600
+end
+
+ it "should load a mostly blank current resource if the file specified in new_resource doesn't exist/isn't readable" do
+ resource = Chef::Resource::File.new("seattle")
+ resource.path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates", "woot.txt")))
+ node = Chef::Node.new
+ node.name "latte"
+ provider = Chef::Provider::File.new(resource, @run_context)
+ provider.load_current_resource
+ provider.current_resource.should be_a_kind_of(Chef::Resource::File)
+ provider.current_resource.name.should eql(resource.name)
+ provider.current_resource.path.should eql(resource.path)
+ end
+
+ it "should not backup symbolic links on delete" do
+ path = File.expand_path(File.join(CHEF_SPEC_DATA, "detroit.txt"))
+ ::File.open(path, "w") do |file|
+ file.write("Detroit's not so nice, so you should come to Seattle instead and buy me a beer instead.")
+ end
+ @resource = Chef::Resource::File.new("detroit")
+ @resource.path(path)
+ @node = Chef::Node.new
+ @node.name "latte"
+ @provider = Chef::Provider::File.new(@resource, @run_context)
+
+ ::File.stub!(:symlink?).and_return(true)
+ @provider.should_not_receive(:backup)
+ @provider.run_action(:delete)
+ @resource.should be_updated_by_last_action
+ end
+
+ it "should compare the current content with the requested content" do
+ @provider.load_current_resource
+
+ @provider.new_resource.content "foobar"
+ @provider.compare_content.should eql(false)
+
+ @provider.new_resource.content IO.read(@resource.path)
+ @provider.compare_content.should eql(true)
+ end
+
+ it "should set the content of the file to the requested content" do
+ io = StringIO.new
+ @provider.load_current_resource
+ @provider.new_resource.content "foobar"
+ @provider.should_receive(:diff_current_from_content).and_return("")
+ @provider.should_receive(:backup)
+ File.should_receive(:open).with(@provider.new_resource.path, "w").and_yield(io)
+ @provider.set_content
+ lambda { @provider.send(:converge_actions).converge! }.should_not raise_error
+ io.string.should == "foobar"
+ end
+
+ it "should not set the content of the file if it already matches the requested content" do
+ @provider.load_current_resource
+ @provider.new_resource.content IO.read(@resource.path)
+ File.stub!(:open).and_return(1)
+ File.should_not_receive(:open).with(@provider.new_resource.path, "w")
+ lambda { @provider.set_content }.should_not raise_error
+ @resource.should_not be_updated_by_last_action
+ end
+
+ it "should create the file if it is missing, then set the attributes on action_create" do
+ @provider.load_current_resource
+ @provider.stub!(:update_new_file_state)
+ @provider.new_resource.stub!(:path).and_return(File.join(Dir.tmpdir, "monkeyfoo"))
+ @provider.access_controls.should_receive(:set_all)
+ @provider.should_receive(:diff_current_from_content).and_return("")
+ File.stub!(:open).and_return(1)
+ #File.should_receive(:directory?).with("/tmp").and_return(true)
+ File.should_receive(:open).with(@provider.new_resource.path, "w+")
+ @provider.run_action(:create)
+ @resource.should be_updated_by_last_action
+ end
+
+ it "should create the file with the proper content if it is missing, then set attributes on action_create" do
+ io = StringIO.new
+ @provider.load_current_resource
+ @provider.new_resource.content "foobar"
+ @provider.new_resource.stub!(:path).and_return(File.join(Dir.tmpdir, "monkeyfoo"))
+ @provider.should_receive(:diff_current_from_content).and_return("")
+ @provider.stub!(:update_new_file_state)
+ File.should_receive(:open).with(@provider.new_resource.path, "w+").and_yield(io)
+ @provider.access_controls.should_receive(:set_all)
+ @provider.run_action(:create)
+ io.string.should == "foobar"
+ @resource.should be_updated_by_last_action
+ end
+
+ it "should delete the file if it exists and is writable on action_delete" do
+ @provider.new_resource.stub!(:path).and_return(File.join(Dir.tmpdir, "monkeyfoo"))
+ @provider.stub!(:backup).and_return(true)
+ File.should_receive("exists?").exactly(2).times.with(@provider.new_resource.path).and_return(true)
+ File.should_receive("writable?").with(@provider.new_resource.path).and_return(true)
+ File.should_receive(:delete).with(@provider.new_resource.path).and_return(true)
+ @provider.run_action(:delete)
+ @resource.should be_updated_by_last_action
+ end
+
+ it "should not raise an error if it cannot delete the file because it does not exist" do
+ @provider.new_resource.stub!(:path).and_return(File.join(Dir.tmpdir, "monkeyfoo"))
+ @provider.stub!(:backup).and_return(true)
+ File.should_receive("exists?").exactly(2).times.with(@provider.new_resource.path).and_return(false)
+ lambda { @provider.run_action(:delete) }.should_not raise_error()
+ @resource.should_not be_updated_by_last_action
+ end
+
+ it "should update the atime/mtime on action_touch" do
+ @provider.load_current_resource
+ @provider.new_resource.stub!(:path).and_return(File.join(Dir.tmpdir, "monkeyfoo"))
+ @provider.should_receive(:diff_current_from_content).and_return("")
+ @provider.stub!(:update_new_file_state)
+ File.should_receive(:utime).once.and_return(1)
+ File.stub!(:open).and_return(1)
+ @provider.access_controls.should_receive(:set_all).once
+ @provider.run_action(:touch)
+ @resource.should be_updated_by_last_action
+ end
+
+ it "should keep 1 backup copy if specified" do
+ @provider.load_current_resource
+ @provider.new_resource.stub!(:path).and_return("/tmp/s-20080705111233")
+ @provider.new_resource.stub!(:backup).and_return(1)
+ Dir.stub!(:[]).and_return([ "/tmp/s-20080705111233", "/tmp/s-20080705111232", "/tmp/s-20080705111223"])
+ FileUtils.should_receive(:rm).with("/tmp/s-20080705111223").once.and_return(true)
+ FileUtils.should_receive(:rm).with("/tmp/s-20080705111232").once.and_return(true)
+ FileUtils.stub!(:cp).and_return(true)
+ FileUtils.stub!(:mkdir_p).and_return(true)
+ File.stub!(:exist?).and_return(true)
+ @provider.backup
+ end
+
+ it "should backup a file no more than :backup times" do
+ @provider.load_current_resource
+ @provider.new_resource.stub!(:path).and_return("/tmp/s-20080705111233")
+ @provider.new_resource.stub!(:backup).and_return(2)
+ Dir.stub!(:[]).and_return([ "/tmp/s-20080705111233", "/tmp/s-20080705111232", "/tmp/s-20080705111223"])
+ FileUtils.should_receive(:rm).with("/tmp/s-20080705111223").once.and_return(true)
+ FileUtils.stub!(:cp).and_return(true)
+ FileUtils.stub!(:mkdir_p).and_return(true)
+ File.stub!(:exist?).and_return(true)
+ @provider.backup
+ end
+
+ it "should not attempt to backup a file if :backup == 0" do
+ @provider.load_current_resource
+ @provider.new_resource.stub!(:path).and_return("/tmp/s-20080705111233")
+ @provider.new_resource.stub!(:backup).and_return(0)
+ FileUtils.stub!(:cp).and_return(true)
+ File.stub!(:exist?).and_return(true)
+ FileUtils.should_not_receive(:cp)
+ @provider.backup
+ end
+
+ it "should put the backup backup file in the directory specified by Chef::Config[:file_backup_path]" do
+ @provider.load_current_resource
+ @provider.new_resource.stub!(:path).and_return("/tmp/s-20080705111233")
+ @provider.new_resource.stub!(:backup).and_return(1)
+ Chef::Config.stub!(:[]).with(:file_backup_path).and_return("/some_prefix")
+ Dir.stub!(:[]).and_return([ "/some_prefix/tmp/s-20080705111233", "/some_prefix/tmp/s-20080705111232", "/some_prefix/tmp/s-20080705111223"])
+ FileUtils.should_receive(:mkdir_p).with("/some_prefix/tmp").once
+ FileUtils.should_receive(:rm).with("/some_prefix/tmp/s-20080705111232").once.and_return(true)
+ FileUtils.should_receive(:rm).with("/some_prefix/tmp/s-20080705111223").once.and_return(true)
+ FileUtils.stub!(:cp).and_return(true)
+ File.stub!(:exist?).and_return(true)
+ @provider.backup
+ end
+
+ it "should strip the drive letter from the backup resource path (for Windows platforms)" do
+ @provider.load_current_resource
+ @provider.new_resource.stub!(:path).and_return("C:/tmp/s-20080705111233")
+ @provider.new_resource.stub!(:backup).and_return(1)
+ Chef::Config.stub!(:[]).with(:file_backup_path).and_return("C:/some_prefix")
+ Dir.stub!(:[]).and_return([ "C:/some_prefix/tmp/s-20080705111233", "C:/some_prefix/tmp/s-20080705111232", "C:/some_prefix/tmp/s-20080705111223"])
+ FileUtils.should_receive(:mkdir_p).with("C:/some_prefix/tmp").once
+ FileUtils.should_receive(:rm).with("C:/some_prefix/tmp/s-20080705111232").once.and_return(true)
+ FileUtils.should_receive(:rm).with("C:/some_prefix/tmp/s-20080705111223").once.and_return(true)
+ FileUtils.stub!(:cp).and_return(true)
+ File.stub!(:exist?).and_return(true)
+ @provider.backup
+ end
+
+ it "should keep the same ownership on backed up files" do
+ @provider.load_current_resource
+ @provider.new_resource.stub!(:path).and_return("/tmp/s-20080705111233")
+ @provider.new_resource.stub!(:backup).and_return(1)
+ Chef::Config.stub!(:[]).with(:file_backup_path).and_return("/some_prefix")
+ Dir.stub!(:[]).and_return([ "/some_prefix/tmp/s-20080705111233", "/some_prefix/tmp/s-20080705111232", "/some_prefix/tmp/s-20080705111223"])
+ FileUtils.stub!(:mkdir_p).and_return(true)
+ FileUtils.stub!(:rm).and_return(true)
+ File.stub!(:exist?).and_return(true)
+ Time.stub!(:now).and_return(Time.at(1272147455).getgm)
+ FileUtils.should_receive(:cp).with("/tmp/s-20080705111233", "/some_prefix/tmp/s-20080705111233.chef-20100424221735", {:preserve => true}).and_return(true)
+ @provider.backup
+ end
+
+ describe "when the enclosing directory does not exist" do
+ before do
+ @resource.path("/tmp/no-such-path/file.txt")
+ end
+
+ it "raises a specific error describing the problem" do
+ lambda {@provider.run_action(:create)}.should raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+ end
+ end
+
+ describe "when creating a file which may be missing" do
+ it "should not call action create if the file exists" do
+ @resource.path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates", "seattle.txt")))
+ @provider = Chef::Provider::File.new(@resource, @run_context)
+ File.should_not_receive(:open)
+ @provider.run_action(:create_if_missing)
+ @resource.should_not be_updated_by_last_action
+ end
+
+ it "should call action create if the does not file exist" do
+ @resource.path("/tmp/non_existant_file")
+ @provider = Chef::Provider::File.new(@resource, @run_context)
+ @provider.should_receive(:diff_current_from_content).and_return("")
+ ::File.stub!(:exists?).with(@resource.path).and_return(false)
+ @provider.stub!(:update_new_file_state)
+ io = StringIO.new
+ File.should_receive(:open).with(@provider.new_resource.path, "w+").and_yield(io)
+ #@provider.should_receive(:action_create).and_return(true)
+ @provider.run_action(:create_if_missing)
+ @resource.should be_updated_by_last_action
+ end
+ end
+
+ describe "when a diff is requested" do
+
+ before(:each) do
+ @original_config = Chef::Config.hash_dup
+ end
+
+ after(:each) do
+ Chef::Config.configuration = @original_config if @original_config
+ end
+
+ describe "when identifying files as binary or text" do
+
+ it "should identify zero-length files as text" do
+ Tempfile.open("some-temp") do |file|
+ @resource.path(file.path)
+ @provider = Chef::Provider::File.new(@resource, @run_context)
+ @provider.is_binary?(file.path).should be_false
+ end
+ end
+
+ it "should correctly identify text files as being text" do
+ Tempfile.open("some-temp") do |file|
+ @resource.path(file.path)
+ file.puts("This is a text file.")
+ file.puts("That has a couple of lines in it.")
+ file.puts("And lets make sure that other printable chars work too: ~!@\#$%^&*()`:\"<>?{}|_+,./;'[]\\-=")
+ file.close
+ @provider = Chef::Provider::File.new(@resource, @run_context)
+ @provider.is_binary?(file.path).should be_false
+ end
+ end
+
+ it "should identify a null-terminated string as binary" do
+ Tempfile.open("some-temp") do |file|
+ @resource.path(file.path)
+ file.write("This is a binary file.\0")
+ file.close
+ @provider = Chef::Provider::File.new(@resource, @run_context)
+ @provider.is_binary?(file.path).should be_true
+ end
+ end
+
+ end
+
+ it "should not return diff output when chef config has disabled it" do
+ Chef::Config[:diff_disabled] = true
+ Tempfile.open("some-temp") do |file|
+ @resource.path(file.path)
+ @provider = Chef::Provider::File.new(@resource, @run_context)
+ @provider.load_current_resource
+ result = @provider.diff_current_from_content "foo baz"
+ result.should == [ "(diff output suppressed by config)" ]
+ @resource.diff.should be_nil
+ end
+ end
+
+ it "should not return diff output when there is no new file to compare it to" do
+ Tempfile.open("some-temp") do |file|
+ Tempfile.open("other-temp") do |missing_file|
+ missing_path = missing_file.path
+ missing_file.close
+ missing_file.unlink
+ @resource.path(file.path)
+ @provider = Chef::Provider::File.new(@resource, @run_context)
+ @provider.load_current_resource
+ result = @provider.diff_current missing_path
+ result.should == [ "(no temp file with new content, diff output suppressed)" ]
+ @resource.diff.should be_nil
+ end
+ end
+ end
+
+ it "should produce diff output when the file does not exist yet, but suppress reporting it" do
+ Tempfile.open("some-temp") do |file|
+ @resource.path(file.path)
+ file.close
+ file.unlink
+ @provider = Chef::Provider::File.new(@resource, @run_context)
+ @provider.load_current_resource
+ result = @provider.diff_current_from_content "foo baz"
+ result.length.should == 4
+ @resource.diff.should be_nil
+ end
+ end
+
+ it "should not produce a diff when the current resource file is above the filesize threshold" do
+ Chef::Config[:diff_filesize_threshold] = 5
+ Tempfile.open("some-temp") do |file|
+ @resource.path(file.path)
+ file.puts("this is a line which is longer than 5 characters")
+ file.flush
+ @provider = Chef::Provider::File.new(@resource, @run_context)
+ @provider.load_current_resource
+ result = @provider.diff_current_from_content "foo" # not longer than 5
+ result.should == [ "(file sizes exceed 5 bytes, diff output suppressed)" ]
+ @resource.diff.should be_nil
+ end
+ end
+
+ it "should not produce a diff when the new content is above the filesize threshold" do
+ Chef::Config[:diff_filesize_threshold] = 5
+ Tempfile.open("some-temp") do |file|
+ @resource.path(file.path)
+ file.puts("foo")
+ file.flush
+ @provider = Chef::Provider::File.new(@resource, @run_context)
+ @provider.load_current_resource
+ result = @provider.diff_current_from_content "this is a line that is longer than 5 characters"
+ result.should == [ "(file sizes exceed 5 bytes, diff output suppressed)" ]
+ @resource.diff.should be_nil
+ end
+ end
+
+ it "should not produce a diff when the generated diff size is above the diff size threshold" do
+ Chef::Config[:diff_output_threshold] = 5
+ Tempfile.open("some-temp") do |file|
+ @resource.path(file.path)
+ file.puts("some text to increase the size of the diff")
+ file.flush
+ @provider = Chef::Provider::File.new(@resource, @run_context)
+ @provider.load_current_resource
+ result = @provider.diff_current_from_content "this is a line that is longer than 5 characters"
+ result.should == [ "(long diff of over 5 characters, diff output suppressed)" ]
+ @resource.diff.should be_nil
+ end
+ end
+
+ it "should return valid diff output when content does not match the string content provided" do
+ Tempfile.open("some-temp") do |file|
+ @resource.path file.path
+ @provider = Chef::Provider::File.new(@resource, @run_context)
+ @provider.load_current_resource
+ result = @provider.diff_current_from_content "foo baz"
+ # remove the file name info which varies.
+ result.shift(2)
+ # Result appearance seems to vary slightly under solaris diff
+ # So we'll compare the second line which is common to both.
+ # Solaris: -1,1 +1,0 @@, "+foo baz"
+ # Linux/Mac: -1,0, +1 @@, "+foo baz"
+ result.length.should == 2
+ result[1].should == "+foo baz"
+ @resource.diff.should_not be_nil
+ end
+ end
+ end
+end
diff --git a/spec/unit/provider/git_spec.rb b/spec/unit/provider/git_spec.rb
new file mode 100644
index 0000000000..bb8208dc27
--- /dev/null
+++ b/spec/unit/provider/git_spec.rb
@@ -0,0 +1,352 @@
+#
+# Author:: Daniel DeLeo (<dan@kallistec.com>)
+# Copyright:: Copyright (c) 2008 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'
+describe Chef::Provider::Git do
+
+ before(:each) do
+ STDOUT.stub!(:tty?).and_return(true)
+ Chef::Log.level = :info
+
+ @current_resource = Chef::Resource::Git.new("web2.0 app")
+ @current_resource.revision("d35af14d41ae22b19da05d7d03a0bafc321b244c")
+
+ @resource = Chef::Resource::Git.new("web2.0 app")
+ @resource.repository "git://github.com/opscode/chef.git"
+ @resource.destination "/my/deploy/dir"
+ @resource.revision "d35af14d41ae22b19da05d7d03a0bafc321b244c"
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @provider = Chef::Provider::Git.new(@resource, @run_context)
+ @provider.current_resource = @current_resource
+ end
+
+ context "determining the revision of the currently deployed checkout" do
+
+ before do
+ @stdout = mock("standard out")
+ @stderr = mock("standard error")
+ @exitstatus = mock("exitstatus")
+ end
+
+ it "sets the current revision to nil if the deploy dir does not exist" do
+ ::File.should_receive(:exist?).with("/my/deploy/dir/.git").and_return(false)
+ @provider.find_current_revision.should be_nil
+ end
+
+ it "determines the current revision when there is one" do
+ ::File.should_receive(:exist?).with("/my/deploy/dir/.git").and_return(true)
+ @stdout = "9b4d8dc38dd471246e7cfb1c3c1ad14b0f2bee13\n"
+ @provider.should_receive(:shell_out!).with('git rev-parse HEAD', {:cwd => '/my/deploy/dir', :returns => [0,128]}).and_return(mock("ShellOut result", :stdout => @stdout))
+ @provider.find_current_revision.should eql("9b4d8dc38dd471246e7cfb1c3c1ad14b0f2bee13")
+ end
+
+ it "gives the current revision as nil when there is no current revision" do
+ ::File.should_receive(:exist?).with("/my/deploy/dir/.git").and_return(true)
+ @stderr = "fatal: Not a git repository (or any of the parent directories): .git"
+ @stdout = ""
+ @provider.should_receive(:shell_out!).with('git rev-parse HEAD', :cwd => '/my/deploy/dir', :returns => [0,128]).and_return(mock("ShellOut result", :stdout => "", :stderr => @stderr))
+ @provider.find_current_revision.should be_nil
+ end
+ end
+
+ it "creates a current_resource with the currently deployed revision when a clone exists in the destination dir" do
+ @provider.stub!(:find_current_revision).and_return("681c9802d1c62a45b490786c18f0b8216b309440")
+ @provider.load_current_resource
+ @provider.current_resource.name.should eql(@resource.name)
+ @provider.current_resource.revision.should eql("681c9802d1c62a45b490786c18f0b8216b309440")
+ end
+
+ it "keeps the node and resource passed to it on initialize" do
+ @provider.node.should equal(@node)
+ @provider.new_resource.should equal(@resource)
+ end
+
+ context "resolving revisions to a SHA" do
+
+ before do
+ @git_ls_remote = "git ls-remote git://github.com/opscode/chef.git "
+ end
+
+ it "returns resource.revision as is if revision is already a full SHA" do
+ @provider.target_revision.should eql("d35af14d41ae22b19da05d7d03a0bafc321b244c")
+ end
+
+ it "converts resource.revision from a tag to a SHA" do
+ @resource.revision "v1.0"
+ @stdout = "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n"
+ @provider.should_receive(:shell_out!).with(@git_ls_remote + "v1.0", {:log_tag=>"git[web2.0 app]", :log_level=>:debug}).and_return(mock("ShellOut result", :stdout => @stdout))
+ @provider.target_revision.should eql("503c22a5e41f5ae3193460cca044ed1435029f53")
+ end
+
+ it "raises an invalid remote reference error if you try to deploy from ``origin'' and assertions are run" do
+ @resource.revision "origin/"
+ @provider.action = :checkout
+ @provider.define_resource_requirements
+ ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+ lambda {@provider.process_resource_requirements}.should raise_error(Chef::Exceptions::InvalidRemoteGitReference)
+ end
+
+ it "raises an unresolvable git reference error if the revision can't be resolved to any revision and assertions are run" do
+ @resource.revision "FAIL, that's the revision I want"
+ @provider.action = :checkout
+ @provider.should_receive(:shell_out!).and_return(mock("ShellOut result", :stdout => "\n"))
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::UnresolvableGitReference)
+ end
+
+ it "does not raise an error if the revision can't be resolved when assertions are not run" do
+ @resource.revision "FAIL, that's the revision I want"
+ @provider.should_receive(:shell_out!).and_return(mock("ShellOut result", :stdout => "\n"))
+ @provider.target_revision.should == nil
+ end
+
+ it "does not raise an error when the revision is valid and assertions are run." do
+ @resource.revision "v1.0"
+ @stdout = "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n"
+ @provider.should_receive(:shell_out!).with(@git_ls_remote + "v1.0", {:log_tag=>"git[web2.0 app]", :log_level=>:debug}).and_return(mock("ShellOut result", :stdout => @stdout))
+ @provider.action = :checkout
+ ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should_not raise_error(RuntimeError)
+ end
+
+ it "gives the latest HEAD revision SHA if nothing is specified" do
+ @stdout =<<-SHAS
+28af684d8460ba4793eda3e7ac238c864a5d029a\tHEAD
+503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha
+28af684d8460ba4793eda3e7ac238c864a5d029a\trefs/heads/master
+c44fe79bb5e36941ce799cee6b9de3a2ef89afee\trefs/tags/0.5.2
+14534f0e0bf133dc9ff6dbe74f8a0c863ff3ac6d\trefs/tags/0.5.4
+d36fddb4291341a1ff2ecc3c560494e398881354\trefs/tags/0.5.6
+9e5ce9031cbee81015de680d010b603bce2dd15f\trefs/tags/0.6.0
+9b4d8dc38dd471246e7cfb1c3c1ad14b0f2bee13\trefs/tags/0.6.2
+014a69af1cdce619de82afaf6cdb4e6ac658fede\trefs/tags/0.7.0
+fa8097ff666af3ce64761d8e1f1c2aa292a11378\trefs/tags/0.7.2
+44f9be0b33ba5c10027ddb030a5b2f0faa3eeb8d\trefs/tags/0.7.4
+d7b9957f67236fa54e660cc3ab45ffecd6e0ba38\trefs/tags/0.7.8
+b7d19519a1c15f1c1a324e2683bd728b6198ce5a\trefs/tags/0.7.8^{}
+ebc1b392fe7e8f0fbabc305c299b4d365d2b4d9b\trefs/tags/chef-server-package
+SHAS
+ @resource.revision ''
+ @provider.should_receive(:shell_out!).with(@git_ls_remote, {:log_tag=>"git[web2.0 app]", :log_level=>:debug}).and_return(mock("ShellOut result", :stdout => @stdout))
+ @provider.target_revision.should eql("28af684d8460ba4793eda3e7ac238c864a5d029a")
+ end
+ end
+
+ it "responds to :revision_slug as an alias for target_revision" do
+ @provider.should respond_to(:revision_slug)
+ end
+
+ it "runs a clone command with default git options" do
+ @resource.user "deployNinja"
+ @resource.ssh_wrapper "do_it_this_way.sh"
+ expected_cmd = "git clone git://github.com/opscode/chef.git /my/deploy/dir"
+ @provider.should_receive(:shell_out!).with(expected_cmd, :user => "deployNinja",
+ :environment =>{"GIT_SSH"=>"do_it_this_way.sh"}, :log_level => :info, :log_tag => "git[web2.0 app]", :live_stream => STDOUT)
+
+ @provider.clone
+ @provider.converge
+ end
+
+ it "runs a clone command with escaped destination" do
+ @resource.user "deployNinja"
+ @resource.destination "/Application Support/with/space"
+ @resource.ssh_wrapper "do_it_this_way.sh"
+ expected_cmd = "git clone git://github.com/opscode/chef.git /Application\\ Support/with/space"
+ @provider.should_receive(:shell_out!).with(expected_cmd, :user => "deployNinja",
+ :environment =>{"GIT_SSH"=>"do_it_this_way.sh"}, :log_level => :info, :log_tag => "git[web2.0 app]", :live_stream => STDOUT)
+ @provider.clone
+ @provider.converge
+ end
+
+ it "compiles a clone command using --depth for shallow cloning" do
+ @resource.depth 5
+ expected_cmd = 'git clone --depth 5 git://github.com/opscode/chef.git /my/deploy/dir'
+ @provider.should_receive(:shell_out!).with(expected_cmd, {:log_level => :info, :log_tag => "git[web2.0 app]", :live_stream => STDOUT})
+ @provider.clone
+ @provider.converge
+ end
+
+ it "compiles a clone command with a remote other than ``origin''" do
+ @resource.remote "opscode"
+ expected_cmd = 'git clone -o opscode git://github.com/opscode/chef.git /my/deploy/dir'
+ @provider.should_receive(:shell_out!).with(expected_cmd, {:log_level => :info, :log_tag => "git[web2.0 app]", :live_stream => STDOUT})
+ @provider.clone
+ @provider.converge
+ end
+
+ it "runs a checkout command with default options" do
+ expected_cmd = 'git checkout -b deploy d35af14d41ae22b19da05d7d03a0bafc321b244c'
+ @provider.should_receive(:shell_out!).with(expected_cmd, :cwd => "/my/deploy/dir", :log_level => :debug, :log_tag => "git[web2.0 app]")
+ @provider.checkout
+ @provider.converge
+ end
+
+ it "runs an enable_submodule command" do
+ @resource.enable_submodules true
+ expected_cmd = "git submodule update --init --recursive"
+ @provider.should_receive(:shell_out!).with(expected_cmd, :cwd => "/my/deploy/dir", :log_level => :info, :log_tag => "git[web2.0 app]", :live_stream => STDOUT)
+ @provider.enable_submodules
+ @provider.converge
+ end
+
+ it "does nothing for enable_submodules if resource.enable_submodules #=> false" do
+ @provider.should_not_receive(:shell_out!)
+ @provider.enable_submodules
+ @provider.converge
+ end
+
+ it "runs a sync command with default options" do
+ expected_cmd = "git fetch origin && git fetch origin --tags && git reset --hard d35af14d41ae22b19da05d7d03a0bafc321b244c"
+ @provider.should_receive(:shell_out!).with(expected_cmd, :cwd=> "/my/deploy/dir", :log_level => :debug, :log_tag => "git[web2.0 app]")
+ @provider.fetch_updates
+ @provider.converge
+ end
+
+ it "runs a sync command with the user and group specified in the resource" do
+ @resource.user("whois")
+ @resource.group("thisis")
+ expected_cmd = "git fetch origin && git fetch origin --tags && git reset --hard d35af14d41ae22b19da05d7d03a0bafc321b244c"
+ @provider.should_receive(:shell_out!).with(expected_cmd, :cwd => "/my/deploy/dir",
+ :user => "whois", :group => "thisis", :log_level => :debug, :log_tag => "git[web2.0 app]")
+ @provider.fetch_updates
+ @provider.converge
+ end
+
+ it "configures remote tracking branches when remote is not ``origin''" do
+ @resource.remote "opscode"
+ conf_tracking_branches = "git config remote.opscode.url git://github.com/opscode/chef.git && " +
+ "git config remote.opscode.fetch +refs/heads/*:refs/remotes/opscode/*"
+ @provider.should_receive(:shell_out!).with(conf_tracking_branches, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]", :log_level => :debug)
+ fetch_command = "git fetch opscode && git fetch opscode --tags && git reset --hard d35af14d41ae22b19da05d7d03a0bafc321b244c"
+ @provider.should_receive(:shell_out!).with(fetch_command, :cwd => "/my/deploy/dir", :log_level => :debug, :log_tag => "git[web2.0 app]")
+ @provider.fetch_updates
+ @provider.converge
+ end
+
+ it "raises an error if the git clone command would fail because the enclosing directory doesn't exist" do
+ @provider.stub!(:shell_out!)
+ lambda {@provider.run_action(:sync)}.should raise_error(Chef::Exceptions::MissingParentDirectory)
+ end
+
+ it "does a checkout by cloning the repo and then enabling submodules" do
+ # will be invoked in load_current_resource
+ ::File.stub!(:exist?).with("/my/deploy/dir/.git").and_return(false)
+
+ ::File.stub!(:exist?).with("/my/deploy/dir").and_return(true)
+ ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+ ::Dir.stub!(:entries).with("/my/deploy/dir").and_return(['.','..'])
+ @provider.should_receive(:clone)
+ @provider.should_receive(:checkout)
+ @provider.should_receive(:enable_submodules)
+ @provider.run_action(:checkout)
+ # Even though an actual run will cause an update to occur, the fact that we've stubbed out
+ # the actions above will prevent updates from registering
+ # @resource.should be_updated
+ end
+
+ # REGRESSION TEST: on some OSes, the entries from an empty directory will be listed as
+ # ['..', '.'] but this shouldn't change the behavior
+ it "does a checkout by cloning the repo and then enabling submodules when the directory entries are listed as %w{.. .}" do
+ ::File.stub!(:exist?).with("/my/deploy/dir/.git").and_return(false)
+ ::File.stub!(:exist?).with("/my/deploy/dir").and_return(false)
+ ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+ ::Dir.stub!(:entries).with("/my/deploy/dir").and_return(['..','.'])
+ @provider.should_receive(:clone)
+ @provider.should_receive(:checkout)
+ @provider.should_receive(:enable_submodules)
+ @provider.run_action(:checkout)
+ # @resource.should be_updated
+ end
+
+ it "should not checkout if the destination exists or is a non empty directory" do
+ # will be invoked in load_current_resource
+ ::File.stub!(:exist?).with("/my/deploy/dir/.git").and_return(false)
+
+ ::File.stub!(:exist?).with("/my/deploy/dir").and_return(true)
+ ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+ ::Dir.stub!(:entries).with("/my/deploy/dir").and_return(['.','..','foo','bar'])
+ @provider.should_not_receive(:clone)
+ @provider.should_not_receive(:checkout)
+ @provider.should_not_receive(:enable_submodules)
+ @provider.run_action(:checkout)
+ @resource.should_not be_updated
+ end
+
+ it "syncs the code by updating the source when the repo has already been checked out" do
+ ::File.should_receive(:exist?).with("/my/deploy/dir/.git").and_return(true)
+ ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+ @provider.should_receive(:find_current_revision).exactly(2).and_return('d35af14d41ae22b19da05d7d03a0bafc321b244c')
+ @provider.should_not_receive(:fetch_updates)
+ @provider.run_action(:sync)
+ @resource.should_not be_updated
+ end
+
+ it "marks the resource as updated when the repo is updated and gets a new version" do
+ ::File.should_receive(:exist?).with("/my/deploy/dir/.git").and_return(true)
+ ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+ # invoked twice - first time from load_current_resource
+ @provider.should_receive(:find_current_revision).exactly(2).and_return('d35af14d41ae22b19da05d7d03a0bafc321b244c')
+ @provider.stub!(:target_revision).and_return('28af684d8460ba4793eda3e7ac238c864a5d029a')
+ @provider.should_receive(:fetch_updates)
+ @provider.should_receive(:enable_submodules)
+ @provider.run_action(:sync)
+ # @resource.should be_updated
+ end
+
+ it "does not fetch any updates if the remote revision matches the current revision" do
+ ::File.should_receive(:exist?).with("/my/deploy/dir/.git").and_return(true)
+ ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+ @provider.stub!(:find_current_revision).and_return('d35af14d41ae22b19da05d7d03a0bafc321b244c')
+ @provider.stub!(:target_revision).and_return('d35af14d41ae22b19da05d7d03a0bafc321b244c')
+ @provider.should_not_receive(:fetch_updates)
+ @provider.run_action(:sync)
+ @resource.should_not be_updated
+ end
+
+ it "clones the repo instead of fetching it if the deploy directory doesn't exist" do
+ ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+ ::File.should_receive(:exist?).with("/my/deploy/dir/.git").exactly(2).and_return(false)
+ @provider.should_receive(:action_checkout)
+ @provider.should_not_receive(:shell_out!)
+ @provider.run_action(:sync)
+ # @resource.should be_updated
+ end
+
+ it "clones the repo instead of fetching updates if the deploy directory is empty" do
+ ::File.should_receive(:exist?).with("/my/deploy/dir/.git").exactly(2).and_return(false)
+ ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+ ::File.stub!(:directory?).with("/my/deploy/dir").and_return(true)
+ @provider.stub!(:sync_command).and_return("huzzah!")
+ @provider.should_receive(:action_checkout)
+ @provider.should_not_receive(:shell_out!).with("huzzah!", :cwd => "/my/deploy/dir")
+ @provider.run_action(:sync)
+ #@resource.should be_updated
+ end
+
+ it "does an export by cloning the repo then removing the .git directory" do
+ @provider.should_receive(:action_checkout)
+ FileUtils.should_receive(:rm_rf).with(@resource.destination + "/.git")
+ @provider.run_action(:export)
+ @resource.should be_updated
+ end
+
+end
diff --git a/spec/unit/provider/group/dscl_spec.rb b/spec/unit/provider/group/dscl_spec.rb
new file mode 100644
index 0000000000..b526848dfd
--- /dev/null
+++ b/spec/unit/provider/group/dscl_spec.rb
@@ -0,0 +1,294 @@
+#
+# Author:: Dreamcat4 (<dreamcat4@gmail.com>)
+# Copyright:: Copyright (c) 2009 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'
+
+describe Chef::Provider::Group::Dscl do
+ before do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Group.new("aj")
+ @current_resource = Chef::Resource::Group.new("aj")
+ @provider = Chef::Provider::Group::Dscl.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ @status = mock("Process::Status", :exitstatus => 0)
+ @pid = 2342
+ @stdin = StringIO.new
+ @stdout = StringIO.new("\n")
+ @stderr = StringIO.new("")
+ @provider.stub!(:popen4).and_yield(@pid,@stdin,@stdout,@stderr).and_return(@status)
+ end
+
+ it "should run popen4 with the supplied array of arguments appended to the dscl command" do
+ @provider.should_receive(:popen4).with("dscl . -cmd /Path arg1 arg2")
+ @provider.dscl("cmd", "/Path", "arg1", "arg2")
+ end
+
+ it "should return an array of four elements - cmd, status, stdout, stderr" do
+ dscl_retval = @provider.dscl("cmd /Path args")
+ dscl_retval.should be_a_kind_of(Array)
+ dscl_retval.should == ["dscl . -cmd /Path args",@status,"\n",""]
+ end
+
+ describe "safe_dscl" do
+ before do
+ @node = Chef::Node.new
+ @provider = Chef::Provider::Group::Dscl.new(@node, @new_resource)
+ @provider.stub!(:dscl).and_return(["cmd", @status, "stdout", "stderr"])
+ end
+
+ it "should run dscl with the supplied cmd /Path args" do
+ @provider.should_receive(:dscl).with("cmd /Path args")
+ @provider.safe_dscl("cmd /Path args")
+ end
+
+ describe "with the dscl command returning a non zero exit status for a delete" do
+ before do
+ @status = mock("Process::Status", :exitstatus => 1)
+ @provider.stub!(:dscl).and_return(["cmd", @status, "stdout", "stderr"])
+ end
+
+ it "should return an empty string of standard output for a delete" do
+ safe_dscl_retval = @provider.safe_dscl("delete /Path args")
+ safe_dscl_retval.should be_a_kind_of(String)
+ safe_dscl_retval.should == ""
+ end
+
+ it "should raise an exception for any other command" do
+ lambda { @provider.safe_dscl("cmd /Path arguments") }.should raise_error(Chef::Exceptions::Group)
+ end
+ end
+
+ describe "with the dscl command returning no such key" do
+ before do
+ @provider.stub!(:dscl).and_return(["cmd", @status, "No such key: ", "stderr"])
+ end
+
+ it "should raise an exception" do
+ lambda { @provider.safe_dscl("cmd /Path arguments") }.should raise_error(Chef::Exceptions::Group)
+ end
+ end
+
+ describe "with the dscl command returning a zero exit status" do
+ it "should return the third array element, the string of standard output" do
+ safe_dscl_retval = @provider.safe_dscl("cmd /Path args")
+ safe_dscl_retval.should be_a_kind_of(String)
+ safe_dscl_retval.should == "stdout"
+ end
+ end
+ end
+
+ describe "get_free_gid" do
+ before do
+ @node = Chef::Node.new
+ @provider = Chef::Provider::Group::Dscl.new(@node, @new_resource)
+ @provider.stub!(:safe_dscl).and_return("\naj 200\njt 201\n")
+ end
+
+ it "should run safe_dscl with list /Groups gid" do
+ @provider.should_receive(:safe_dscl).with("list /Groups gid")
+ @provider.get_free_gid
+ end
+
+ it "should return the first unused gid number on or above 200" do
+ @provider.get_free_gid.should equal(202)
+ end
+
+ it "should raise an exception when the search limit is exhausted" do
+ search_limit = 1
+ lambda { @provider.get_free_gid(search_limit) }.should raise_error(RuntimeError)
+ end
+ end
+
+ describe "gid_used?" do
+ before do
+ @node = Chef::Node.new
+ @provider = Chef::Provider::Group::Dscl.new(@node, @new_resource)
+ @provider.stub!(:safe_dscl).and_return("\naj 500\n")
+ end
+
+ it "should run safe_dscl with list /Groups gid" do
+ @provider.should_receive(:safe_dscl).with("list /Groups gid")
+ @provider.gid_used?(500)
+ end
+
+ it "should return true for a used gid number" do
+ @provider.gid_used?(500).should be_true
+ end
+
+ it "should return false for an unused gid number" do
+ @provider.gid_used?(501).should be_false
+ end
+
+ it "should return false if not given any valid gid number" do
+ @provider.gid_used?(nil).should be_false
+ end
+ end
+
+ describe "set_gid" do
+ describe "with the new resource and a gid number which is already in use" do
+ before do
+ @provider.stub!(:gid_used?).and_return(true)
+ end
+
+ it "should raise an exception if the new resources gid is already in use" do
+ lambda { @provider.set_gid }.should raise_error(Chef::Exceptions::Group)
+ end
+ end
+
+ describe "with no gid number for the new resources" do
+ it "should run get_free_gid and return a valid, unused gid number" do
+ @provider.should_receive(:get_free_gid).and_return(501)
+ @provider.set_gid
+ end
+ end
+
+ describe "with blank gid number for the new resources" do
+ before do
+ @new_resource.instance_variable_set(:@gid, nil)
+ @new_resource.stub!(:safe_dscl)
+ end
+
+ it "should run get_free_gid and return a valid, unused gid number" do
+ @provider.should_receive(:get_free_gid).and_return(501)
+ @provider.set_gid
+ end
+ end
+
+ describe "with a valid gid number which is not already in use" do
+ it "should run safe_dscl with create /Groups/group PrimaryGroupID gid" do
+ @provider.stub(:get_free_gid).and_return(50)
+ @provider.should_receive(:safe_dscl).with("list /Groups gid")
+ @provider.should_receive(:safe_dscl).with("create /Groups/aj PrimaryGroupID 50").and_return(true)
+ @provider.set_gid
+ end
+ end
+ end
+
+ describe "set_members" do
+
+ describe "with existing members in the current resource and append set to false in the new resource" do
+ before do
+ @new_resource.stub!(:members).and_return([])
+ @new_resource.stub!(:append).and_return(false)
+ @current_resource.stub!(:members).and_return(["all", "your", "base"])
+ end
+
+ it "should log an appropriate message" do
+ Chef::Log.should_receive(:debug).with("group[aj] removing group members all your base")
+ @provider.set_members
+ end
+
+ it "should run safe_dscl with create /Groups/group GroupMembership to clear the Group's UID list" do
+ @provider.should_receive(:safe_dscl).with("create /Groups/aj GroupMembers ''").and_return(true)
+ @provider.should_receive(:safe_dscl).with("create /Groups/aj GroupMembership ''").and_return(true)
+ @provider.set_members
+ end
+ end
+
+ describe "with supplied members in the new resource" do
+ before do
+ @new_resource.members(["all", "your", "base"])
+ @current_resource.members([])
+ end
+
+ it "should log an appropriate debug message" do
+ Chef::Log.should_receive(:debug).with("group[aj] setting group members all, your, base")
+ @provider.set_members
+ end
+
+ it "should run safe_dscl with append /Groups/group GroupMembership and group members all, your, base" do
+ @provider.should_receive(:safe_dscl).with("create /Groups/aj GroupMembers ''").and_return(true)
+ @provider.should_receive(:safe_dscl).with("append /Groups/aj GroupMembership all your base").and_return(true)
+ @provider.should_receive(:safe_dscl).with("create /Groups/aj GroupMembership ''").and_return(true)
+ @provider.set_members
+ end
+ end
+
+ describe "with no members in the new resource" do
+ before do
+ @new_resource.append(true)
+ @new_resource.members([])
+ end
+
+ it "should not call safe_dscl" do
+ @provider.should_not_receive(:safe_dscl)
+ @provider.set_members
+ end
+ end
+ end
+
+ describe "when loading the current system state" do
+ before (:each) do
+ @provider.load_current_resource
+ @provider.define_resource_requirements
+ end
+ it "raises an error if the required binary /usr/bin/dscl doesn't exist" do
+ File.should_receive(:exists?).with("/usr/bin/dscl").and_return(false)
+
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Group)
+ end
+
+ it "doesn't raise an error if /usr/bin/dscl exists" do
+ File.stub!(:exists?).and_return(true)
+ lambda { @provider.process_resource_requirements }.should_not raise_error(Chef::Exceptions::Group)
+ end
+ end
+
+ describe "when creating the group" do
+ it "creates the group, password field, gid, and sets group membership" do
+ @provider.should_receive(:set_gid).and_return(true)
+ @provider.should_receive(:set_members).and_return(true)
+ @provider.should_receive(:safe_dscl).with("create /Groups/aj Password '*'")
+ @provider.should_receive(:safe_dscl).with("create /Groups/aj")
+ @provider.create_group
+ end
+ end
+
+ describe "managing the group" do
+ it "should manage the group_name if it changed and the new resources group_name is not null" do
+ @current_resource.group_name("oldval")
+ @new_resource.group_name("newname")
+ @provider.should_receive(:safe_dscl).with("create /Groups/newname")
+ @provider.should_receive(:safe_dscl).with("create /Groups/newname Password '*'")
+ @provider.manage_group
+ end
+
+ it "should manage the gid if it changed and the new resources gid is not null" do
+ @current_resource.gid(23)
+ @new_resource.gid(42)
+ @provider.should_receive(:set_gid)
+ @provider.manage_group
+ end
+
+ it "should manage the members if it changed and the new resources members is not null" do
+ @current_resource.members(%{charlie root})
+ @new_resource.members(%{crab revenge})
+ @provider.should_receive(:set_members)
+ @provider.manage_group
+ end
+ end
+
+ describe "remove_group" do
+ it "should run safe_dscl with delete /Groups/group and with the new resources group name" do
+ @provider.should_receive(:safe_dscl).with("delete /Groups/aj").and_return(true)
+ @provider.remove_group
+ end
+ end
+end
diff --git a/spec/unit/provider/group/gpasswd_spec.rb b/spec/unit/provider/group/gpasswd_spec.rb
new file mode 100644
index 0000000000..59da88e851
--- /dev/null
+++ b/spec/unit/provider/group/gpasswd_spec.rb
@@ -0,0 +1,108 @@
+#
+# Author:: AJ Christensen (<aj@opscode.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::Group::Gpasswd, "modify_group_members" do
+ before do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Group.new("wheel")
+ @new_resource.members %w{lobster rage fist}
+ @new_resource.append false
+ @provider = Chef::Provider::Group::Gpasswd.new(@new_resource, @run_context)
+ #@provider.stub!(:run_command).and_return(true)
+ end
+
+ describe "when determining the current group state" do
+ before (:each) do
+ @provider.load_current_resource
+ @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/bin/gpasswd doesn't exist" do
+ File.stub!(:exists?).and_return(true)
+ File.should_receive(:exists?).with("/usr/bin/gpasswd").and_return(false)
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Group)
+ end
+
+ it "shouldn't raise an error if the required binaries exist" do
+ File.stub!(:exists?).and_return(true)
+ lambda { @provider.process_resource_requirements }.should_not raise_error(Chef::Exceptions::Group)
+ end
+ end
+
+ describe "after the group's current state is known" do
+ before do
+ @current_resource = @new_resource.dup
+ @provider.current_resource = @new_resource
+ end
+
+ describe "when no group members are specified and append is not set" do
+ before do
+ @new_resource.append(false)
+ @new_resource.members([])
+ end
+
+ it "logs a message and sets group's members to 'none'" do
+ Chef::Log.should_receive(:debug).with("group[wheel] setting group members to: none")
+ @provider.should_receive(:shell_out!).with("gpasswd -M \"\" wheel")
+ @provider.modify_group_members
+ end
+ end
+
+ describe "when no group members are specified and append is set" do
+ before do
+ @new_resource.append(true)
+ @new_resource.members([])
+ end
+
+ it "logs a message and does not modify group membership" do
+ Chef::Log.should_receive(:debug).with("group[wheel] not changing group members, the group has no members to add")
+ @provider.should_not_receive(:shell_out!)
+ @provider.modify_group_members
+ end
+ end
+
+ describe "when the resource specifies group members" do
+ it "should log an appropriate debug message" do
+ Chef::Log.should_receive(:debug).with("group[wheel] setting group members to lobster, rage, fist")
+ @provider.stub!(:shell_out!)
+ @provider.modify_group_members
+ end
+
+ it "should run gpasswd with the members joined by ',' followed by the target group" do
+ @provider.should_receive(:shell_out!).with("gpasswd -M lobster,rage,fist wheel")
+ @provider.modify_group_members
+ end
+
+ it "should run gpasswd individually for each user when the append option is set" do
+ @new_resource.append(true)
+ @provider.should_receive(:shell_out!).with("gpasswd -a lobster wheel")
+ @provider.should_receive(:shell_out!).with("gpasswd -a rage wheel")
+ @provider.should_receive(:shell_out!).with("gpasswd -a fist wheel")
+ @provider.modify_group_members
+ end
+
+ end
+ end
+end
diff --git a/spec/unit/provider/group/groupadd_spec.rb b/spec/unit/provider/group/groupadd_spec.rb
new file mode 100644
index 0000000000..f08e14f99b
--- /dev/null
+++ b/spec/unit/provider/group/groupadd_spec.rb
@@ -0,0 +1,161 @@
+#
+# Author:: AJ Christensen (<aj@opscode.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::Group::Groupadd, "set_options" do
+ before do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Group.new("aj")
+ @new_resource.gid(50)
+ @new_resource.members(["root", "aj"])
+ @new_resource.system false
+ @current_resource = Chef::Resource::Group.new("aj")
+ @current_resource.gid(50)
+ @current_resource.members(["root", "aj"])
+ @current_resource.system false
+ @provider = Chef::Provider::Group::Groupadd.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ end
+
+ field_list = {
+ :gid => "-g"
+ }
+
+ field_list.each do |attribute, option|
+ it "should check for differences in #{attribute.to_s} between the current and new resources" do
+ @new_resource.should_receive(attribute)
+ @current_resource.should_receive(attribute)
+ @provider.set_options
+ end
+ it "should set the option for #{attribute} if the new resources #{attribute} is not null" do
+ @new_resource.stub!(attribute).and_return("wowaweea")
+ @provider.set_options.should eql(" #{option} '#{@new_resource.send(attribute)}' #{@new_resource.group_name}")
+ end
+ end
+
+ it "should combine all the possible options" do
+ match_string = ""
+ field_list.sort{ |a,b| a[0] <=> b[0] }.each do |attribute, option|
+ @new_resource.stub!(attribute).and_return("hola")
+ match_string << " #{option} 'hola'"
+ end
+ match_string << " aj"
+ @provider.set_options.should eql(match_string)
+ end
+
+ describe "when we want to create a system group" do
+ it "should not set groupadd_options '-r' when system is false" do
+ @new_resource.system(false)
+ @provider.groupadd_options.should_not =~ /-r/
+ end
+
+ it "should set groupadd -r if system is true" do
+ @new_resource.system(true)
+ @provider.groupadd_options.should == " -r"
+ end
+ end
+end
+
+describe Chef::Provider::Group::Groupadd, "create_group" do
+ before do
+ @node = Chef::Node.new
+ @new_resource = Chef::Resource::Group.new("aj")
+ @provider = Chef::Provider::Group::Groupadd.new(@node, @new_resource)
+ @provider.stub!(:run_command).and_return(true)
+ @provider.stub!(:set_options).and_return(" monkey")
+ @provider.stub!(:groupadd_options).and_return("")
+ @provider.stub!(:modify_group_members).and_return(true)
+ end
+
+ it "should run groupadd with the return of set_options" do
+ @provider.should_receive(:run_command).with({ :command => "groupadd monkey" }).and_return(true)
+ @provider.create_group
+ end
+
+ it "should modify the group members" do
+ @provider.should_receive(:modify_group_members).and_return(true)
+ @provider.create_group
+ end
+end
+
+describe Chef::Provider::Group::Groupadd do
+ before do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Group.new("aj")
+ @provider = Chef::Provider::Group::Groupadd.new(@new_resource, @run_context)
+ @provider.stub!(:run_command).and_return(true)
+ @provider.stub!(:set_options).and_return(" monkey")
+ end
+
+ describe "manage group" do
+
+ it "should run groupmod with the return of set_options" do
+ @provider.stub!(:modify_group_members).and_return(true)
+ @provider.should_receive(:run_command).with({ :command => "groupmod monkey" }).and_return(true)
+ @provider.manage_group
+ end
+
+ it "should modify the group members" do
+ @provider.should_receive(:modify_group_members).and_return(true)
+ @provider.manage_group
+ end
+ end
+
+ describe "remove_group" do
+
+ it "should run groupdel with the new resources group name" do
+ @provider.should_receive(:run_command).with({ :command => "groupdel aj" }).and_return(true)
+ @provider.remove_group
+ end
+ end
+
+ describe "modify_group_members" do
+
+ it "should raise an error when calling modify_group_members" do
+ lambda { @provider.modify_group_members ; @provider.converge }.should raise_error(Chef::Exceptions::Group, "you must override modify_group_members in #{@provider.to_s}")
+ end
+ end
+
+ describe "load_current_resource" do
+ before do
+ File.stub!(:exists?).and_return(false)
+ @provider.define_resource_requirements
+ end
+ it "should raise an error if the required binary /usr/sbin/groupadd doesn't exist" do
+ File.should_receive(:exists?).with("/usr/sbin/groupadd").and_return(false)
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Group)
+ end
+ it "should raise an error if the required binary /usr/sbin/groupmod doesn't exist" do
+ File.should_receive(:exists?).with("/usr/sbin/groupadd").and_return(true)
+ File.should_receive(:exists?).with("/usr/sbin/groupmod").and_return(false)
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Group)
+ end
+ it "should raise an error if the required binary /usr/sbin/groupdel doesn't exist" do
+ File.should_receive(:exists?).with("/usr/sbin/groupadd").and_return(true)
+ File.should_receive(:exists?).with("/usr/sbin/groupmod").and_return(true)
+ File.should_receive(:exists?).with("/usr/sbin/groupdel").and_return(false)
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Group)
+ end
+
+ end
+end
diff --git a/spec/unit/provider/group/groupmod_spec.rb b/spec/unit/provider/group/groupmod_spec.rb
new file mode 100644
index 0000000000..c9c56313b5
--- /dev/null
+++ b/spec/unit/provider/group/groupmod_spec.rb
@@ -0,0 +1,134 @@
+#
+# Author:: Dan Crosta (<dcrosta@late.am>)
+# Copyright:: Copyright (c) 2012 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'
+
+describe Chef::Provider::Group::Groupmod do
+ before do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Group.new("wheel")
+ @new_resource.gid 123
+ @new_resource.members %w{lobster rage fist}
+ @new_resource.append false
+ @provider = Chef::Provider::Group::Groupmod.new(@new_resource, @run_context)
+ end
+
+ describe "manage_group" do
+ describe "when determining the current group state" do
+ it "should raise an error if the required binary /usr/sbin/group doesn't exist" do
+ File.should_receive(:exists?).with("/usr/sbin/group").and_return(false)
+ lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Group)
+ end
+ it "should raise an error if the required binary /usr/sbin/user doesn't exist" do
+ File.should_receive(:exists?).with("/usr/sbin/group").and_return(true)
+ File.should_receive(:exists?).with("/usr/sbin/user").and_return(false)
+ lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Group)
+ end
+
+ it "shouldn't raise an error if the required binaries exist" do
+ File.stub!(:exists?).and_return(true)
+ lambda { @provider.load_current_resource }.should_not raise_error(Chef::Exceptions::Group)
+ end
+ end
+
+ describe "after the group's current state is known" do
+ before do
+ @current_resource = @new_resource.dup
+ @provider.current_resource = @current_resource
+ end
+
+ describe "when no group members are specified and append is not set" do
+ before do
+ @new_resource.append(false)
+ @new_resource.members([])
+ end
+
+ it "logs a message and sets group's members to 'none', then removes existing group members" do
+ Chef::Log.should_receive(:debug).with("group[wheel] setting group members to: none")
+ Chef::Log.should_receive(:debug).with("group[wheel] removing members lobster, rage, fist")
+ @provider.should_receive(:shell_out!).with("group mod -n wheel_bak wheel")
+ @provider.should_receive(:shell_out!).with("group add -g '123' -o wheel")
+ @provider.should_receive(:shell_out!).with("group del wheel_bak")
+ @provider.manage_group
+ end
+ end
+
+ describe "when no group members are specified and append is set" do
+ before do
+ @new_resource.append(true)
+ @new_resource.members([])
+ end
+
+ it "logs a message and does not modify group membership" do
+ Chef::Log.should_receive(:debug).with("group[wheel] not changing group members, the group has no members to add")
+ @provider.should_not_receive(:shell_out!)
+ @provider.manage_group
+ end
+ end
+
+ describe "when removing some group members" do
+ before do
+ @new_resource.append(false)
+ @new_resource.members(%w{ lobster })
+ end
+
+ it "updates group membership correctly" do
+ Chef::Log.stub!(:debug)
+ @provider.should_receive(:shell_out!).with("group mod -n wheel_bak wheel")
+ @provider.should_receive(:shell_out!).with("user mod -G wheel lobster")
+ @provider.should_receive(:shell_out!).with("group add -g '123' -o wheel")
+ @provider.should_receive(:shell_out!).with("group del wheel_bak")
+ @provider.manage_group
+ end
+ end
+ end
+ end
+
+ describe "create_group" do
+ describe "when creating a new group" do
+ before do
+ @current_resource = Chef::Resource::Group.new("wheel")
+ @provider.current_resource = @current_resource
+ end
+
+ it "should run a group add command and some user mod commands" do
+ @provider.should_receive(:shell_out!).with("group add -g '123' wheel")
+ @provider.should_receive(:shell_out!).with("user mod -G wheel lobster")
+ @provider.should_receive(:shell_out!).with("user mod -G wheel rage")
+ @provider.should_receive(:shell_out!).with("user mod -G wheel fist")
+ @provider.create_group
+ end
+ end
+ end
+
+ describe "remove_group" do
+ describe "when removing an existing group" do
+ before do
+ @current_resource = @new_resource.dup
+ @provider.current_resource = @current_resource
+ end
+
+ it "should run a group del command" do
+ @provider.should_receive(:shell_out!).with("group del wheel")
+ @provider.remove_group
+ end
+ end
+ end
+end
diff --git a/spec/unit/provider/group/pw_spec.rb b/spec/unit/provider/group/pw_spec.rb
new file mode 100644
index 0000000000..a7dbdb8615
--- /dev/null
+++ b/spec/unit/provider/group/pw_spec.rb
@@ -0,0 +1,140 @@
+#
+# Author:: Stephen Haynes (<sh@nomitor.com>)
+# Copyright:: Copyright (c) 2009 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'
+
+describe Chef::Provider::Group::Pw do
+ before do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Group.new("wheel")
+ @new_resource.gid 50
+ @new_resource.members [ "root", "aj"]
+
+ @current_resource = Chef::Resource::Group.new("aj")
+ @current_resource.gid 50
+ @current_resource.members [ "root", "aj"]
+ @provider = Chef::Provider::Group::Pw.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ end
+
+ describe "when setting options for the pw command" do
+ it "does not set the gid option if gids match or are unmanaged" do
+ @provider.set_options.should == " wheel"
+ end
+
+ it "sets the option for gid if it is not nil" do
+ @new_resource.gid(42)
+ @provider.set_options.should eql(" wheel -g '42'")
+ end
+ end
+
+ describe "when creating a group" do
+ it "should run pw groupadd with the return of set_options and set_members_option" do
+ @new_resource.gid(23)
+ @provider.should_receive(:run_command).with({ :command => "pw groupadd wheel -g '23' -M root,aj" }).and_return(true)
+ @provider.create_group
+ end
+ end
+
+ describe "when managing the group" do
+
+ it "should run pw groupmod with the return of set_options" do
+ @new_resource.gid(42)
+ @provider.should_receive(:run_command).with({ :command => "pw groupmod wheel -g '42' -M root,aj" }).and_return(true)
+ @provider.manage_group
+ end
+
+ end
+
+ describe "when removing the group" do
+ it "should run pw groupdel with the new resources group name" do
+ @provider.should_receive(:run_command).with({ :command => "pw groupdel wheel" }).and_return(true)
+ @provider.remove_group
+ end
+ end
+
+ describe "when setting group membership" do
+
+ describe "with an empty members array in both the new and current resource" do
+ before do
+ @new_resource.stub!(:members).and_return([])
+ @current_resource.stub!(:members).and_return([])
+ end
+
+ it "should log an appropriate message" do
+ Chef::Log.should_receive(:debug).with("group[wheel] not changing group members, the group has no members")
+ @provider.set_members_option
+ end
+
+ it "should set no options" do
+ @provider.set_members_option.should eql("")
+ end
+ end
+
+ describe "with an empty members array in the new resource and existing members in the current resource" do
+ before do
+ @new_resource.stub!(:members).and_return([])
+ @current_resource.stub!(:members).and_return(["all", "your", "base"])
+ end
+
+ it "should log an appropriate message" do
+ Chef::Log.should_receive(:debug).with("group[wheel] removing group members all, your, base")
+ @provider.set_members_option
+ end
+
+ it "should set the -d option with the members joined by ','" do
+ @provider.set_members_option.should eql(" -d all,your,base")
+ end
+ end
+
+ describe "with supplied members array in the new resource and an empty members array in the current resource" do
+ before do
+ @new_resource.stub!(:members).and_return(["all", "your", "base"])
+ @current_resource.stub!(:members).and_return([])
+ end
+
+ it "should log an appropriate debug message" do
+ Chef::Log.should_receive(:debug).with("group[wheel] setting group members to all, your, base")
+ @provider.set_members_option
+ end
+
+ it "should set the -M option with the members joined by ','" do
+ @provider.set_members_option.should eql(" -M all,your,base")
+ end
+ end
+ end
+
+ describe"load_current_resource" do
+ before (:each) do
+ @provider.load_current_resource
+ @provider.define_resource_requirements
+ end
+ it "should raise an error if the required binary /usr/sbin/pw doesn't exist" do
+ File.should_receive(:exists?).with("/usr/sbin/pw").and_return(false)
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Group)
+ end
+
+ it "shouldn't raise an error if /usr/sbin/pw exists" do
+ File.stub!(:exists?).and_return(true)
+ lambda { @provider.process_resource_requirements }.should_not raise_error(Chef::Exceptions::Group)
+ end
+ end
+end
diff --git a/spec/unit/provider/group/usermod_spec.rb b/spec/unit/provider/group/usermod_spec.rb
new file mode 100644
index 0000000000..6e6e275a7a
--- /dev/null
+++ b/spec/unit/provider/group/usermod_spec.rb
@@ -0,0 +1,95 @@
+#
+# Author:: AJ Christensen (<aj@opscode.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::Group::Usermod do
+ before do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Group.new("wheel")
+ @new_resource.members [ "all", "your", "base" ]
+ @provider = Chef::Provider::Group::Usermod.new(@new_resource, @run_context)
+ @provider.stub!(:run_command)
+ end
+
+ describe "modify_group_members" do
+
+ describe "with an empty members array" do
+ before do
+ @new_resource.stub!(:members).and_return([])
+ end
+
+ it "should log an appropriate message" do
+ Chef::Log.should_receive(:debug).with("group[wheel] not changing group members, the group has no members")
+ @provider.modify_group_members
+ end
+ end
+
+ describe "with supplied members" do
+ platforms = {
+ "openbsd" => "-G",
+ "netbsd" => "-G",
+ "solaris" => "-a -G"
+ }
+
+ before do
+ @new_resource.stub!(:members).and_return(["all", "your", "base"])
+ File.stub!(:exists?).and_return(true)
+ end
+
+ it "should raise an error when setting the entire group directly" do
+ @provider.define_resource_requirements
+ @provider.load_current_resource
+ @provider.instance_variable_set("@group_exists", true)
+ @provider.action = :modify
+ lambda { @provider.run_action(@provider.process_resource_requirements) }.should raise_error(Chef::Exceptions::Group, "setting group members directly is not supported by #{@provider.to_s}, must set append true in group")
+ end
+
+ platforms.each do |platform, flags|
+ it "should usermod each user when the append option is set on #{platform}" do
+ @node.automatic_attrs[:platform] = platform
+ @new_resource.stub!(:append).and_return(true)
+ @provider.should_receive(:run_command).with({:command => "usermod #{flags} wheel all"})
+ @provider.should_receive(:run_command).with({:command => "usermod #{flags} wheel your"})
+ @provider.should_receive(:run_command).with({:command => "usermod #{flags} wheel base"})
+ @provider.modify_group_members
+ end
+ end
+ end
+ end
+
+ describe "when loading the current resource" do
+ before(:each) do
+ File.stub!(:exists?).and_return(false)
+ @provider.define_resource_requirements
+ end
+
+ it "should raise an error if the required binary /usr/sbin/usermod doesn't exist" do
+ File.stub!(:exists?).and_return(true)
+ File.should_receive(:exists?).with("/usr/sbin/usermod").and_return(false)
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Group)
+ end
+
+ it "shouldn't raise an error if the required binaries exist" do
+ File.stub!(:exists?).and_return(true)
+ lambda { @provider.process_resource_requirements }.should_not raise_error(Chef::Exceptions::Group)
+ end
+ end
+end
diff --git a/spec/unit/provider/group/windows_spec.rb b/spec/unit/provider/group/windows_spec.rb
new file mode 100644
index 0000000000..084d1d0acf
--- /dev/null
+++ b/spec/unit/provider/group/windows_spec.rb
@@ -0,0 +1,94 @@
+#
+# Author:: Doug MacEachern (<dougm@vmware.com>)
+# Copyright:: Copyright (c) 2010 VMware, 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'
+
+class Chef
+ class Util
+ class Windows
+ class NetGroup
+ end
+ end
+ end
+end
+
+describe Chef::Provider::Group::Windows do
+ before do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Group.new("staff")
+ @net_group = mock("Chef::Util::Windows::NetGroup")
+ Chef::Util::Windows::NetGroup.stub!(:new).and_return(@net_group)
+ @provider = Chef::Provider::Group::Windows.new(@new_resource, @run_context)
+ end
+
+ describe "when creating the group" do
+ it "should call @net_group.local_add" do
+ @net_group.should_receive(:local_set_members).with([])
+ @net_group.should_receive(:local_add)
+ @provider.create_group
+ end
+ end
+
+ describe "manage_group" do
+ before do
+ @new_resource.members([ "us" ])
+ @current_resource = Chef::Resource::Group.new("staff")
+ @current_resource.members [ "all", "your", "base" ]
+
+ Chef::Util::Windows::NetGroup.stub!(:new).and_return(@net_group)
+ @net_group.stub!(:local_add_members)
+ @net_group.stub!(:local_set_members)
+ @provider.current_resource = @current_resource
+ end
+
+ it "should call @net_group.local_set_members" do
+ @new_resource.stub!(:append).and_return(false)
+ @net_group.should_receive(:local_set_members).with(@new_resource.members)
+ @provider.manage_group
+ end
+
+ it "should call @net_group.local_add_members" do
+ @new_resource.stub!(:append).and_return(true)
+ @net_group.should_receive(:local_add_members).with(@new_resource.members)
+ @provider.manage_group
+ end
+
+ it "should call @net_group.local_set_members if append fails" do
+ @new_resource.stub!(:append).and_return(true)
+ @net_group.stub!(:local_add_members).and_raise(ArgumentError)
+ @net_group.should_receive(:local_add_members).with(@new_resource.members)
+ @net_group.should_receive(:local_set_members).with(@new_resource.members + @current_resource.members)
+ @provider.manage_group
+ end
+
+ end
+
+ describe "remove_group" do
+ before do
+ Chef::Util::Windows::NetGroup.stub!(:new).and_return(@net_group)
+ @provider.stub!(:run_command).and_return(true)
+ end
+
+ it "should call @net_group.local_delete" do
+ @net_group.should_receive(:local_delete)
+ @provider.remove_group
+ end
+ end
+end
diff --git a/spec/unit/provider/group_spec.rb b/spec/unit/provider/group_spec.rb
new file mode 100644
index 0000000000..106a0db14c
--- /dev/null
+++ b/spec/unit/provider/group_spec.rb
@@ -0,0 +1,259 @@
+#
+# Author:: AJ Christensen (<aj@opscode.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::User do
+
+ before do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Group.new("wheel", @run_context)
+ @new_resource.gid 500
+ @new_resource.members "aj"
+
+ @provider = Chef::Provider::Group.new(@new_resource, @run_context)
+
+ @current_resource = Chef::Resource::Group.new("aj", @run_context)
+ @current_resource.gid 500
+ @current_resource.members "aj"
+
+ @provider.current_resource = @current_resource
+
+ @pw_group = mock("Struct::Group",
+ :name => "wheel",
+ :gid => 20,
+ :mem => [ "root", "aj" ]
+ )
+ Etc.stub!(:getgrnam).with('wheel').and_return(@pw_group)
+ end
+
+ it "assumes the group exists by default" do
+ @provider.group_exists.should be_true
+ end
+
+ describe "when establishing the current state of the group" do
+
+ it "sets the group name of the current resource to the group name of the new resource" do
+ @provider.load_current_resource
+ @provider.current_resource.group_name.should == 'wheel'
+ end
+
+ it "does not modify the desired gid if set" do
+ @provider.load_current_resource
+ @new_resource.gid.should == 500
+ end
+
+ it "sets the desired gid to the current gid if none is set" do
+ @new_resource.instance_variable_set(:@gid, nil)
+ @provider.load_current_resource
+ @new_resource.gid.should == 20
+ end
+
+ it "looks up the group in /etc/group with getgrnam" do
+ Etc.should_receive(:getgrnam).with(@new_resource.group_name).and_return(@pw_group)
+ @provider.load_current_resource
+ @provider.current_resource.gid.should == 20
+ @provider.current_resource.members.should == %w{root aj}
+ end
+
+ it "should flip the value of exists if it cannot be found in /etc/group" do
+ Etc.stub!(:getgrnam).and_raise(ArgumentError)
+ @provider.load_current_resource
+ @provider.group_exists.should be_false
+ end
+
+ it "should return the current resource" do
+ @provider.load_current_resource.should equal(@provider.current_resource)
+ end
+ end
+
+ describe "when determining if the system is already in the target state" do
+ [ :gid, :members ].each do |attribute|
+ it "should return true if #{attribute} doesn't match" do
+ @current_resource.stub!(attribute).and_return("looooooooooooooooooool")
+ @provider.compare_group.should be_true
+ end
+ end
+
+ it "should return false if gid and members are equal" do
+ @provider.compare_group.should be_false
+ end
+
+ it "should return false if append is true and the group member(s) already exists" do
+ @current_resource.members << "extra_user"
+ @new_resource.stub!(:append).and_return(true)
+ @provider.compare_group.should be_false
+ end
+
+ it "should return true if append is true and the group member(s) do not already exist" do
+ @new_resource.members << "extra_user"
+ @new_resource.stub!(:append).and_return(true)
+ @provider.compare_group.should be_true
+ end
+
+ end
+
+ describe "when creating a group" do
+ it "should call create_group if the group does not exist" do
+ @provider.group_exists = false
+ @provider.should_receive(:create_group).and_return(true)
+ @provider.run_action(:create)
+ end
+
+ it "should set the the new_resources updated flag when it creates the group" do
+ @provider.group_exists = false
+ @provider.stub!(:create_group)
+ @provider.run_action(:create)
+ @provider.new_resource.should be_updated
+ end
+
+ it "should check to see if the group has mismatched attributes if the group exists" do
+ @provider.group_exists = true
+ @provider.stub!(:compare_group).and_return(false)
+ @provider.run_action(:create)
+ @provider.new_resource.should_not be_updated
+ end
+
+ it "should call manage_group if the group exists and has mismatched attributes" do
+ @provider.group_exists = true
+ @provider.stub!(:compare_group).and_return(true)
+ @provider.should_receive(:manage_group).and_return(true)
+ @provider.run_action(:create)
+ end
+
+ it "should set the the new_resources updated flag when it creates the group if we call manage_group" do
+ @provider.group_exists = true
+ @provider.stub!(:compare_group).and_return(true)
+ @provider.stub!(:manage_group).and_return(true)
+ @provider.run_action(:create)
+ @new_resource.should be_updated
+ end
+ end
+
+ describe "when removing a group" do
+
+ it "should not call remove_group if the group does not exist" do
+ @provider.group_exists = false
+ @provider.should_not_receive(:remove_group)
+ @provider.run_action(:remove)
+ @provider.new_resource.should_not be_updated
+ end
+
+ it "should call remove_group if the group exists" do
+ @provider.group_exists = true
+ @provider.should_receive(:remove_group)
+ @provider.run_action(:remove)
+ @provider.new_resource.should be_updated
+ end
+ end
+
+ describe "when updating a group" do
+ before(:each) do
+ @provider.group_exists = true
+ @provider.stub!(:manage_group).and_return(true)
+ end
+
+ it "should run manage_group if the group exists and has mismatched attributes" do
+ @provider.should_receive(:compare_group).and_return(true)
+ @provider.should_receive(:manage_group).and_return(true)
+ @provider.run_action(:manage)
+ end
+
+ it "should set the new resources updated flag to true if manage_group is called" do
+ @provider.stub!(:compare_group).and_return(true)
+ @provider.stub!(:manage_group).and_return(true)
+ @provider.run_action(:manage)
+ @new_resource.should be_updated
+ end
+
+ it "should not run manage_group if the group does not exist" do
+ @provider.group_exists = false
+ @provider.should_not_receive(:manage_group)
+ @provider.run_action(:manage)
+ end
+
+ it "should not run manage_group if the group exists but has no differing attributes" do
+ @provider.should_receive(:compare_group).and_return(false)
+ @provider.should_not_receive(:manage_group)
+ @provider.run_action(:manage)
+ end
+ end
+
+ describe "when modifying the group" do
+ before(:each) do
+ @provider.group_exists = true
+ @provider.stub!(:manage_group).and_return(true)
+ end
+
+ it "should run manage_group if the group exists and has mismatched attributes" do
+ @provider.should_receive(:compare_group).and_return(true)
+ @provider.should_receive(:manage_group).and_return(true)
+ @provider.run_action(:modify)
+ end
+
+ it "should set the new resources updated flag to true if manage_group is called" do
+ @provider.stub!(:compare_group).and_return(true)
+ @provider.stub!(:manage_group).and_return(true)
+ @provider.run_action(:modify)
+ @new_resource.should be_updated
+ end
+
+ it "should not run manage_group if the group exists but has no differing attributes" do
+ @provider.should_receive(:compare_group).and_return(false)
+ @provider.should_not_receive(:manage_group)
+ @provider.run_action(:modify)
+ end
+
+ it "should raise a Chef::Exceptions::Group if the group doesn't exist" do
+ @provider.group_exists = false
+ lambda { @provider.run_action(:modify) }.should raise_error(Chef::Exceptions::Group)
+ end
+ end
+
+ describe "when determining the reason for a change" do
+ it "should report which group members are missing if members are missing and appending to the group" do
+ @new_resource.members << "user1"
+ @new_resource.members << "user2"
+ @new_resource.stub!(:append).and_return true
+ @provider.compare_group.should be_true
+ @provider.change_desc.should == "add missing member(s): user1, user2"
+ end
+
+ it "should report that the group members will be overwritten if not appending" do
+ @new_resource.members << "user1"
+ @new_resource.stub!(:append).and_return false
+ @provider.compare_group.should be_true
+ @provider.change_desc.should == "replace group members with new list of members"
+ end
+
+ it "should report the gid will be changed when it does not match" do
+ @current_resource.stub!(:gid).and_return("BADF00D")
+ @provider.compare_group.should be_true
+ @provider.change_desc.should == "change gid #{@current_resource.gid} to #{@new_resource.gid}"
+
+ end
+
+ it "should report no change reason when no change is required" do
+ @provider.compare_group.should be_false
+ @provider.change_desc.should == nil
+ end
+ end
+
+end
diff --git a/spec/unit/provider/http_request_spec.rb b/spec/unit/provider/http_request_spec.rb
new file mode 100644
index 0000000000..26d73cebb4
--- /dev/null
+++ b/spec/unit/provider/http_request_spec.rb
@@ -0,0 +1,178 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::HttpRequest do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::HttpRequest.new('adam')
+ @new_resource.name "adam"
+ @new_resource.url "http://www.opscode.com"
+ @new_resource.message "is cool"
+
+ @provider = Chef::Provider::HttpRequest.new(@new_resource, @run_context)
+ end
+
+ describe "load_current_resource" do
+
+ it "should set up a Chef::REST client, with no authentication" do
+ Chef::REST.should_receive(:new).with(@new_resource.url, nil, nil)
+ @provider.load_current_resource
+ end
+ end
+
+ describe "when making REST calls" do
+ before(:each) do
+ # run_action(x) forces load_current_resource to run;
+ # that would overwrite our supplied mock Chef::Rest # object
+ @provider.stub!(:load_current_resource).and_return(true)
+ @rest = mock("Chef::REST", :create_url => "http://www.opscode.com", :run_request => "you made it!" )
+ @provider.rest = @rest
+ end
+
+ describe "action_get" do
+ it "should create the url with a message argument" do
+ @rest.should_receive(:create_url).with("#{@new_resource.url}?message=#{@new_resource.message}")
+ @provider.run_action(:get)
+ end
+
+ it "should inflate a message block at runtime" do
+ @new_resource.stub!(:message).and_return(lambda { "return" })
+ @rest.should_receive(:create_url).with("#{@new_resource.url}?message=return")
+ @provider.run_action(:get)
+ end
+
+ it "should run a GET request" do
+ @rest.should_receive(:run_request).with(:GET, @rest.create_url, {}, false, 10, false)
+ @provider.run_action(:get)
+ end
+
+ it "should update the resource" do
+ @provider.run_action(:get)
+ @new_resource.should be_updated
+ end
+ end
+
+ describe "action_put" do
+ it "should create the url" do
+ @rest.should_receive(:create_url).with("#{@new_resource.url}")
+ @provider.run_action(:put)
+ end
+
+ it "should run a PUT request with the message as the payload" do
+ @rest.should_receive(:run_request).with(:PUT, @rest.create_url, {}, @new_resource.message, 10, false)
+ @provider.run_action(:put)
+ end
+
+ it "should inflate a message block at runtime" do
+ @new_resource.stub!(:message).and_return(lambda { "return" })
+ @rest.should_receive(:run_request).with(:PUT, @rest.create_url, {}, "return", 10, false)
+ @provider.run_action(:put)
+ end
+
+ it "should update the resource" do
+ @provider.run_action(:put)
+ @new_resource.should be_updated
+ end
+ end
+
+ describe "action_post" do
+ it "should create the url" do
+ @rest.should_receive(:create_url).with("#{@new_resource.url}")
+ @provider.run_action(:post)
+ end
+
+ it "should run a PUT request with the message as the payload" do
+ @rest.should_receive(:run_request).with(:POST, @rest.create_url, {}, @new_resource.message, 10, false)
+ @provider.run_action(:post)
+ end
+
+ it "should inflate a message block at runtime" do
+ @new_resource.stub!(:message).and_return(lambda { "return" })
+ @rest.should_receive(:run_request).with(:POST, @rest.create_url, {}, "return", 10, false)
+ @provider.run_action(:post)
+ end
+
+ it "should update the resource" do
+ @provider.run_action(:post)
+ @new_resource.should be_updated
+ end
+ end
+
+ describe "action_delete" do
+ it "should create the url" do
+ @rest.should_receive(:create_url).with("#{@new_resource.url}")
+ @provider.run_action(:delete)
+ end
+
+ it "should run a DELETE request" do
+ @rest.should_receive(:run_request).with(:DELETE, @rest.create_url, {}, false, 10, false)
+ @provider.run_action(:delete)
+ end
+
+ it "should update the resource" do
+ @provider.run_action(:delete)
+ @new_resource.should be_updated
+ end
+ end
+
+ describe "action_head" do
+ before do
+ @rest = mock("Chef::REST", :create_url => "http://www.opscode.com", :run_request => true)
+ @provider.rest = @rest
+ end
+
+ it "should create the url with a message argument" do
+ @rest.should_receive(:create_url).with("#{@new_resource.url}?message=#{@new_resource.message}")
+ @provider.run_action(:head)
+ end
+
+ it "should inflate a message block at runtime" do
+ @new_resource.stub!(:message).and_return(lambda { "return" })
+ @rest.should_receive(:create_url).with("#{@new_resource.url}?message=return")
+ @provider.run_action(:head)
+ end
+
+ it "should run a HEAD request" do
+ @rest.should_receive(:run_request).with(:HEAD, @rest.create_url, {}, false, 10, false)
+ @provider.run_action(:head)
+ end
+
+ it "should update the resource" do
+ @provider.run_action(:head)
+ @new_resource.should be_updated
+ end
+
+ it "should run a HEAD request with If-Modified-Since header" do
+ @new_resource.headers "If-Modified-Since" => File.mtime(__FILE__).httpdate
+ @rest.should_receive(:run_request).with(:HEAD, @rest.create_url, @new_resource.headers, false, 10, false)
+ @provider.run_action(:head)
+ end
+
+ it "doesn't call converge_by if HEAD does not return modified" do
+ @rest.should_receive(:run_request).and_return(false)
+ @provider.should_not_receive(:converge_by)
+ @provider.run_action(:head)
+ end
+ end
+ end
+end
diff --git a/spec/unit/provider/ifconfig_spec.rb b/spec/unit/provider/ifconfig_spec.rb
new file mode 100644
index 0000000000..1bf702cf6e
--- /dev/null
+++ b/spec/unit/provider/ifconfig_spec.rb
@@ -0,0 +1,213 @@
+#
+# Author:: Prajakta Purohit (prajakta@opscode.com)
+# Copyright:: Copyright (c) 2008 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+require 'spec_helper'
+require 'chef/exceptions'
+
+describe Chef::Provider::Ifconfig do
+ before do
+ @node = Chef::Node.new
+ @cookbook_collection = Chef::CookbookCollection.new([])
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+ #This new_resource can be called anything --> it is not the same as in ifconfig.rb
+ @new_resource = Chef::Resource::Ifconfig.new("10.0.0.1", @run_context)
+ @new_resource.mask "255.255.254.0"
+ @new_resource.metric "1"
+ @new_resource.mtu "1500"
+ @new_resource.device "eth0"
+ @provider = Chef::Provider::Ifconfig.new(@new_resource, @run_context)
+ @current_resource = Chef::Resource::Ifconfig.new("10.0.0.1", @run_context)
+
+ status = mock("Status", :exitstatus => 0)
+ @provider.instance_variable_set("@status", status)
+ @provider.current_resource = @current_resource
+
+ end
+ describe Chef::Provider::Ifconfig, "load_current_resource" do
+ before do
+ status = mock("Status", :exitstatus => 1)
+ @provider.should_receive(:popen4).and_return status
+ @provider.load_current_resource
+ end
+ it "should track state of ifconfig failure." do
+ @provider.instance_variable_get("@status").exitstatus.should_not == 0
+ end
+ it "should thrown an exception when ifconfig fails" do
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error Chef::Exceptions::Ifconfig
+ end
+ end
+ describe Chef::Provider::Ifconfig, "action_add" do
+
+ it "should add an interface if it does not exist" do
+ #@provider.stub!(:run_command).and_return(true)
+ @provider.stub!(:load_current_resource)
+ @current_resource.inet_addr nil
+ command = "ifconfig eth0 10.0.0.1 netmask 255.255.254.0 metric 1 mtu 1500"
+ @provider.should_receive(:run_command).with(:command => command)
+ @provider.should_receive(:generate_config)
+
+ @provider.run_action(:add)
+ @new_resource.should be_updated
+ end
+
+ it "should not add an interface if it already exists" do
+ @provider.stub!(:load_current_resource)
+ @provider.should_not_receive(:run_command)
+ @current_resource.inet_addr "10.0.0.1"
+ @provider.should_receive(:generate_config)
+
+ @provider.run_action(:add)
+ @new_resource.should_not be_updated
+ end
+
+ #We are not testing this case with the assumption that anyone writing the cookbook would not make a typo == lo
+ #it "should add a blank command if the #{@new_resource.device} == lo" do
+ #end
+ end
+
+ describe Chef::Provider::Ifconfig, "action_enable" do
+
+ it "should enable interface if does not exist" do
+ @provider.stub!(:load_current_resource)
+ @current_resource.inet_addr nil
+ command = "ifconfig eth0 10.0.0.1 netmask 255.255.254.0 metric 1 mtu 1500"
+ @provider.should_receive(:run_command).with(:command => command)
+ @provider.should_not_receive(:generate_config)
+
+ @provider.run_action(:enable)
+ @new_resource.should be_updated
+ end
+
+ it "should not enable interface if it already exists" do
+ @provider.stub!(:load_current_resource)
+ @provider.should_not_receive(:run_command)
+ @current_resource.inet_addr "10.0.0.1"
+ @provider.should_not_receive(:generate_config)
+
+ @provider.run_action(:enable)
+ @new_resource.should_not be_updated
+ end
+ end
+
+ describe Chef::Provider::Ifconfig, "action_delete" do
+
+ it "should delete interface if it exists" do
+ @provider.stub!(:load_current_resource)
+ @current_resource.device "eth0"
+ command = "ifconfig #{@new_resource.device} down"
+ @provider.should_receive(:run_command).with(:command => command)
+ @provider.should_receive(:delete_config)
+
+ @provider.run_action(:delete)
+ @new_resource.should be_updated
+ end
+
+ it "should not delete interface if it does not exist" do
+ @provider.stub!(:load_current_resource)
+ @provider.should_not_receive(:run_command)
+ @provider.should_not_receive(:delete_config)
+
+ @provider.run_action(:delete)
+ @new_resource.should_not be_updated
+ end
+ end
+
+ describe Chef::Provider::Ifconfig, "action_disable" do
+
+ it "should disable interface if it exists" do
+ @provider.stub!(:load_current_resource)
+ @current_resource.device "eth0"
+ command = "ifconfig #{@new_resource.device} down"
+ @provider.should_receive(:run_command).with(:command => command)
+ @provider.should_not_receive(:delete_config)
+
+ @provider.run_action(:disable)
+ @new_resource.should be_updated
+ end
+
+ it "should not delete interface if it does not exist" do
+ @provider.stub!(:load_current_resource)
+ @provider.should_not_receive(:run_command)
+ @provider.should_not_receive(:delete_config)
+
+ @provider.run_action(:disable)
+ @new_resource.should_not be_updated
+ end
+ end
+
+ describe Chef::Provider::Ifconfig, "action_delete" do
+
+ it "should delete interface of it exists" do
+ @provider.stub!(:load_current_resource)
+ @current_resource.device "eth0"
+ command = "ifconfig #{@new_resource.device} down"
+ @provider.should_receive(:run_command).with(:command => command)
+ @provider.should_receive(:delete_config)
+
+ @provider.run_action(:delete)
+ @new_resource.should be_updated
+ end
+
+ it "should not delete interface if it does not exist" do
+ # This is so that our fake values do not get overwritten
+ @provider.stub!(:load_current_resource)
+ # This is so that nothing actually runs
+ @provider.should_not_receive(:run_command)
+ @provider.should_not_receive(:delete_config)
+
+ @provider.run_action(:delete)
+ @new_resource.should_not be_updated
+ end
+ end
+
+ describe Chef::Provider::Ifconfig, "generate_config for action_add" do
+ #%w[ centos redhat fedora ].each do |platform|
+
+ it "should write network-script for centos" do
+ @provider.stub!(:load_current_resource)
+ @node.automatic_attrs[:platform] = "centos"
+ @provider.stub!(:run_command)
+ config_filename = "/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}"
+ config_file = StringIO.new
+ File.should_receive(:new).with(config_filename, "w").and_return(config_file)
+
+ @provider.run_action(:add)
+ config_file.string.should match(/^\s*DEVICE=eth0\s*$/)
+ config_file.string.should match(/^\s*IPADDR=10.0.0.1\s*$/)
+ config_file.string.should match(/^\s*NETMASK=255.255.254.0\s*$/)
+ end
+ end
+
+ describe Chef::Provider::Ifconfig, "delete_config for action_delete" do
+
+ it "should delete network-script if it exists for centos" do
+ @node.automatic_attrs[:platform] = "centos"
+ @current_resource.device "eth0"
+ @provider.stub!(:load_current_resource)
+ @provider.stub!(:run_command)
+ config_filename = "/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}"
+ File.should_receive(:exist?).with(config_filename).and_return(true)
+ FileUtils.should_receive(:rm_f).with(config_filename, :verbose => false)
+
+ @provider.run_action(:delete)
+ end
+ end
+end
diff --git a/spec/unit/provider/link_spec.rb b/spec/unit/provider/link_spec.rb
new file mode 100644
index 0000000000..bb3f01abbe
--- /dev/null
+++ b/spec/unit/provider/link_spec.rb
@@ -0,0 +1,252 @@
+#
+# Author:: AJ Christensen (<aj@junglist.gen.nz>)
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2008 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 'ostruct'
+
+require 'spec_helper'
+
+if Chef::Platform.windows?
+ require 'chef/win32/file' #probably need this in spec_helper
+end
+
+describe Chef::Resource::Link do
+ let(:provider) do
+ node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ run_context = Chef::RunContext.new(node, {}, @events)
+ Chef::Provider::Link.new(new_resource, run_context)
+ end
+ let(:new_resource) do
+ result = Chef::Resource::Link.new("#{CHEF_SPEC_DATA}/fofile-link")
+ result.to "#{CHEF_SPEC_DATA}/fofile"
+ result
+ end
+
+ def canonicalize(path)
+ Chef::Platform.windows? ? path.gsub('/', '\\') : path
+ end
+
+ describe "when the target is a symlink" do
+ before(:each) do
+ lstat = mock("stats", :ino => 5)
+ lstat.stub!(:uid).and_return(501)
+ lstat.stub!(:gid).and_return(501)
+ lstat.stub!(:mode).and_return(0777)
+ File.stub!(:lstat).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(lstat)
+ provider.file_class.stub!(:symlink?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(true)
+ provider.file_class.stub!(:readlink).with("#{CHEF_SPEC_DATA}/fofile-link").and_return("#{CHEF_SPEC_DATA}/fofile")
+ end
+
+ describe "to a file that exists" do
+ before do
+ File.stub(:exist?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(true)
+ new_resource.owner 501 # only loaded in current_resource if present in new
+ new_resource.group 501
+ provider.load_current_resource
+ end
+
+ it "should set the symlink target" do
+ provider.current_resource.target_file.should == "#{CHEF_SPEC_DATA}/fofile-link"
+ end
+ it "should set the link type" do
+ provider.current_resource.link_type.should == :symbolic
+ end
+ it "should update the source of the existing link with the links target" do
+ provider.current_resource.to.should == canonicalize("#{CHEF_SPEC_DATA}/fofile")
+ end
+ it "should set the owner" do
+ provider.current_resource.owner.should == 501
+ end
+ it "should set the group" do
+ provider.current_resource.group.should == 501
+ end
+
+ # We test create in unit tests because there is no other way to ensure
+ # it does no work. Other create and delete scenarios are covered in
+ # the functional tests for links.
+ context 'when the desired state is identical' do
+ let(:new_resource) do
+ result = Chef::Resource::Link.new("#{CHEF_SPEC_DATA}/fofile-link")
+ result.to "#{CHEF_SPEC_DATA}/fofile"
+ result
+ end
+ it 'create does no work' do
+ provider.access_controls.should_not_receive(:set_all)
+ provider.run_action(:create)
+ end
+ end
+ end
+
+ describe "to a file that doesn't exist" do
+ before do
+ File.stub!(:exist?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(false)
+ provider.file_class.stub!(:symlink?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(true)
+ provider.file_class.stub!(:readlink).with("#{CHEF_SPEC_DATA}/fofile-link").and_return("#{CHEF_SPEC_DATA}/fofile")
+ new_resource.owner "501" # only loaded in current_resource if present in new
+ new_resource.group "501"
+ provider.load_current_resource
+ end
+
+ it "should set the symlink target" do
+ provider.current_resource.target_file.should == "#{CHEF_SPEC_DATA}/fofile-link"
+ end
+ it "should set the link type" do
+ provider.current_resource.link_type.should == :symbolic
+ end
+ it "should update the source of the existing link to the link's target" do
+ provider.current_resource.to.should == canonicalize("#{CHEF_SPEC_DATA}/fofile")
+ end
+ it "should not set the owner" do
+ provider.current_resource.owner.should be_nil
+ end
+ it "should not set the group" do
+ provider.current_resource.group.should be_nil
+ end
+ end
+ end
+
+ describe "when the target doesn't exist" do
+ before do
+ File.stub!(:exists?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(false)
+ provider.file_class.stub!(:symlink?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(false)
+ provider.load_current_resource
+ end
+
+ it "should set the symlink target" do
+ provider.current_resource.target_file.should == "#{CHEF_SPEC_DATA}/fofile-link"
+ end
+ it "should update the source of the existing link to nil" do
+ provider.current_resource.to.should be_nil
+ end
+ it "should not set the owner" do
+ provider.current_resource.owner.should == nil
+ end
+ it "should not set the group" do
+ provider.current_resource.group.should == nil
+ end
+ end
+
+ describe "when the target is a regular old file" do
+ before do
+ stat = mock("stats", :ino => 5)
+ stat.stub!(:uid).and_return(501)
+ stat.stub!(:gid).and_return(501)
+ stat.stub!(:mode).and_return(0755)
+ provider.file_class.stub!(:stat).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(stat)
+
+ File.stub!(:exists?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(true)
+ provider.file_class.stub!(:symlink?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(false)
+ end
+
+ describe "and the source does not exist" do
+ before do
+ File.stub!(:exists?).with("#{CHEF_SPEC_DATA}/fofile").and_return(false)
+ provider.load_current_resource
+ end
+
+ it "should set the symlink target" do
+ provider.current_resource.target_file.should == "#{CHEF_SPEC_DATA}/fofile-link"
+ end
+ it "should update the current source of the existing link with an empty string" do
+ provider.current_resource.to.should == ''
+ end
+ it "should not set the owner" do
+ provider.current_resource.owner.should == nil
+ end
+ it "should not set the group" do
+ provider.current_resource.group.should == nil
+ end
+ end
+
+ describe "and the source exists" do
+ before do
+ stat = mock("stats", :ino => 6)
+ stat.stub!(:uid).and_return(502)
+ stat.stub!(:gid).and_return(502)
+ stat.stub!(:mode).and_return(0644)
+
+ provider.file_class.stub!(:stat).with("#{CHEF_SPEC_DATA}/fofile").and_return(stat)
+
+ File.stub!(:exists?).with("#{CHEF_SPEC_DATA}/fofile").and_return(true)
+ provider.load_current_resource
+ end
+
+ it "should set the symlink target" do
+ provider.current_resource.target_file.should == "#{CHEF_SPEC_DATA}/fofile-link"
+ end
+ it "should update the current source of the existing link with an empty string" do
+ provider.current_resource.to.should == ''
+ end
+ it "should not set the owner" do
+ provider.current_resource.owner.should == nil
+ end
+ it "should not set the group" do
+ provider.current_resource.group.should == nil
+ end
+ end
+
+ describe "and is hardlinked to the source" do
+ before do
+ stat = mock("stats", :ino => 5)
+ stat.stub!(:uid).and_return(502)
+ stat.stub!(:gid).and_return(502)
+ stat.stub!(:mode).and_return(0644)
+
+ provider.file_class.stub!(:stat).with("#{CHEF_SPEC_DATA}/fofile").and_return(stat)
+
+ File.stub!(:exists?).with("#{CHEF_SPEC_DATA}/fofile").and_return(true)
+ provider.load_current_resource
+ end
+
+ it "should set the symlink target" do
+ provider.current_resource.target_file.should == "#{CHEF_SPEC_DATA}/fofile-link"
+ end
+ it "should set the link type" do
+ provider.current_resource.link_type.should == :hard
+ end
+ it "should update the source of the existing link to the link's target" do
+ provider.current_resource.to.should == canonicalize("#{CHEF_SPEC_DATA}/fofile")
+ end
+ it "should not set the owner" do
+ provider.current_resource.owner.should == nil
+ end
+ it "should not set the group" do
+ provider.current_resource.group.should == nil
+ end
+
+ # We test create in unit tests because there is no other way to ensure
+ # it does no work. Other create and delete scenarios are covered in
+ # the functional tests for links.
+ context 'when the desired state is identical' do
+ let(:new_resource) do
+ result = Chef::Resource::Link.new("#{CHEF_SPEC_DATA}/fofile-link")
+ result.to "#{CHEF_SPEC_DATA}/fofile"
+ result.link_type :hard
+ result
+ end
+ it 'create does no work' do
+ provider.file_class.should_not_receive(:symlink)
+ provider.file_class.should_not_receive(:link)
+ provider.access_controls.should_not_receive(:set_all)
+ provider.run_action(:create)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/provider/log_spec.rb b/spec/unit/provider/log_spec.rb
new file mode 100644
index 0000000000..fe3733240a
--- /dev/null
+++ b/spec/unit/provider/log_spec.rb
@@ -0,0 +1,81 @@
+#
+# Author:: Cary Penniman (<cary@rightscale.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::Log::ChefLog do
+
+ before(:each) do
+ @log_str = "this is my test string to log"
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ end
+
+ it "should be registered with the default platform hash" do
+ Chef::Platform.platforms[:default][:log].should_not be_nil
+ end
+
+ it "should write the string to the Chef::Log object at default level (info)" do
+ @new_resource = Chef::Resource::Log.new(@log_str)
+ @provider = Chef::Provider::Log::ChefLog.new(@new_resource, @run_context)
+ Chef::Log.should_receive(:info).with(@log_str).and_return(true)
+ @provider.action_write
+ end
+
+ it "should write the string to the Chef::Log object at debug level" do
+ @new_resource = Chef::Resource::Log.new(@log_str)
+ @new_resource.level :debug
+ @provider = Chef::Provider::Log::ChefLog.new(@new_resource, @run_context)
+ Chef::Log.should_receive(:debug).with(@log_str).and_return(true)
+ @provider.action_write
+ end
+
+ it "should write the string to the Chef::Log object at info level" do
+ @new_resource = Chef::Resource::Log.new(@log_str)
+ @new_resource.level :info
+ @provider = Chef::Provider::Log::ChefLog.new(@new_resource, @run_context)
+ Chef::Log.should_receive(:info).with(@log_str).and_return(true)
+ @provider.action_write
+ end
+
+ it "should write the string to the Chef::Log object at warn level" do
+ @new_resource = Chef::Resource::Log.new(@log_str)
+ @new_resource.level :warn
+ @provider = Chef::Provider::Log::ChefLog.new(@new_resource, @run_context)
+ Chef::Log.should_receive(:warn).with(@log_str).and_return(true)
+ @provider.action_write
+ end
+
+ it "should write the string to the Chef::Log object at error level" do
+ @new_resource = Chef::Resource::Log.new(@log_str)
+ @new_resource.level :error
+ @provider = Chef::Provider::Log::ChefLog.new(@new_resource, @run_context)
+ Chef::Log.should_receive(:error).with(@log_str).and_return(true)
+ @provider.action_write
+ end
+
+ it "should write the string to the Chef::Log object at fatal level" do
+ @new_resource = Chef::Resource::Log.new(@log_str)
+ @new_resource.level :fatal
+ @provider = Chef::Provider::Log::ChefLog.new(@new_resource, @run_context)
+ Chef::Log.should_receive(:fatal).with(@log_str).and_return(true)
+ @provider.action_write
+ end
+
+end
diff --git a/spec/unit/provider/mdadm_spec.rb b/spec/unit/provider/mdadm_spec.rb
new file mode 100644
index 0000000000..a818c37101
--- /dev/null
+++ b/spec/unit/provider/mdadm_spec.rb
@@ -0,0 +1,128 @@
+#
+# Author:: Joe Williams (<joe@joetify.com>)
+# Copyright:: Copyright (c) 2009 Joe Williams
+# 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 'ostruct'
+
+describe Chef::Provider::Mdadm do
+
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Mdadm.new('/dev/md1')
+ @new_resource.devices ["/dev/sdz1","/dev/sdz2"]
+ @new_resource.level 1
+ @new_resource.chunk 256
+
+ @provider = Chef::Provider::Mdadm.new(@new_resource, @run_context)
+ end
+
+ describe "when determining the current metadevice status" do
+ it "should set the current resources mount point to the new resources mount point" do
+ @provider.stub!(:shell_out!).and_return(OpenStruct.new(:status => 0))
+ @provider.load_current_resource
+ @provider.current_resource.name.should == '/dev/md1'
+ @provider.current_resource.raid_device.should == '/dev/md1'
+ end
+
+ it "determines that the metadevice exists when mdadm exit code is zero" do
+ @provider.stub!(:shell_out!).with("mdadm --detail --test /dev/md1", :returns => [0,4]).and_return(OpenStruct.new(:status => 0))
+ @provider.load_current_resource
+ @provider.current_resource.exists.should be_true
+ end
+
+ it "determines that the metadevice does not exist when mdadm exit code is 4" do
+ @provider.stub!(:shell_out!).with("mdadm --detail --test /dev/md1", :returns => [0,4]).and_return(OpenStruct.new(:status => 4))
+ @provider.load_current_resource
+ @provider.current_resource.exists.should be_false
+ end
+ end
+
+ describe "after the metadevice status is known" do
+ before(:each) do
+ @current_resource = Chef::Resource::Mdadm.new('/dev/md1')
+ @current_resource.devices ["/dev/sdz1","/dev/sdz2"]
+ @current_resource.level 1
+ @current_resource.chunk 256
+ @provider.stub!(:load_current_resource).and_return(true)
+ @provider.current_resource = @current_resource
+ end
+
+ describe "when creating the metadevice" do
+ it "should create the raid device if it doesnt exist" do
+ @current_resource.exists(false)
+ expected_command = "yes | mdadm --create /dev/md1 --chunk=256 --level 1 --metadata=0.90 --raid-devices 2 /dev/sdz1 /dev/sdz2"
+ @provider.should_receive(:shell_out!).with(expected_command)
+ @provider.run_action(:create)
+ end
+
+ it "should specify a bitmap only if set" do
+ @current_resource.exists(false)
+ @new_resource.bitmap('grow')
+ expected_command = "yes | mdadm --create /dev/md1 --chunk=256 --level 1 --metadata=0.90 --bitmap=grow --raid-devices 2 /dev/sdz1 /dev/sdz2"
+ @provider.should_receive(:shell_out!).with(expected_command)
+ @provider.run_action(:create)
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should not create the raid device if it does exist" do
+ @current_resource.exists(true)
+ @provider.should_not_receive(:shell_out!)
+ @provider.run_action(:create)
+ @new_resource.should_not be_updated_by_last_action
+ end
+ end
+
+ describe "when asembling the metadevice" do
+ it "should assemble the raid device if it doesnt exist" do
+ @current_resource.exists(false)
+ expected_mdadm_cmd = "yes | mdadm --assemble /dev/md1 /dev/sdz1 /dev/sdz2"
+ @provider.should_receive(:shell_out!).with(expected_mdadm_cmd)
+ @provider.run_action(:assemble)
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should not assemble the raid device if it doesnt exist" do
+ @current_resource.exists(true)
+ @provider.should_not_receive(:shell_out!)
+ @provider.run_action(:assemble)
+ @new_resource.should_not be_updated_by_last_action
+ end
+ end
+
+ describe "when stopping the metadevice" do
+
+ it "should stop the raid device if it exists" do
+ @current_resource.exists(true)
+ expected_mdadm_cmd = "yes | mdadm --stop /dev/md1"
+ @provider.should_receive(:shell_out!).with(expected_mdadm_cmd)
+ @provider.run_action(:stop)
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should not attempt to stop the raid device if it does not exist" do
+ @current_resource.exists(false)
+ @provider.should_not_receive(:shell_out!)
+ @provider.run_action(:stop)
+ @new_resource.should_not be_updated_by_last_action
+ end
+ end
+ end
+end
diff --git a/spec/unit/provider/mount/mount_spec.rb b/spec/unit/provider/mount/mount_spec.rb
new file mode 100644
index 0000000000..c497a08e40
--- /dev/null
+++ b/spec/unit/provider/mount/mount_spec.rb
@@ -0,0 +1,398 @@
+#
+# Author:: Joshua Timberman (<joshua@opscode.com>)
+# Copyright:: Copyright (c) 2008 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 'ostruct'
+
+describe Chef::Provider::Mount::Mount do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Mount.new("/tmp/foo")
+ @new_resource.device "/dev/sdz1"
+ @new_resource.device_type :device
+ @new_resource.fstype "ext3"
+
+ @new_resource.supports :remount => false
+
+ @provider = Chef::Provider::Mount::Mount.new(@new_resource, @run_context)
+
+ ::File.stub!(:exists?).with("/dev/sdz1").and_return true
+ ::File.stub!(:exists?).with("/tmp/foo").and_return true
+ end
+
+ describe "when discovering the current fs state" do
+ before do
+ @provider.stub!(:shell_out!).and_return(OpenStruct.new(:stdout => ''))
+ ::File.stub!(:foreach).with("/etc/fstab")
+ end
+
+ it "should create a current resource with the same mount point and device" do
+ @provider.load_current_resource
+ @provider.current_resource.name.should == '/tmp/foo'
+ @provider.current_resource.mount_point.should == '/tmp/foo'
+ @provider.current_resource.device.should == '/dev/sdz1'
+ end
+
+ it "should accecpt device_type :uuid" do
+ @new_resource.device_type :uuid
+ @new_resource.device "d21afe51-a0fe-4dc6-9152-ac733763ae0a"
+ @stdout_findfs = mock("STDOUT", :first => "/dev/sdz1")
+ @provider.should_receive(:popen4).with("/sbin/findfs UUID=d21afe51-a0fe-4dc6-9152-ac733763ae0a").and_yield(@pid,@stdin,@stdout_findfs,@stderr).and_return(@status)
+ @provider.load_current_resource()
+ @provider.mountable?
+ end
+
+ describe "when dealing with network mounts" do
+ { "nfs" => "nfsserver:/vol/path",
+ "cifs" => "//cifsserver/share" }.each do |type, fs_spec|
+ it "should detect network fs_spec (#{type})" do
+ @new_resource.device fs_spec
+ @provider.network_device?.should be_true
+ end
+
+ it "should ignore trailing slash and set mounted to true for network mount (#{type})" do
+ @new_resource.device fs_spec
+ @provider.stub!(:shell_out!).and_return(OpenStruct.new(:stdout => "#{fs_spec}/ on /tmp/foo type #{type} (rw)\n"))
+ @provider.load_current_resource
+ @provider.current_resource.mounted.should be_true
+ end
+ end
+ end
+
+ it "should raise an error if the mount device does not exist" do
+ ::File.stub!(:exists?).with("/dev/sdz1").and_return false
+ lambda { @provider.load_current_resource();@provider.mountable? }.should raise_error(Chef::Exceptions::Mount)
+ end
+
+ it "should not call mountable? with load_current_resource - CHEF-1565" do
+ ::File.stub!(:exists?).with("/dev/sdz1").and_return false
+ @provider.should_receive(:mounted?).and_return(true)
+ @provider.should_receive(:enabled?).and_return(true)
+ @provider.should_not_receive(:mountable?)
+ @provider.load_current_resource
+ end
+
+ it "should raise an error if the mount device (uuid) does not exist" do
+ @new_resource.device_type :uuid
+ @new_resource.device "d21afe51-a0fe-4dc6-9152-ac733763ae0a"
+ status_findfs = mock("Status", :exitstatus => 1)
+ stdout_findfs = mock("STDOUT", :first => nil)
+ @provider.should_receive(:popen4).with("/sbin/findfs UUID=d21afe51-a0fe-4dc6-9152-ac733763ae0a").and_yield(@pid,@stdin,stdout_findfs,@stderr).and_return(status_findfs)
+ ::File.should_receive(:exists?).with("").and_return(false)
+ lambda { @provider.load_current_resource();@provider.mountable? }.should raise_error(Chef::Exceptions::Mount)
+ end
+
+ it "should raise an error if the mount point does not exist" do
+ ::File.stub!(:exists?).with("/tmp/foo").and_return false
+ lambda { @provider.load_current_resource();@provider.mountable? }.should raise_error(Chef::Exceptions::Mount)
+ end
+
+ it "does not expect the device to exist for tmpfs" do
+ @new_resource.fstype("tmpfs")
+ @new_resource.device("whatever")
+ lambda { @provider.load_current_resource() }.should_not raise_error
+ end
+
+ it "does not expect the device to exist for Fuse filesystems" do
+ @new_resource.fstype("fuse")
+ @new_resource.device("nilfs#xxx")
+ lambda { @provider.load_current_resource() }.should_not raise_error
+ end
+
+ it "should set mounted true if the mount point is found in the mounts list" do
+ @provider.stub!(:shell_out!).and_return(OpenStruct.new(:stdout => '/dev/sdz1 on /tmp/foo'))
+ @provider.load_current_resource()
+ @provider.current_resource.mounted.should be_true
+ end
+
+ it "should set mounted true if the symlink target of the device is found in the mounts list" do
+ target = "/dev/mapper/target"
+
+ ::File.stub!(:symlink?).with("#{@new_resource.device}").and_return(true)
+ ::File.stub!(:readlink).with("#{@new_resource.device}").and_return(target)
+
+ @provider.stub!(:shell_out!).and_return(OpenStruct.new(:stdout => "/dev/mapper/target on /tmp/foo type ext3 (rw)\n"))
+ @provider.load_current_resource()
+ @provider.current_resource.mounted.should be_true
+ end
+
+ it "should set mounted true if the mount point is found last in the mounts list" do
+ mount = "/dev/sdy1 on #{@new_resource.mount_point} type ext3 (rw)\n"
+ mount << "#{@new_resource.device} on #{@new_resource.mount_point} type ext3 (rw)\n"
+
+ @provider.stub!(:shell_out!).and_return(OpenStruct.new(:stdout => mount))
+ @provider.load_current_resource()
+ @provider.current_resource.mounted.should be_true
+ end
+
+ it "should set mounted false if the mount point is not last in the mounts list" do
+ mount = "#{@new_resource.device} on #{@new_resource.mount_point} type ext3 (rw)\n"
+ mount << "/dev/sdy1 on #{@new_resource.mount_point} type ext3 (rw)\n"
+
+ @provider.stub!(:shell_out!).and_return(OpenStruct.new(:stdout => mount))
+ @provider.load_current_resource()
+ @provider.current_resource.mounted.should be_false
+ end
+
+ it "mounted should be false if the mount point is not found in the mounts list" do
+ @provider.stub!(:shell_out!).and_return(OpenStruct.new(:stdout => "/dev/sdy1 on /tmp/foo type ext3 (rw)\n"))
+ @provider.load_current_resource()
+ @provider.current_resource.mounted.should be_false
+ end
+
+ it "should set enabled to true if the mount point is last in fstab" do
+ fstab1 = "/dev/sdy1 /tmp/foo ext3 defaults 1 2\n"
+ fstab2 = "#{@new_resource.device} #{@new_resource.mount_point} ext3 defaults 1 2\n"
+
+ ::File.stub!(:foreach).with("/etc/fstab").and_yield(fstab1).and_yield(fstab2)
+
+ @provider.load_current_resource
+ @provider.current_resource.enabled.should be_true
+ end
+
+ it "should set enabled to true if the mount point is not last in fstab and mount_point is a substring of another mount" do
+ fstab1 = "#{@new_resource.device} #{@new_resource.mount_point} ext3 defaults 1 2\n"
+ fstab2 = "/dev/sdy1 /tmp/foo/bar ext3 defaults 1 2\n"
+
+ ::File.stub!(:foreach).with("/etc/fstab").and_yield(fstab1).and_yield(fstab2)
+
+ @provider.load_current_resource
+ @provider.current_resource.enabled.should be_true
+ end
+
+ it "should set enabled to true if the symlink target is in fstab" do
+ target = "/dev/mapper/target"
+
+ ::File.stub!(:symlink?).with("#{@new_resource.device}").and_return(true)
+ ::File.stub!(:readlink).with("#{@new_resource.device}").and_return(target)
+
+ fstab = "/dev/sdz1 /tmp/foo ext3 defaults 1 2\n"
+
+ ::File.stub!(:foreach).with("/etc/fstab").and_yield fstab
+
+ @provider.load_current_resource
+ @provider.current_resource.enabled.should be_true
+ end
+
+ it "should set enabled to false if the mount point is not in fstab" do
+ fstab = "/dev/sdy1 #{@new_resource.mount_point} ext3 defaults 1 2\n"
+ ::File.stub!(:foreach).with("/etc/fstab").and_yield fstab
+
+ @provider.load_current_resource
+ @provider.current_resource.enabled.should be_false
+ end
+
+ it "should ignore commented lines in fstab " do
+ fstab = "\# #{@new_resource.device} #{@new_resource.mount_point} ext3 defaults 1 2\n"
+ ::File.stub!(:foreach).with("/etc/fstab").and_yield fstab
+
+ @provider.load_current_resource
+ @provider.current_resource.enabled.should be_false
+ end
+
+ it "should set enabled to false if the mount point is not last in fstab" do
+ line_1 = "#{@new_resource.device} #{@new_resource.mount_point} ext3 defaults 1 2\n"
+ line_2 = "/dev/sdy1 #{@new_resource.mount_point} ext3 defaults 1 2\n"
+ ::File.stub!(:foreach).with("/etc/fstab").and_yield(line_1).and_yield(line_2)
+
+ @provider.load_current_resource
+ @provider.current_resource.enabled.should be_false
+ end
+ end
+
+ context "after the mount's state has been discovered" do
+ before do
+ @current_resource = Chef::Resource::Mount.new("/tmp/foo")
+ @current_resource.device "/dev/sdz1"
+ @current_resource.device_type :device
+ @current_resource.fstype "ext3"
+
+ @provider.current_resource = @current_resource
+ end
+
+ describe "mount_fs" do
+ it "should mount the filesystem if it is not mounted" do
+ @provider.rspec_reset
+ @provider.should_receive(:shell_out!).with("mount -t ext3 -o defaults /dev/sdz1 /tmp/foo")
+ @provider.mount_fs()
+ end
+
+ it "should mount the filesystem with options if options were passed" do
+ options = "rw,noexec,noauto"
+ @new_resource.options(%w{rw noexec noauto})
+ @provider.should_receive(:shell_out!).with("mount -t ext3 -o rw,noexec,noauto /dev/sdz1 /tmp/foo")
+ @provider.mount_fs()
+ end
+
+ it "should mount the filesystem specified by uuid" do
+ @new_resource.device "d21afe51-a0fe-4dc6-9152-ac733763ae0a"
+ @new_resource.device_type :uuid
+ @stdout_findfs = mock("STDOUT", :first => "/dev/sdz1")
+ @provider.stub!(:popen4).with("/sbin/findfs UUID=d21afe51-a0fe-4dc6-9152-ac733763ae0a").and_yield(@pid,@stdin,@stdout_findfs,@stderr).and_return(@status)
+ @stdout_mock = mock('stdout mock')
+ @stdout_mock.stub!(:each).and_yield("#{@new_resource.device} on #{@new_resource.mount_point}")
+ @provider.should_receive(:shell_out!).with("mount -t #{@new_resource.fstype} -o defaults -U #{@new_resource.device} #{@new_resource.mount_point}").and_return(@stdout_mock)
+ @provider.mount_fs()
+ end
+
+ it "should not mount the filesystem if it is mounted" do
+ @current_resource.stub!(:mounted).and_return(true)
+ @provider.should_not_receive(:shell_out!)
+ @provider.mount_fs()
+ end
+
+ end
+
+ describe "umount_fs" do
+ it "should umount the filesystem if it is mounted" do
+ @current_resource.mounted(true)
+ @provider.should_receive(:shell_out!).with("umount /tmp/foo")
+ @provider.umount_fs()
+ end
+
+ it "should not umount the filesystem if it is not mounted" do
+ @current_resource.mounted(false)
+ @provider.should_not_receive(:shell_out!)
+ @provider.umount_fs()
+ end
+ end
+
+ describe "remount_fs" do
+ it "should use mount -o remount if remount is supported" do
+ @new_resource.supports({:remount => true})
+ @current_resource.mounted(true)
+ @provider.should_receive(:shell_out!).with("mount -o remount #{@new_resource.mount_point}")
+ @provider.remount_fs
+ end
+
+ it "should umount and mount if remount is not supported" do
+ @new_resource.supports({:remount => false})
+ @current_resource.mounted(true)
+ @provider.should_receive(:umount_fs)
+ @provider.should_receive(:sleep).with(1)
+ @provider.should_receive(:mount_fs)
+ @provider.remount_fs()
+ end
+
+ it "should not try to remount at all if mounted is false" do
+ @current_resource.mounted(false)
+ @provider.should_not_receive(:shell_out!)
+ @provider.should_not_receive(:umount_fs)
+ @provider.should_not_receive(:mount_fs)
+ @provider.remount_fs()
+ end
+ end
+
+ describe "when enabling the fs" do
+ it "should enable if enabled isn't true" do
+ @current_resource.enabled(false)
+
+ @fstab = StringIO.new
+ ::File.stub!(:open).with("/etc/fstab", "a").and_yield(@fstab)
+ @provider.enable_fs
+ @fstab.string.should match(%r{^/dev/sdz1\s+/tmp/foo\s+ext3\s+defaults\s+0\s+2\s*$})
+ end
+
+ it "should not enable if enabled is true and resources match" do
+ @current_resource.enabled(true)
+ @current_resource.fstype("ext3")
+ @current_resource.options(["defaults"])
+ @current_resource.dump(0)
+ @current_resource.pass(2)
+ ::File.should_not_receive(:open).with("/etc/fstab", "a")
+
+ @provider.enable_fs
+ end
+
+ it "should enable if enabled is true and resources do not match" do
+ @current_resource.enabled(true)
+ @current_resource.fstype("auto")
+ @current_resource.options(["defaults"])
+ @current_resource.dump(0)
+ @current_resource.pass(2)
+ @fstab = StringIO.new
+ ::File.stub(:readlines).and_return([])
+ ::File.should_receive(:open).once.with("/etc/fstab", "w").and_yield(@fstab)
+ ::File.should_receive(:open).once.with("/etc/fstab", "a").and_yield(@fstab)
+
+ @provider.enable_fs
+ end
+ end
+
+ describe "when disabling the fs" do
+ it "should disable if enabled is true" do
+ @current_resource.enabled(true)
+
+ other_mount = "/dev/sdy1 /tmp/foo ext3 defaults 1 2\n"
+ this_mount = "/dev/sdz1 /tmp/foo ext3 defaults 1 2\n"
+
+ @fstab_read = [this_mount, other_mount]
+ ::File.stub!(:readlines).with("/etc/fstab").and_return(@fstab_read)
+ @fstab_write = StringIO.new
+ ::File.stub!(:open).with("/etc/fstab", "w").and_yield(@fstab_write)
+
+ @provider.disable_fs
+ @fstab_write.string.should match(Regexp.escape(other_mount))
+ @fstab_write.string.should_not match(Regexp.escape(this_mount))
+ end
+
+ it "should disable if enabled is true and ignore commented lines" do
+ @current_resource.enabled(true)
+
+ fstab_read = [%q{/dev/sdy1 /tmp/foo ext3 defaults 1 2},
+ %q{/dev/sdz1 /tmp/foo ext3 defaults 1 2},
+ %q{#/dev/sdz1 /tmp/foo ext3 defaults 1 2}]
+ fstab_write = StringIO.new
+
+ ::File.stub!(:readlines).with("/etc/fstab").and_return(fstab_read)
+ ::File.stub!(:open).with("/etc/fstab", "w").and_yield(fstab_write)
+
+ @provider.disable_fs
+ fstab_write.string.should match(%r{^/dev/sdy1 /tmp/foo ext3 defaults 1 2$})
+ fstab_write.string.should match(%r{^#/dev/sdz1 /tmp/foo ext3 defaults 1 2$})
+ fstab_write.string.should_not match(%r{^/dev/sdz1 /tmp/foo ext3 defaults 1 2$})
+ end
+
+ it "should disable only the last entry if enabled is true" do
+ @current_resource.stub!(:enabled).and_return(true)
+ fstab_read = ["/dev/sdz1 /tmp/foo ext3 defaults 1 2\n",
+ "/dev/sdy1 /tmp/foo ext3 defaults 1 2\n",
+ "/dev/sdz1 /tmp/foo ext3 defaults 1 2\n"]
+
+ fstab_write = StringIO.new
+ ::File.stub!(:readlines).with("/etc/fstab").and_return(fstab_read)
+ ::File.stub!(:open).with("/etc/fstab", "w").and_yield(fstab_write)
+
+ @provider.disable_fs
+ fstab_write.string.should == "/dev/sdz1 /tmp/foo ext3 defaults 1 2\n/dev/sdy1 /tmp/foo ext3 defaults 1 2\n"
+ end
+
+ it "should not disable if enabled is false" do
+ @current_resource.stub!(:enabled).and_return(false)
+
+ ::File.stub!(:readlines).with("/etc/fstab").and_return([])
+ ::File.should_not_receive(:open).and_yield(@fstab)
+
+ @provider.disable_fs
+ end
+ end
+ end
+end
diff --git a/spec/unit/provider/mount/windows_spec.rb b/spec/unit/provider/mount/windows_spec.rb
new file mode 100644
index 0000000000..f173ebba22
--- /dev/null
+++ b/spec/unit/provider/mount/windows_spec.rb
@@ -0,0 +1,134 @@
+#
+# Author:: Doug MacEachern (<dougm@vmware.com>)
+# Copyright:: Copyright (c) 2010 VMware, 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'
+
+class Chef
+ class Util
+ class Windows
+ class NetUse
+ end
+ class Volume
+ end
+ end
+ end
+end
+
+GUID = "\\\\?\\Volume{578e72b5-6e70-11df-b5c5-000c29d4a7d9}\\"
+REMOTE = "\\\\server-name\\path"
+
+describe Chef::Provider::Mount::Windows do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Mount.new("X:")
+ @new_resource.device GUID
+ @current_resource = Chef::Resource::Mount.new("X:")
+ Chef::Resource::Mount.stub!(:new).and_return(@current_resource)
+
+ @net_use = mock("Chef::Util::Windows::NetUse")
+ Chef::Util::Windows::NetUse.stub!(:new).and_return(@net_use)
+ @vol = mock("Chef::Util::Windows::Volume")
+ Chef::Util::Windows::Volume.stub!(:new).and_return(@vol)
+
+ @provider = Chef::Provider::Mount::Windows.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ end
+
+ describe "when loading the current resource" do
+ it "should set mounted true if the mount point is found" do
+ @vol.stub!(:device).and_return(@new_resource.device)
+ @current_resource.should_receive(:mounted).with(true)
+ @provider.load_current_resource
+ end
+
+ it "should set mounted false if the mount point is not found" do
+ @vol.stub!(:device).and_raise(ArgumentError)
+ @current_resource.should_receive(:mounted).with(false)
+ @provider.load_current_resource
+ end
+
+ describe "with a local device" do
+ before do
+ @new_resource.device GUID
+ @vol.stub!(:device).and_return(@new_resource.device)
+ @net_use.stub!(:device).and_raise(ArgumentError)
+ end
+
+ it "should determine the device is a volume GUID" do
+ @provider.should_receive(:is_volume).with(@new_resource.device).and_return(true)
+ @provider.load_current_resource
+ end
+ end
+
+ describe "with a remote device" do
+ before do
+ @new_resource.device REMOTE
+ @net_use.stub!(:device).and_return(@new_resource.device)
+ @vol.stub!(:device).and_raise(ArgumentError)
+ end
+
+ it "should determine the device is remote" do
+ @provider.should_receive(:is_volume).with(@new_resource.device).and_return(false)
+ @provider.load_current_resource
+ end
+ end
+
+ describe "when mounting a file system" do
+ before do
+ @new_resource.device GUID
+ @vol.stub!(:add)
+ @vol.stub!(:device).and_raise(ArgumentError)
+ @provider.load_current_resource
+ end
+
+ it "should mount the filesystem if it is not mounted" do
+ @vol.should_receive(:add).with(@new_resource.device)
+ @provider.mount_fs
+ end
+
+ it "should not mount the filesystem if it is mounted" do
+ @vol.should_not_receive(:add)
+ @current_resource.stub!(:mounted).and_return(true)
+ @provider.mount_fs
+ end
+ end
+
+ describe "when unmounting a file system" do
+ before do
+ @new_resource.device GUID
+ @vol.stub!(:delete)
+ @vol.stub!(:device).and_raise(ArgumentError)
+ @provider.load_current_resource
+ end
+
+ it "should umount the filesystem if it is mounted" do
+ @current_resource.stub!(:mounted).and_return(true)
+ @vol.should_receive(:delete)
+ @provider.umount_fs
+ end
+
+ it "should not umount the filesystem if it is not mounted" do
+ @current_resource.stub!(:mounted).and_return(false)
+ @vol.should_not_receive(:delete)
+ @provider.umount_fs
+ end
+ end
+ end
+end
diff --git a/spec/unit/provider/mount_spec.rb b/spec/unit/provider/mount_spec.rb
new file mode 100644
index 0000000000..921bde4cc9
--- /dev/null
+++ b/spec/unit/provider/mount_spec.rb
@@ -0,0 +1,160 @@
+#
+# Author:: Joshua Timberman (<joshua@opscode.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::Mount do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Mount.new('/tmp/foo')
+ @new_resource.device "/dev/sdz1"
+ @new_resource.name "/tmp/foo"
+ @new_resource.mount_point "/tmp/foo"
+ @new_resource.fstype "ext3"
+
+ @current_resource = Chef::Resource::Mount.new('/tmp/foo')
+ @current_resource.device "/dev/sdz1"
+ @current_resource.name "/tmp/foo"
+ @current_resource.mount_point "/tmp/foo"
+ @current_resource.fstype "ext3"
+
+ @provider = Chef::Provider::Mount.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ end
+
+ describe "when the target state is a mounted filesystem" do
+
+ it "should mount the filesystem if it isn't mounted" do
+ @current_resource.stub!(:mounted).and_return(false)
+ @provider.should_receive(:mount_fs).with.and_return(true)
+ @provider.run_action(:mount)
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should not mount the filesystem if it is mounted" do
+ @current_resource.stub!(:mounted).and_return(true)
+ @provider.should_not_receive(:mount_fs).and_return(true)
+ @provider.run_action(:mount)
+ @new_resource.should_not be_updated_by_last_action
+ end
+
+ end
+
+ describe "when the target state is an unmounted filesystem" do
+ it "should umount the filesystem if it is mounted" do
+ @current_resource.stub!(:mounted).and_return(true)
+ @provider.should_receive(:umount_fs).with.and_return(true)
+ @provider.run_action(:umount)
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should not umount the filesystem if it is not mounted" do
+ @current_resource.stub!(:mounted).and_return(false)
+ @provider.should_not_receive(:umount_fs).and_return(true)
+ @provider.run_action(:umount)
+ @new_resource.should_not be_updated_by_last_action
+ end
+ end
+
+ describe "when the filesystem should be remounted and the resource supports remounting" do
+ before do
+ @new_resource.supports[:remount] = true
+ end
+
+ it "should remount the filesystem if it is mounted" do
+ @current_resource.stub!(:mounted).and_return(true)
+ @provider.should_receive(:remount_fs).and_return(true)
+ @provider.run_action(:remount)
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should not remount the filesystem if it is not mounted" do
+ @current_resource.stub!(:mounted).and_return(false)
+ @provider.should_not_receive(:remount_fs)
+ @provider.run_action(:remount)
+ @new_resource.should_not be_updated_by_last_action
+ end
+ end
+ describe "when the filesystem should be remounted and the resource does not support remounting" do
+ before do
+ @new_resource.supports[:remount] = false
+ end
+
+ it "should fail to remount the filesystem" do
+ @provider.should_not_receive(:remount_fs)
+ lambda {@provider.run_action(:remount)}.should raise_error(Chef::Exceptions::UnsupportedAction)
+ @new_resource.should_not be_updated_by_last_action
+ end
+
+ end
+ describe "when enabling the filesystem to be mounted" do
+ it "should enable the mount if it isn't enable" do
+ @current_resource.stub!(:enabled).and_return(false)
+ @provider.should_receive(:enable_fs).with.and_return(true)
+ @provider.run_action(:enable)
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should not enable the mount if it is enabled" do
+ @current_resource.stub!(:enabled).and_return(true)
+ @provider.should_not_receive(:enable_fs).with.and_return(true)
+ @provider.run_action(:enable)
+ @new_resource.should_not be_updated_by_last_action
+ end
+ end
+
+ describe "when the target state is to disable the mount" do
+ it "should disable the mount if it is enabled" do
+ @current_resource.stub!(:enabled).and_return(true)
+ @provider.should_receive(:disable_fs).with.and_return(true)
+ @provider.run_action(:disable)
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should not disable the mount if it isn't enabled" do
+ @current_resource.stub!(:enabled).and_return(false)
+ @provider.should_not_receive(:disable_fs).with.and_return(true)
+ @provider.run_action(:disable)
+ @new_resource.should_not be_updated_by_last_action
+ end
+ end
+
+
+ it "should delegates the mount implementation to subclasses" do
+ lambda { @provider.mount_fs }.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+
+ it "should delegates the umount implementation to subclasses" do
+ lambda { @provider.umount_fs }.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+
+ it "should delegates the remount implementation to subclasses" do
+ lambda { @provider.remount_fs }.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+
+ it "should delegates the enable implementation to subclasses" do
+ lambda { @provider.enable_fs }.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+
+ it "should delegates the disable implementation to subclasses" do
+ lambda { @provider.disable_fs }.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+end
diff --git a/spec/unit/provider/ohai_spec.rb b/spec/unit/provider/ohai_spec.rb
new file mode 100644
index 0000000000..8402c92e97
--- /dev/null
+++ b/spec/unit/provider/ohai_spec.rb
@@ -0,0 +1,85 @@
+#
+# Author:: Michael Leinartas (<mleinartas@gmail.com>)
+# Copyright:: Copyright (c) 2010 Michael Leinartas
+# 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/run_context'
+
+describe Chef::Provider::Ohai do
+ before(:each) do
+ # Copied from client_spec
+ @fqdn = "hostname.domainname"
+ @hostname = "hostname"
+ @platform = "example-platform"
+ @platform_version = "example-platform"
+ Chef::Config[:node_name] = @fqdn
+ mock_ohai = {
+ :fqdn => @fqdn,
+ :hostname => @hostname,
+ :platform => @platform,
+ :platform_version => @platform_version,
+ :data => {
+ :origdata => "somevalue"
+ },
+ :data2 => {
+ :origdata => "somevalue",
+ :newdata => "somevalue"
+ }
+ }
+ mock_ohai.stub!(:all_plugins).and_return(true)
+ mock_ohai.stub!(:require_plugin).and_return(true)
+ mock_ohai.stub!(:data).and_return(mock_ohai[:data],
+ mock_ohai[:data2])
+ Ohai::System.stub!(:new).and_return(mock_ohai)
+ Chef::Platform.stub!(:find_platform_and_version).and_return({ "platform" => @platform,
+ "platform_version" => @platform_version})
+ # Fake node with a dummy save
+ @node = Chef::Node.new
+ @node.name(@fqdn)
+ @node.stub!(:save).and_return(@node)
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Ohai.new("ohai_reload")
+ ohai = Ohai::System.new
+ ohai.all_plugins
+ @node.consume_external_attrs(ohai.data,{})
+
+ @provider = Chef::Provider::Ohai.new(@new_resource, @run_context)
+ end
+
+ describe "when reloading ohai" do
+ before do
+ @node.automatic_attrs[:origdata] = 'somevalue'
+ end
+
+ it "applies updated ohai data to the node" do
+ @node[:origdata].should == 'somevalue'
+ @node[:newdata].should be_nil
+ @provider.run_action(:reload)
+ @node[:origdata].should == 'somevalue'
+ @node[:newdata].should == 'somevalue'
+ end
+
+ it "should reload a specific plugin and cause node to pick up new values" do
+ @new_resource.plugin "someplugin"
+ @provider.run_action(:reload)
+ @node[:origdata].should == 'somevalue'
+ @node[:newdata].should == 'somevalue'
+ end
+ end
+end
diff --git a/spec/unit/provider/package/apt_spec.rb b/spec/unit/provider/package/apt_spec.rb
new file mode 100644
index 0000000000..06ada5189e
--- /dev/null
+++ b/spec/unit/provider/package/apt_spec.rb
@@ -0,0 +1,351 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 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 'ostruct'
+
+describe Chef::Provider::Package::Apt do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Package.new("irssi", @run_context)
+ @current_resource = Chef::Resource::Package.new("irssi", @run_context)
+
+ @status = mock("Status", :exitstatus => 0)
+ @provider = Chef::Provider::Package::Apt.new(@new_resource, @run_context)
+ Chef::Resource::Package.stub!(:new).and_return(@current_resource)
+ @stdin = StringIO.new
+ @stdout =<<-PKG_STATUS
+irssi:
+ Installed: (none)
+ Candidate: 0.8.14-1ubuntu4
+ Version table:
+ 0.8.14-1ubuntu4 0
+ 500 http://us.archive.ubuntu.com/ubuntu/ lucid/main Packages
+PKG_STATUS
+ @stderr = StringIO.new
+ @pid = 12345
+ @shell_out = OpenStruct.new(:stdout => @stdout,:stdin => @stdin,:stderr => @stderr,:status => @status,:exitstatus => 0)
+ end
+
+ describe "when loading current resource" do
+
+ it "should create a current resource with the name of the new_resource" do
+ @provider.should_receive(:shell_out!).and_return(@shell_out)
+ Chef::Resource::Package.should_receive(:new).and_return(@current_resource)
+ @provider.load_current_resource
+ end
+
+ it "should set the current resources package name to the new resources package name" do
+ @provider.should_receive(:shell_out!).and_return(@shell_out)
+ @current_resource.should_receive(:package_name).with(@new_resource.package_name)
+ @provider.load_current_resource
+ end
+
+ it "should run apt-cache policy with the package name" do
+ @provider.should_receive(:shell_out!).with("apt-cache policy #{@new_resource.package_name}").and_return(@shell_out)
+ @provider.load_current_resource
+ end
+
+ it "should set the installed version to nil on the current resource if package state is not installed" do
+ @provider.should_receive(:shell_out!).and_return(@shell_out)
+ @current_resource.should_receive(:version).with(nil).and_return(true)
+ @provider.load_current_resource
+ end
+
+ it "should set the installed version if package has one" do
+ @stdout.replace(<<-INSTALLED)
+sudo:
+ Installed: 1.7.2p1-1ubuntu5.3
+ Candidate: 1.7.2p1-1ubuntu5.3
+ Version table:
+ *** 1.7.2p1-1ubuntu5.3 0
+ 500 http://us.archive.ubuntu.com/ubuntu/ lucid-updates/main Packages
+ 500 http://security.ubuntu.com/ubuntu/ lucid-security/main Packages
+ 100 /var/lib/dpkg/status
+ 1.7.2p1-1ubuntu5 0
+ 500 http://us.archive.ubuntu.com/ubuntu/ lucid/main Packages
+INSTALLED
+ @provider.should_receive(:shell_out!).and_return(@shell_out)
+ @provider.load_current_resource
+ @current_resource.version.should == "1.7.2p1-1ubuntu5.3"
+ @provider.candidate_version.should eql("1.7.2p1-1ubuntu5.3")
+ end
+
+ it "should return the current resouce" do
+ @provider.should_receive(:shell_out!).and_return(@shell_out)
+ @provider.load_current_resource.should eql(@current_resource)
+ end
+
+ # libmysqlclient-dev is a real package in newer versions of debian + ubuntu
+ # list of virtual packages: http://www.debian.org/doc/packaging-manuals/virtual-package-names-list.txt
+ it "should not install the virtual package there is a single provider package and it is installed" do
+ @new_resource.package_name("libmysqlclient15-dev")
+ virtual_package_out=<<-VPKG_STDOUT
+libmysqlclient15-dev:
+ Installed: (none)
+ Candidate: (none)
+ Version table:
+VPKG_STDOUT
+ virtual_package = mock(:stdout => virtual_package_out,:exitstatus => 0)
+ @provider.should_receive(:shell_out!).with("apt-cache policy libmysqlclient15-dev").and_return(virtual_package)
+ showpkg_out =<<-SHOWPKG_STDOUT
+Package: libmysqlclient15-dev
+Versions:
+
+Reverse Depends:
+ libmysqlclient-dev,libmysqlclient15-dev
+ libmysqlclient-dev,libmysqlclient15-dev
+ libmysqlclient-dev,libmysqlclient15-dev
+ libmysqlclient-dev,libmysqlclient15-dev
+ libmysqlclient-dev,libmysqlclient15-dev
+ libmysqlclient-dev,libmysqlclient15-dev
+Dependencies:
+Provides:
+Reverse Provides:
+libmysqlclient-dev 5.1.41-3ubuntu12.7
+libmysqlclient-dev 5.1.41-3ubuntu12.10
+libmysqlclient-dev 5.1.41-3ubuntu12
+SHOWPKG_STDOUT
+ showpkg = mock(:stdout => showpkg_out,:exitstatus => 0)
+ @provider.should_receive(:shell_out!).with("apt-cache showpkg libmysqlclient15-dev").and_return(showpkg)
+ real_package_out=<<-RPKG_STDOUT
+libmysqlclient-dev:
+ Installed: 5.1.41-3ubuntu12.10
+ Candidate: 5.1.41-3ubuntu12.10
+ Version table:
+ *** 5.1.41-3ubuntu12.10 0
+ 500 http://us.archive.ubuntu.com/ubuntu/ lucid-updates/main Packages
+ 100 /var/lib/dpkg/status
+ 5.1.41-3ubuntu12.7 0
+ 500 http://security.ubuntu.com/ubuntu/ lucid-security/main Packages
+ 5.1.41-3ubuntu12 0
+ 500 http://us.archive.ubuntu.com/ubuntu/ lucid/main Packages
+RPKG_STDOUT
+ real_package = mock(:stdout => real_package_out,:exitstatus => 0)
+ @provider.should_receive(:shell_out!).with("apt-cache policy libmysqlclient-dev").and_return(real_package)
+ @provider.should_not_receive(:run_command_with_systems_locale)
+ @provider.load_current_resource
+ end
+
+ it "should raise an exception if you specify a virtual package with multiple provider packages" do
+ @new_resource.package_name("mp3-decoder")
+ virtual_package_out=<<-VPKG_STDOUT
+mp3-decoder:
+ Installed: (none)
+ Candidate: (none)
+ Version table:
+VPKG_STDOUT
+ virtual_package = mock(:stdout => virtual_package_out,:exitstatus => 0)
+ @provider.should_receive(:shell_out!).with("apt-cache policy mp3-decoder").and_return(virtual_package)
+ showpkg_out=<<-SHOWPKG_STDOUT
+Package: mp3-decoder
+Versions:
+
+Reverse Depends:
+ nautilus,mp3-decoder
+ vux,mp3-decoder
+ plait,mp3-decoder
+ ecasound,mp3-decoder
+ nautilus,mp3-decoder
+Dependencies:
+Provides:
+Reverse Provides:
+vlc-nox 1.0.6-1ubuntu1.8
+vlc 1.0.6-1ubuntu1.8
+vlc-nox 1.0.6-1ubuntu1
+vlc 1.0.6-1ubuntu1
+opencubicplayer 1:0.1.17-2
+mpg321 0.2.10.6
+mpg123 1.12.1-0ubuntu1
+SHOWPKG_STDOUT
+ showpkg = mock(:stdout => showpkg_out,:exitstatus => 0)
+ @provider.should_receive(:shell_out!).with("apt-cache showpkg mp3-decoder").and_return(showpkg)
+ lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Package)
+ end
+
+ it "should run apt-cache policy with the default_release option, if there is one and provider is explicitly defined" do
+ @new_resource = Chef::Resource::AptPackage.new("irssi", @run_context)
+ @provider = Chef::Provider::Package::Apt.new(@new_resource, @run_context)
+
+ @new_resource.stub!(:default_release).and_return("lenny-backports")
+ @new_resource.stub!(:provider).and_return("Chef::Provider::Package::Apt")
+ @provider.should_receive(:shell_out!).with("apt-cache -o APT::Default-Release=lenny-backports policy irssi").and_return(@shell_out)
+ @provider.load_current_resource
+ end
+
+ end
+
+ describe "install_package" do
+ it "should run apt-get install with the package name and version" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "apt-get -q -y install irssi=0.8.12-7",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ })
+ @provider.install_package("irssi", "0.8.12-7")
+ end
+
+ it "should run apt-get install with the package name and version and options if specified" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "apt-get -q -y --force-yes install irssi=0.8.12-7",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ })
+ @new_resource.stub!(:options).and_return("--force-yes")
+
+ @provider.install_package("irssi", "0.8.12-7")
+ end
+
+ it "should run apt-get install with the package name and version and default_release if there is one and provider is explicitly defined" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "apt-get -q -y -o APT::Default-Release=lenny-backports install irssi=0.8.12-7",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ })
+ @new_resource.stub!(:default_release).and_return("lenny-backports")
+ @new_resource.stub!(:provider).and_return("Chef::Provider::Package::Apt")
+
+ @provider.install_package("irssi", "0.8.12-7")
+ end
+ end
+
+ describe Chef::Provider::Package::Apt, "upgrade_package" do
+
+ it "should run install_package with the name and version" do
+ @provider.should_receive(:install_package).with("irssi", "0.8.12-7")
+ @provider.upgrade_package("irssi", "0.8.12-7")
+ end
+ end
+
+ describe Chef::Provider::Package::Apt, "remove_package" do
+
+ it "should run apt-get remove with the package name" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "apt-get -q -y remove irssi",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ })
+ @provider.remove_package("irssi", "0.8.12-7")
+ end
+
+ it "should run apt-get remove with the package name and options if specified" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "apt-get -q -y --force-yes remove irssi",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ })
+ @new_resource.stub!(:options).and_return("--force-yes")
+
+ @provider.remove_package("irssi", "0.8.12-7")
+ end
+ end
+
+ describe "when purging a package" do
+
+ it "should run apt-get purge with the package name" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "apt-get -q -y purge irssi",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ })
+ @provider.purge_package("irssi", "0.8.12-7")
+ end
+
+ it "should run apt-get purge with the package name and options if specified" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "apt-get -q -y --force-yes purge irssi",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ })
+ @new_resource.stub!(:options).and_return("--force-yes")
+
+ @provider.purge_package("irssi", "0.8.12-7")
+ end
+ end
+
+ describe "when preseeding a package" do
+ before(:each) do
+ @provider.stub!(:get_preseed_file).and_return("/tmp/irssi-0.8.12-7.seed")
+ @provider.stub!(:run_command_with_systems_locale).and_return(true)
+ end
+
+ it "should get the full path to the preseed response file" do
+ @provider.should_receive(:get_preseed_file).with("irssi", "0.8.12-7").and_return("/tmp/irssi-0.8.12-7.seed")
+ file = @provider.get_preseed_file("irssi", "0.8.12-7")
+ @provider.preseed_package(file)
+ end
+
+ it "should run debconf-set-selections on the preseed file if it has changed" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "debconf-set-selections /tmp/irssi-0.8.12-7.seed",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ }).and_return(true)
+ file = @provider.get_preseed_file("irssi", "0.8.12-7")
+ @provider.preseed_package(file)
+ end
+
+ it "should not run debconf-set-selections if the preseed file has not changed" do
+ @provider.stub(:check_package_state)
+ @current_resource.version "0.8.11"
+ @new_resource.response_file "/tmp/file"
+ @provider.stub!(:get_preseed_file).and_return(false)
+ @provider.should_not_receive(:run_command_with_systems_locale)
+ @provider.run_action(:reconfig)
+ end
+ end
+
+ describe "when reconfiguring a package" do
+ before(:each) do
+ @provider.stub!(:run_command_with_systems_locale).and_return(true)
+ end
+
+ it "should run dpkg-reconfigure package" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "dpkg-reconfigure irssi",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ }).and_return(true)
+ @provider.reconfig_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.is_virtual_package = true
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "apt-get -q -y install libmysqlclient-dev",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ })
+ @provider.install_package("libmysqlclient-dev", "not_a_real_version")
+ end
+ end
+end
diff --git a/spec/unit/provider/package/dpkg_spec.rb b/spec/unit/provider/package/dpkg_spec.rb
new file mode 100644
index 0000000000..aa22e0a2a3
--- /dev/null
+++ b/spec/unit/provider/package/dpkg_spec.rb
@@ -0,0 +1,216 @@
+#
+# Author:: Bryan McLellan (btm@loftninjas.org)
+# Copyright:: Copyright (c) 2009 Bryan McLellan
+# 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::Package::Dpkg do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Package.new("wget")
+ @new_resource.source "/tmp/wget_1.11.4-1ubuntu1_amd64.deb"
+
+ @provider = Chef::Provider::Package::Dpkg.new(@new_resource, @run_context)
+
+ @stdin = StringIO.new
+ @stdout = StringIO.new
+ @status = mock("Status", :exitstatus => 0)
+ @stderr = StringIO.new
+ @pid = mock("PID")
+ @provider.stub!(:popen4).and_return(@status)
+
+ ::File.stub!(:exists?).and_return(true)
+ end
+
+ describe "when loading the current resource state" do
+
+ it "should create a current resource with the name of the new_resource" do
+ @provider.load_current_resource
+ @provider.current_resource.package_name.should == "wget"
+ end
+
+ it "should raise an exception if a source is supplied but not found" do
+ @provider.load_current_resource
+ @provider.define_resource_requirements
+ ::File.stub!(:exists?).and_return(false)
+ lambda { @provider.run_action(:install) }.should raise_error(Chef::Exceptions::Package)
+ end
+
+ describe 'gets the source package version from dpkg-deb' do
+ def check_version(version)
+ @stdout = StringIO.new("wget\t#{version}")
+ @provider.stub!(:popen4).with("dpkg-deb -W #{@new_resource.source}").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.load_current_resource
+ @provider.current_resource.package_name.should == "wget"
+ @new_resource.version.should == version
+ end
+
+ it 'if short version provided' do
+ check_version('1.11.4')
+ end
+
+ it 'if extended version provided' do
+ check_version('1.11.4-1ubuntu1')
+ end
+
+ it 'if distro-specific version provided' do
+ check_version('1.11.4-1ubuntu1~lucid')
+ end
+ end
+
+ it "gets the source package name from dpkg-deb correctly when the package name has `-', `+' or `.' characters" do
+ @stdout = StringIO.new("f.o.o-pkg++2\t1.11.4-1ubuntu1")
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.load_current_resource
+ @provider.current_resource.package_name.should == "f.o.o-pkg++2"
+ end
+
+ it "should raise an exception if the source is not set but we are installing" do
+ @new_resource = Chef::Resource::Package.new("wget")
+ @provider.new_resource = @new_resource
+ @provider.define_resource_requirements
+ @provider.load_current_resource
+ lambda { @provider.run_action(:install)}.should raise_error(Chef::Exceptions::Package)
+ end
+
+ it "should return the current version installed if found by dpkg" do
+ @stdout = StringIO.new(<<-DPKG_S)
+Package: wget
+Status: install ok installed
+Priority: important
+Section: web
+Installed-Size: 1944
+Maintainer: Ubuntu Core developers <ubuntu-devel-discuss@lists.ubuntu.com>
+Architecture: amd64
+Version: 1.11.4-1ubuntu1
+Config-Version: 1.11.4-1ubuntu1
+Depends: libc6 (>= 2.8~20080505), libssl0.9.8 (>= 0.9.8f-5)
+Conflicts: wget-ssl
+DPKG_S
+ @provider.stub!(:popen4).with("dpkg -s wget").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+
+ @provider.load_current_resource
+ @provider.current_resource.version.should == "1.11.4-1ubuntu1"
+ end
+
+ it "should raise an exception if dpkg fails to run" do
+ @status = mock("Status", :exitstatus => -1)
+ @provider.stub!(:popen4).and_return(@status)
+ lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Package)
+ end
+ end
+
+ describe Chef::Provider::Package::Dpkg, "install and upgrade" do
+ it "should run dpkg -i with the package source" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "dpkg -i /tmp/wget_1.11.4-1ubuntu1_amd64.deb",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ })
+ @provider.install_package("wget", "1.11.4-1ubuntu1")
+ end
+
+ it "should run dpkg -i if the package is a path and the source is nil" do
+ @new_resource = Chef::Resource::Package.new("/tmp/wget_1.11.4-1ubuntu1_amd64.deb")
+ @provider = Chef::Provider::Package::Dpkg.new(@new_resource, @run_context)
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "dpkg -i /tmp/wget_1.11.4-1ubuntu1_amd64.deb",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ })
+ @provider.install_package("/tmp/wget_1.11.4-1ubuntu1_amd64.deb", "1.11.4-1ubuntu1")
+ end
+
+ it "should run dpkg -i if the package is a path and the source is nil for an upgrade" do
+ @new_resource = Chef::Resource::Package.new("/tmp/wget_1.11.4-1ubuntu1_amd64.deb")
+ @provider = Chef::Provider::Package::Dpkg.new(@new_resource, @run_context)
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "dpkg -i /tmp/wget_1.11.4-1ubuntu1_amd64.deb",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ })
+ @provider.upgrade_package("/tmp/wget_1.11.4-1ubuntu1_amd64.deb", "1.11.4-1ubuntu1")
+ end
+
+ it "should run dpkg -i with the package source and options if specified" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "dpkg -i --force-yes /tmp/wget_1.11.4-1ubuntu1_amd64.deb",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ })
+ @new_resource.stub!(:options).and_return("--force-yes")
+
+ @provider.install_package("wget", "1.11.4-1ubuntu1")
+ end
+ it "should upgrade by running install_package" do
+ @provider.should_receive(:install_package).with("wget", "1.11.4-1ubuntu1")
+ @provider.upgrade_package("wget", "1.11.4-1ubuntu1")
+ end
+ end
+
+ describe Chef::Provider::Package::Dpkg, "remove and purge" do
+ it "should run dpkg -r to remove the package" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "dpkg -r wget",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ })
+ @provider.remove_package("wget", "1.11.4-1ubuntu1")
+ end
+
+ it "should run dpkg -r to remove the package with options if specified" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "dpkg -r --force-yes wget",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ })
+ @new_resource.stub!(:options).and_return("--force-yes")
+
+ @provider.remove_package("wget", "1.11.4-1ubuntu1")
+ end
+
+ it "should run dpkg -P to purge the package" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "dpkg -P wget",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ })
+ @provider.purge_package("wget", "1.11.4-1ubuntu1")
+ end
+
+ it "should run dpkg -P to purge the package with options if specified" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "dpkg -P --force-yes wget",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ })
+ @new_resource.stub!(:options).and_return("--force-yes")
+
+ @provider.purge_package("wget", "1.11.4-1ubuntu1")
+ end
+ end
+end
diff --git a/spec/unit/provider/package/easy_install_spec.rb b/spec/unit/provider/package/easy_install_spec.rb
new file mode 100644
index 0000000000..02f8399af8
--- /dev/null
+++ b/spec/unit/provider/package/easy_install_spec.rb
@@ -0,0 +1,112 @@
+#
+# Author:: Joe Williams (<joe@joetify.com>)
+# Copyright:: Copyright (c) 2009 Joe Williams
+# 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::Package::EasyInstall do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::EasyInstallPackage.new('boto')
+ @new_resource.version('1.8d')
+
+ @current_resource = Chef::Resource::EasyInstallPackage.new('boto')
+ @current_resource.version('1.8d')
+
+ @provider = Chef::Provider::Package::EasyInstall.new(@new_resource, @run_context)
+ Chef::Resource::Package.stub!(:new).and_return(@current_resource)
+
+ @stdin = StringIO.new
+ @stdout = StringIO.new
+ @status = mock("Status", :exitstatus => 0)
+ @stderr = StringIO.new
+ @pid = 2342
+ @provider.stub!(:popen4).and_return(@status)
+ end
+
+ describe "easy_install_binary_path" do
+ it "should return a Chef::Provider::EasyInstall object" do
+ provider = Chef::Provider::Package::EasyInstall.new(@node, @new_resource)
+ provider.should be_a_kind_of(Chef::Provider::Package::EasyInstall)
+ end
+
+ it "should set the current resources package name to the new resources package name" do
+ $stdout.stub!(:write)
+ @current_resource.should_receive(:package_name).with(@new_resource.package_name)
+ @provider.load_current_resource
+ end
+
+ it "should return a relative path to easy_install if no easy_install_binary is given" do
+ @provider.easy_install_binary_path.should eql("easy_install")
+ end
+
+ it "should return a specific path to easy_install if a easy_install_binary is given" do
+ @new_resource.should_receive(:easy_install_binary).and_return("/opt/local/bin/custom/easy_install")
+ @provider.easy_install_binary_path.should eql("/opt/local/bin/custom/easy_install")
+ end
+
+ end
+
+ describe "actions_on_package" do
+ it "should run easy_install with the package name and version" do
+ @provider.should_receive(:run_command).with({
+ :command => "easy_install \"boto==1.8d\""
+ })
+ @provider.install_package("boto", "1.8d")
+ end
+
+ it "should run easy_install with the package name and version and specified options" do
+ @provider.should_receive(:run_command).with({
+ :command => "easy_install --always-unzip \"boto==1.8d\""
+ })
+ @new_resource.stub!(:options).and_return("--always-unzip")
+ @provider.install_package("boto", "1.8d")
+ end
+
+ it "should run easy_install with the package name and version" do
+ @provider.should_receive(:run_command).with({
+ :command => "easy_install \"boto==1.8d\""
+ })
+ @provider.upgrade_package("boto", "1.8d")
+ end
+
+ it "should run easy_install -m with the package name and version" do
+ @provider.should_receive(:run_command).with({
+ :command => "easy_install -m boto"
+ })
+ @provider.remove_package("boto", "1.8d")
+ end
+
+ it "should run easy_install -m with the package name and version and specified options" do
+ @provider.should_receive(:run_command).with({
+ :command => "easy_install -x -m boto"
+ })
+ @new_resource.stub!(:options).and_return("-x")
+ @provider.remove_package("boto", "1.8d")
+ end
+
+ it "should run easy_install -m with the package name and version" do
+ @provider.should_receive(:run_command).with({
+ :command => "easy_install -m boto"
+ })
+ @provider.purge_package("boto", "1.8d")
+ end
+
+ end
+end
diff --git a/spec/unit/provider/package/freebsd_spec.rb b/spec/unit/provider/package/freebsd_spec.rb
new file mode 100644
index 0000000000..270c85d934
--- /dev/null
+++ b/spec/unit/provider/package/freebsd_spec.rb
@@ -0,0 +1,259 @@
+#
+# Authors:: Bryan McLellan (btm@loftninjas.org)
+# Matthew Landauer (matthew@openaustralia.org)
+# Copyright:: Copyright (c) 2009 Bryan McLellan, Matthew Landauer
+# 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 'ostruct'
+
+describe Chef::Provider::Package::Freebsd, "load_current_resource" do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Package.new("zsh")
+ @current_resource = Chef::Resource::Package.new("zsh")
+
+ @provider = Chef::Provider::Package::Freebsd.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ ::File.stub!(:exist?).with('/usr/ports/Makefile').and_return(false)
+ end
+
+ describe "when determining the current package state" do
+ before do
+ @provider.stub!(:ports_candidate_version).and_return("4.3.6")
+ end
+
+ it "should create a current resource with the name of the new_resource" do
+ current_resource = Chef::Provider::Package::Freebsd.new(@new_resource, @run_context).current_resource
+ current_resource.name.should == "zsh"
+ end
+
+ it "should return a version if the package is installed" do
+ @provider.should_receive(:current_installed_version).and_return("4.3.6_7")
+ @provider.load_current_resource
+ @current_resource.version.should == "4.3.6_7"
+ end
+
+ it "should return nil if the package is not installed" do
+ @provider.should_receive(:current_installed_version).and_return(nil)
+ @provider.load_current_resource
+ @current_resource.version.should be_nil
+ end
+
+ it "should return a candidate version if it exists" do
+ @provider.should_receive(:current_installed_version).and_return(nil)
+ @provider.load_current_resource
+ @provider.candidate_version.should eql("4.3.6")
+ end
+ end
+
+ describe "when querying for package state and attributes" do
+ before do
+ #@new_resource = Chef::Resource::Package.new("zsh")
+
+ #@provider = Chef::Provider::Package::Freebsd.new(@node, @new_resource)
+
+ #@status = mock("Status", :exitstatus => 0)
+ #@stdin = mock("STDIN", :null_object => true)
+ #@stdout = mock("STDOUT", :null_object => true)
+ #@stderr = mock("STDERR", :null_object => true)
+ #@pid = mock("PID", :null_object => true)
+ end
+
+ it "should return the version number when it is installed" do
+ pkg_info = OpenStruct.new(:stdout => "zsh-4.3.6_7")
+ @provider.should_receive(:shell_out!).with('pkg_info -E "zsh*"', :env => nil, :returns => [0,1]).and_return(pkg_info)
+ #@provider.should_receive(:popen4).with('pkg_info -E "zsh*"').and_yield(@pid, @stdin, ["zsh-4.3.6_7"], @stderr).and_return(@status)
+ @provider.stub!(:package_name).and_return("zsh")
+ @provider.current_installed_version.should == "4.3.6_7"
+ end
+
+ it "does not set the current version number when the package is not installed" do
+ pkg_info = OpenStruct.new(:stdout => "")
+ @provider.should_receive(:shell_out!).with('pkg_info -E "zsh*"', :env => nil, :returns => [0,1]).and_return(pkg_info)
+ @provider.stub!(:package_name).and_return("zsh")
+ @provider.current_installed_version.should be_nil
+ end
+
+ it "should return the port path for a valid port name" do
+ whereis = OpenStruct.new(:stdout => "zsh: /usr/ports/shells/zsh")
+ @provider.should_receive(:shell_out!).with("whereis -s zsh", :env => nil).and_return(whereis)
+ #@provider.should_receive(:popen4).with("whereis -s zsh").and_yield(@pid, @stdin, ["zsh: /usr/ports/shells/zsh"], @stderr).and_return(@status)
+ @provider.stub!(:port_name).and_return("zsh")
+ @provider.port_path.should == "/usr/ports/shells/zsh"
+ end
+
+ # Not happy with the form of these tests as they are far too closely tied to the implementation and so very fragile.
+ it "should return the ports candidate version when given a valid port path" do
+ @provider.stub!(:port_path).and_return("/usr/ports/shells/zsh")
+ make_v = OpenStruct.new(:stdout => "4.3.6\n")
+ @provider.should_receive(:shell_out!).with("make -V PORTVERSION", {:cwd=>"/usr/ports/shells/zsh", :returns=>[0, 1], :env=>nil}).and_return(make_v)
+ @provider.ports_candidate_version.should == "4.3.6"
+ end
+
+ it "should figure out the package name when we have ports" do
+ ::File.stub!(:exist?).with('/usr/ports/Makefile').and_return(true)
+ @provider.stub!(:port_path).and_return("/usr/ports/shells/zsh")
+ make_v = OpenStruct.new(:stdout => "zsh-4.3.6_7\n")
+ @provider.should_receive(:shell_out!).with("make -V PKGNAME", {:cwd=>"/usr/ports/shells/zsh", :env=>nil, :returns=>[0, 1]}).and_return(make_v)
+ #@provider.should_receive(:ports_makefile_variable_value).with("PKGNAME").and_return("zsh-4.3.6_7")
+ @provider.package_name.should == "zsh"
+ end
+ end
+
+ describe Chef::Provider::Package::Freebsd, "install_package" do
+ before(:each) do
+ @cmd_result = OpenStruct.new(:status => true)
+
+ @provider.current_resource = @current_resource
+ @provider.stub!(:package_name).and_return("zsh")
+ @provider.stub!(:latest_link_name).and_return("zsh")
+ @provider.stub!(:port_path).and_return("/usr/ports/shells/zsh")
+ end
+
+ it "should run pkg_add -r with the package name" do
+ @provider.should_receive(:shell_out!).with("pkg_add -r zsh", :env => nil).and_return(@cmd_result)
+ @provider.install_package("zsh", "4.3.6_7")
+ end
+
+ it "should run make install when installing from ports" do
+ @new_resource.stub!(:source).and_return("ports")
+ @provider.should_not_receive(:shell_out!).with("make -DBATCH -f /usr/ports/shells/zsh/Makefile install", :timeout => 1200, :env=>nil)
+ @provider.should_receive(:shell_out!).with("make -DBATCH install", :timeout => 1200, :env=>nil, :cwd => @provider.port_path).and_return(@cmd_result)
+ @provider.install_package("zsh", "4.3.6_7")
+ end
+ end
+
+ describe Chef::Provider::Package::Freebsd, "port path" do
+ before do
+ #@node = Chef::Node.new
+ @new_resource = Chef::Resource::Package.new("zsh")
+ @new_resource.cookbook_name = "adventureclub"
+ @provider = Chef::Provider::Package::Freebsd.new(@new_resource, @run_context)
+ end
+
+ it "should figure out the port path from the package_name using whereis" do
+ whereis = OpenStruct.new(:stdout => "zsh: /usr/ports/shells/zsh")
+ @provider.should_receive(:shell_out!).with("whereis -s zsh", :env=>nil).and_return(whereis)
+ @provider.port_path.should == "/usr/ports/shells/zsh"
+ end
+
+ it "should use the package_name as the port path when it starts with /" do
+ new_resource = Chef::Resource::Package.new("/usr/ports/www/wordpress")
+ provider = Chef::Provider::Package::Freebsd.new(new_resource, @run_context)
+ provider.should_not_receive(:popen4)
+ provider.port_path.should == "/usr/ports/www/wordpress"
+ end
+
+ it "should use the package_name as a relative path from /usr/ports when it contains / but doesn't start with it" do
+ # @new_resource = mock( "Chef::Resource::Package",
+ # :package_name => "www/wordpress",
+ # :cookbook_name => "xenoparadox")
+ new_resource = Chef::Resource::Package.new("www/wordpress")
+ provider = Chef::Provider::Package::Freebsd.new(new_resource, @run_context)
+ provider.should_not_receive(:popen4)
+ provider.port_path.should == "/usr/ports/www/wordpress"
+ end
+ end
+
+ describe Chef::Provider::Package::Freebsd, "ruby-iconv (package with a dash in the name)" do
+ before(:each) do
+ @new_resource = Chef::Resource::Package.new("ruby-iconv")
+ @current_resource = Chef::Resource::Package.new("ruby-iconv")
+ @provider = Chef::Provider::Package::Freebsd.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ @provider.stub!(:port_path).and_return("/usr/ports/converters/ruby-iconv")
+ @provider.stub!(:package_name).and_return("ruby18-iconv")
+ @provider.stub!(:latest_link_name).and_return("ruby18-iconv")
+
+ @install_result = OpenStruct.new(:status => true)
+ end
+
+ it "should run pkg_add -r with the package name" do
+ @provider.should_receive(:shell_out!).with("pkg_add -r ruby18-iconv", :env => nil).and_return(@install_result)
+ @provider.install_package("ruby-iconv", "1.0")
+ end
+
+ it "should run make install when installing from ports" do
+ @new_resource.stub!(:source).and_return("ports")
+ @provider.should_receive(:shell_out!).with("make -DBATCH install", :timeout => 1200, :env=>nil, :cwd => @provider.port_path).and_return(@install_result)
+ @provider.install_package("ruby-iconv", "1.0")
+ end
+ end
+
+ describe Chef::Provider::Package::Freebsd, "remove_package" do
+ before(:each) do
+ @pkg_delete = OpenStruct.new(:status => true)
+ @new_resource.version "4.3.6_7"
+ @current_resource.version "4.3.6_7"
+ @provider.current_resource = @current_resource
+ @provider.stub!(:package_name).and_return("zsh")
+ end
+
+ it "should run pkg_delete with the package name and version" do
+ @provider.should_receive(:shell_out!).with("pkg_delete zsh-4.3.6_7", :env => nil).and_return(@pkg_delete)
+ @provider.remove_package("zsh", "4.3.6_7")
+ end
+ end
+
+ # A couple of examples to show up the difficulty of determining the command to install the binary package given the port:
+ # PORT DIRECTORY INSTALLED PACKAGE NAME COMMAND TO INSTALL PACKAGE
+ # /usr/ports/lang/perl5.8 perl-5.8.8_1 pkg_add -r perl
+ # /usr/ports/databases/mysql50-server mysql-server-5.0.45_1 pkg_add -r mysql50-server
+ #
+ # So, in one case it appears the command to install the package can be derived from the name of the port directory and in the
+ # other case it appears the command can be derived from the package name. Very confusing!
+ # Well, luckily, after much poking around, I discovered that the two can be disambiguated through the use of the LATEST_LINK
+ # variable which is set by the ports Makefile
+ #
+ # PORT DIRECTORY LATEST_LINK INSTALLED PACKAGE NAME COMMAND TO INSTALL PACKAGE
+ # /usr/ports/lang/perl5.8 perl perl-5.8.8_1 pkg_add -r perl
+ # /usr/ports/databases/mysql50-server mysql50-server mysql-server-5.0.45_1 pkg_add -r mysql50-server
+ #
+ # The variable LATEST_LINK is named that way because the directory that "pkg_add -r" downloads from is called "Latest" and
+ # contains the "latest" versions of package as symbolic links to the files in the "All" directory.
+
+ describe Chef::Provider::Package::Freebsd, "install_package latest link fixes" do
+ it "should install the perl binary package with the correct name" do
+ @new_resource = Chef::Resource::Package.new("perl5.8")
+ @current_resource = Chef::Resource::Package.new("perl5.8")
+ @provider = Chef::Provider::Package::Freebsd.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ @provider.stub!(:package_name).and_return("perl")
+ @provider.stub!(:latest_link_name).and_return("perl")
+
+ cmd = OpenStruct.new(:status => true)
+ @provider.should_receive(:shell_out!).with("pkg_add -r perl", :env => nil).and_return(cmd)
+ @provider.install_package("perl5.8", "5.8.8_1")
+ end
+
+ it "should install the mysql50-server binary package with the correct name" do
+
+ @new_resource = Chef::Resource::Package.new("mysql50-server")
+ @current_resource = Chef::Resource::Package.new("mysql50-server")
+ @provider = Chef::Provider::Package::Freebsd.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ @provider.stub!(:package_name).and_return("mysql-server")
+ @provider.stub!(:latest_link_name).and_return("mysql50-server")
+
+ cmd = OpenStruct.new(:status => true)
+ @provider.should_receive(:shell_out!).with("pkg_add -r mysql50-server", :env=>nil).and_return(cmd)
+ @provider.install_package("mysql50-server", "5.0.45_1")
+ end
+ end
+end
diff --git a/spec/unit/provider/package/ips_spec.rb b/spec/unit/provider/package/ips_spec.rb
new file mode 100644
index 0000000000..641a527012
--- /dev/null
+++ b/spec/unit/provider/package/ips_spec.rb
@@ -0,0 +1,209 @@
+#
+# Author:: Bryan McLellan <btm@opscode.com>
+# Copyright:: Copyright (c) 2012 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 'ostruct'
+
+# based on the apt specs
+
+describe Chef::Provider::Package::Ips do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Package.new("crypto/gnupg", @run_context)
+ @current_resource = Chef::Resource::Package.new("crypto/gnupg", @run_context)
+ Chef::Resource::Package.stub!(:new).and_return(@current_resource)
+ @provider = Chef::Provider::Package::Ips.new(@new_resource, @run_context)
+
+ @stdin = StringIO.new
+ @stderr = StringIO.new
+ @stdout =<<-PKG_STATUS
+ Name: crypto/gnupg
+ Summary: GNU Privacy Guard
+ Description: A complete and free implementation of the OpenPGP Standard as
+ defined by RFC4880.
+ Category: Applications/System Utilities
+ State: Not installed
+ Publisher: solaris
+ Version: 2.0.17
+ Build Release: 5.11
+ Branch: 0.175.0.0.0.2.537
+Packaging Date: October 19, 2011 09:14:50 AM
+ Size: 8.07 MB
+ FMRI: pkg://solaris/crypto/gnupg@2.0.17,5.11-0.175.0.0.0.2.537:20111019T091450Z
+PKG_STATUS
+ @pid = 12345
+ @shell_out = OpenStruct.new(:stdout => @stdout,:stdin => @stdin,:stderr => @stderr,:status => @status,:exitstatus => 0)
+ end
+
+ context "when loading current resource" do
+ it "should create a current resource with the name of the new_resource" do
+ @provider.should_receive(:shell_out!).and_return(@shell_out)
+ Chef::Resource::Package.should_receive(:new).and_return(@current_resource)
+ @provider.load_current_resource
+ end
+
+ it "should set the current resources package name to the new resources package name" do
+ @provider.should_receive(:shell_out!).and_return(@shell_out)
+ @current_resource.should_receive(:package_name).with(@new_resource.package_name)
+ @provider.load_current_resource
+ end
+
+ it "should run pkg info with the package name" do
+ @provider.should_receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(@shell_out)
+ @provider.load_current_resource
+ end
+
+ it "should set the installed version to nil on the current resource if package state is not installed" do
+ @provider.should_receive(:shell_out!).and_return(@shell_out)
+ @current_resource.should_receive(:version).with(nil).and_return(true)
+ @provider.load_current_resource
+ end
+
+ it "should set the installed version if package has one" do
+ @stdout.replace(<<-INSTALLED)
+ Name: crypto/gnupg
+ Summary: GNU Privacy Guard
+ Description: A complete and free implementation of the OpenPGP Standard as
+ defined by RFC4880.
+ Category: Applications/System Utilities
+ State: Installed
+ Publisher: solaris
+ Version: 2.0.17
+ Build Release: 5.11
+ Branch: 0.175.0.0.0.2.537
+Packaging Date: October 19, 2011 09:14:50 AM
+ Size: 8.07 MB
+ FMRI: pkg://solaris/crypto/gnupg@2.0.17,5.11-0.175.0.0.0.2.537:20111019T091450Z
+INSTALLED
+ @provider.should_receive(:shell_out!).and_return(@shell_out)
+ @provider.load_current_resource
+ @current_resource.version.should == "2.0.17"
+ @provider.candidate_version.should eql("2.0.17")
+ end
+
+ it "should return the current resouce" do
+ @provider.should_receive(:shell_out!).and_return(@shell_out)
+ @provider.load_current_resource.should eql(@current_resource)
+ end
+ end
+
+ context "when installing a package" do
+ it "should run pkg install with the package name and version" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "pkg install -q crypto/gnupg@2.0.17"
+ })
+ @provider.install_package("crypto/gnupg", "2.0.17")
+ end
+
+
+ it "should run pkg install with the package name and version and options if specified" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "pkg --no-refresh install -q crypto/gnupg@2.0.17"
+ })
+ @new_resource.stub!(:options).and_return("--no-refresh")
+ @provider.install_package("crypto/gnupg", "2.0.17")
+ end
+
+ it "should not contain invalid characters for the version string" do
+ @stdout.replace(<<-PKG_STATUS)
+ Name: security/sudo
+ Summary: sudo - authority delegation tool
+ State: Not Installed
+ Publisher: omnios
+ Version: 1.8.4.1 (1.8.4p1)
+ Build Release: 5.11
+ Branch: 0.151002
+Packaging Date: April 1, 2012 05:55:52 PM
+ Size: 2.57 MB
+ FMRI: pkg://omnios/security/sudo@1.8.4.1,5.11-0.151002:20120401T175552Z
+PKG_STATUS
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "pkg install -q security/sudo@1.8.4.1"
+ })
+ @provider.install_package("security/sudo", "1.8.4.1")
+ end
+
+ it "should not include the human-readable version in the candidate_version" do
+ @stdout.replace(<<-PKG_STATUS)
+ Name: security/sudo
+ Summary: sudo - authority delegation tool
+ State: Not Installed
+ Publisher: omnios
+ Version: 1.8.4.1 (1.8.4p1)
+ Build Release: 5.11
+ Branch: 0.151002
+Packaging Date: April 1, 2012 05:55:52 PM
+ Size: 2.57 MB
+ FMRI: pkg://omnios/security/sudo@1.8.4.1,5.11-0.151002:20120401T175552Z
+PKG_STATUS
+ @provider.should_receive(:shell_out!).and_return(@shell_out)
+ @provider.load_current_resource
+ @current_resource.version.should be_nil
+ @provider.candidate_version.should eql("1.8.4.1")
+ end
+
+ context "using the ips_package resource" do
+ before do
+ @new_resource = Chef::Resource::IpsPackage.new("crypto/gnupg", @run_context)
+ @current_resource = Chef::Resource::IpsPackage.new("crypto/gnupg", @run_context)
+ @provider = Chef::Provider::Package::Ips.new(@new_resource, @run_context)
+ end
+
+ context "when accept_license is true" do
+ before do
+ @new_resource.stub!(:accept_license).and_return(true)
+ end
+
+ it "should run pkg install with the --accept flag" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "pkg install -q --accept crypto/gnupg@2.0.17"
+ })
+ @provider.install_package("crypto/gnupg", "2.0.17")
+ end
+ end
+ end
+ end
+
+ context "when upgrading a package" do
+ it "should run pkg install with the package name and version" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "pkg install -q crypto/gnupg@2.0.17"
+ })
+ @provider.upgrade_package("crypto/gnupg", "2.0.17")
+ end
+ end
+
+ context "when uninstalling a package" do
+ it "should run pkg uninstall with the package name and version" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "pkg uninstall -q crypto/gnupg@2.0.17"
+ })
+ @provider.remove_package("crypto/gnupg", "2.0.17")
+ end
+
+ it "should run pkg uninstall with the package name and version and options if specified" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "pkg --no-refresh uninstall -q crypto/gnupg@2.0.17"
+ })
+ @new_resource.stub!(:options).and_return("--no-refresh")
+ @provider.remove_package("crypto/gnupg", "2.0.17")
+ end
+ end
+end
diff --git a/spec/unit/provider/package/macports_spec.rb b/spec/unit/provider/package/macports_spec.rb
new file mode 100644
index 0000000000..2f0db3f7a8
--- /dev/null
+++ b/spec/unit/provider/package/macports_spec.rb
@@ -0,0 +1,203 @@
+#
+# Author:: David Balatero (<dbalatero@gmail.com>)
+# Copyright:: Copyright (c) 2009 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'
+
+describe Chef::Provider::Package::Macports do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Package.new("zsh")
+ @current_resource = Chef::Resource::Package.new("zsh")
+
+ @provider = Chef::Provider::Package::Macports.new(@new_resource, @run_context)
+ Chef::Resource::Package.stub!(:new).and_return(@current_resource)
+
+ @status = mock("Status", :exitstatus => 0)
+ @stdin = StringIO.new
+ @stdout = StringIO.new
+ @stderr = StringIO.new
+ @pid = 2342
+ end
+
+ describe "load_current_resource" do
+ it "should create a current resource with the name of the new_resource" do
+ @provider.should_receive(:current_installed_version).and_return(nil)
+ @provider.should_receive(:macports_candidate_version).and_return("4.2.7")
+
+ @provider.load_current_resource
+ @provider.current_resource.name.should == "zsh"
+ end
+
+ it "should create a current resource with the version if the package is installed" do
+ @provider.should_receive(:macports_candidate_version).and_return("4.2.7")
+ @provider.should_receive(:current_installed_version).and_return("4.2.7")
+
+ @provider.load_current_resource
+ @provider.candidate_version.should == "4.2.7"
+ end
+
+ it "should create a current resource with a nil version if the package is not installed" do
+ @provider.should_receive(:current_installed_version).and_return(nil)
+ @provider.should_receive(:macports_candidate_version).and_return("4.2.7")
+ @provider.load_current_resource
+ @provider.current_resource.version.should be_nil
+ end
+
+ it "should set a candidate version if one exists" do
+ @provider.should_receive(:current_installed_version).and_return(nil)
+ @provider.should_receive(:macports_candidate_version).and_return("4.2.7")
+ @provider.load_current_resource
+ @provider.candidate_version.should == "4.2.7"
+ end
+ end
+
+ describe "current_installed_version" do
+ it "should return the current version if the package is installed" do
+ @stdout.should_receive(:read).and_return(<<EOF
+The following ports are currently installed:
+ openssl @0.9.8k_0 (active)
+EOF
+ )
+
+ @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.current_installed_version.should == "0.9.8k_0"
+ end
+
+ it "should return nil if a package is not currently installed" do
+ @stdout.should_receive(:read).and_return(" \n")
+ @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.current_installed_version.should be_nil
+ end
+ end
+
+ describe "macports_candidate_version" do
+ it "should return the latest available version of a given package" do
+ @stdout.should_receive(:read).and_return("version: 4.2.7\n")
+ @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.macports_candidate_version.should == "4.2.7"
+ end
+
+ it "should return nil if there is no version for a given package" do
+ @stdout.should_receive(:read).and_return("Error: port fadsfadsfads not found\n")
+ @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.macports_candidate_version.should be_nil
+ end
+ end
+
+ describe "install_package" do
+ it "should run the port install command with the correct version" do
+ @current_resource.should_receive(:version).and_return("4.1.6")
+ @provider.current_resource = @current_resource
+ @provider.should_receive(:run_command_with_systems_locale).with(:command => "port install zsh @4.2.7")
+
+ @provider.install_package("zsh", "4.2.7")
+ end
+
+ it "should not do anything if a package already exists with the same version" do
+ @current_resource.should_receive(:version).and_return("4.2.7")
+ @provider.current_resource = @current_resource
+ @provider.should_not_receive(:run_command_with_systems_locale)
+
+ @provider.install_package("zsh", "4.2.7")
+ end
+
+ it "should add options to the port command when specified" do
+ @current_resource.should_receive(:version).and_return("4.1.6")
+ @provider.current_resource = @current_resource
+ @new_resource.stub!(:options).and_return("-f")
+ @provider.should_receive(:run_command_with_systems_locale).with(:command => "port -f install zsh @4.2.7")
+
+ @provider.install_package("zsh", "4.2.7")
+ end
+ end
+
+ describe "purge_package" do
+ it "should run the port uninstall command with the correct version" do
+ @provider.should_receive(:run_command_with_systems_locale).with(:command => "port uninstall zsh @4.2.7")
+ @provider.purge_package("zsh", "4.2.7")
+ end
+
+ it "should purge the currently active version if no explicit version is passed in" do
+ @provider.should_receive(:run_command_with_systems_locale).with(:command => "port uninstall zsh")
+ @provider.purge_package("zsh", nil)
+ end
+
+ it "should add options to the port command when specified" do
+ @new_resource.stub!(:options).and_return("-f")
+ @provider.should_receive(:run_command_with_systems_locale).with(:command => "port -f uninstall zsh @4.2.7")
+ @provider.purge_package("zsh", "4.2.7")
+ end
+ end
+
+ describe "remove_package" do
+ it "should run the port deactivate command with the correct version" do
+ @provider.should_receive(:run_command_with_systems_locale).with(:command => "port deactivate zsh @4.2.7")
+ @provider.remove_package("zsh", "4.2.7")
+ end
+
+ it "should remove the currently active version if no explicit version is passed in" do
+ @provider.should_receive(:run_command_with_systems_locale).with(:command => "port deactivate zsh")
+ @provider.remove_package("zsh", nil)
+ end
+
+ it "should add options to the port command when specified" do
+ @new_resource.stub!(:options).and_return("-f")
+ @provider.should_receive(:run_command_with_systems_locale).with(:command => "port -f deactivate zsh @4.2.7")
+ @provider.remove_package("zsh", "4.2.7")
+ end
+ end
+
+ describe "upgrade_package" do
+ it "should run the port upgrade command with the correct version" do
+ @current_resource.should_receive(:version).at_least(:once).and_return("4.1.6")
+ @provider.current_resource = @current_resource
+
+ @provider.should_receive(:run_command_with_systems_locale).with(:command => "port upgrade zsh @4.2.7")
+
+ @provider.upgrade_package("zsh", "4.2.7")
+ end
+
+ it "should not run the port upgrade command if the version is already installed" do
+ @current_resource.should_receive(:version).at_least(:once).and_return("4.2.7")
+ @provider.current_resource = @current_resource
+ @provider.should_not_receive(:run_command_with_systems_locale)
+
+ @provider.upgrade_package("zsh", "4.2.7")
+ end
+
+ it "should call install_package if the package isn't currently installed" do
+ @current_resource.should_receive(:version).at_least(:once).and_return(nil)
+ @provider.current_resource = @current_resource
+ @provider.should_receive(:install_package).and_return(true)
+
+ @provider.upgrade_package("zsh", "4.2.7")
+ end
+
+ it "should add options to the port command when specified" do
+ @new_resource.stub!(:options).and_return("-f")
+ @current_resource.should_receive(:version).at_least(:once).and_return("4.1.6")
+ @provider.current_resource = @current_resource
+
+ @provider.should_receive(:run_command_with_systems_locale).with(:command => "port -f upgrade zsh @4.2.7")
+
+ @provider.upgrade_package("zsh", "4.2.7")
+ end
+ end
+end
diff --git a/spec/unit/provider/package/pacman_spec.rb b/spec/unit/provider/package/pacman_spec.rb
new file mode 100644
index 0000000000..7e4abcb6d5
--- /dev/null
+++ b/spec/unit/provider/package/pacman_spec.rb
@@ -0,0 +1,206 @@
+#
+# Author:: Jan Zimmek (<jan.zimmek@web.de>)
+# Copyright:: Copyright (c) 2010 Jan Zimmek
+# 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::Package::Pacman do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Package.new("nano")
+ @current_resource = Chef::Resource::Package.new("nano")
+
+ @status = mock("Status", :exitstatus => 0)
+ @provider = Chef::Provider::Package::Pacman.new(@new_resource, @run_context)
+ Chef::Resource::Package.stub!(:new).and_return(@current_resource)
+ @provider.stub!(:popen4).and_return(@status)
+ @stdin = StringIO.new
+ @stdout = StringIO.new(<<-ERR)
+error: package "nano" not found
+ERR
+ @stderr = StringIO.new
+ @pid = 2342
+ end
+
+ describe "when determining the current package state" do
+ it "should create a current resource with the name of the new_resource" do
+ Chef::Resource::Package.should_receive(:new).and_return(@current_resource)
+ @provider.load_current_resource
+ end
+
+ it "should set the current resources package name to the new resources package name" do
+ @current_resource.should_receive(:package_name).with(@new_resource.package_name)
+ @provider.load_current_resource
+ end
+
+ it "should run pacman query with the package name" do
+ @provider.should_receive(:popen4).with("pacman -Qi #{@new_resource.package_name}").and_return(@status)
+ @provider.load_current_resource
+ end
+
+ it "should read stdout on pacman" do
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @stdout.should_receive(:each).and_return(true)
+ @provider.load_current_resource
+ end
+
+ it "should set the installed version to nil on the current resource if pacman installed version not exists" do
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @current_resource.should_receive(:version).with(nil).and_return(true)
+ @provider.load_current_resource
+ end
+
+ it "should set the installed version if pacman has one" do
+ @stdout = StringIO.new(<<-PACMAN)
+Name : nano
+Version : 2.2.2-1
+URL : http://www.nano-editor.org
+Licenses : GPL
+Groups : base
+Provides : None
+Depends On : glibc ncurses
+Optional Deps : None
+Required By : None
+Conflicts With : None
+Replaces : None
+Installed Size : 1496.00 K
+Packager : Andreas Radke <andyrtr@archlinux.org>
+Architecture : i686
+Build Date : Mon 18 Jan 2010 06:16:16 PM CET
+Install Date : Mon 01 Feb 2010 10:06:30 PM CET
+Install Reason : Explicitly installed
+Install Script : Yes
+Description : Pico editor clone with enhancements
+PACMAN
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.load_current_resource
+ @current_resource.version.should == "2.2.2-1"
+ end
+
+ it "should set the candidate version if pacman has one" do
+ @stdout.stub!(:each).and_yield("core/nano 2.2.3-1 (base)").
+ and_yield(" Pico editor clone with enhancements").
+ and_yield("community/nanoblogger 3.4.1-1").
+ and_yield(" NanoBlogger is a small weblog engine written in Bash for the command line")
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.load_current_resource
+ @provider.candidate_version.should eql("2.2.3-1")
+ end
+
+ it "should use pacman.conf to determine valid repo names for package versions" do
+ @pacman_conf = <<-PACMAN_CONF
+[options]
+HoldPkg = pacman glibc
+Architecture = auto
+
+[customrepo]
+Server = https://my.custom.repo
+
+[core]
+Include = /etc/pacman.d/mirrorlist
+
+[extra]
+Include = /etc/pacman.d/mirrorlist
+
+[community]
+Include = /etc/pacman.d/mirrorlist
+PACMAN_CONF
+
+ ::File.stub!(:exists?).with("/etc/pacman.conf").and_return(true)
+ ::File.stub!(:read).with("/etc/pacman.conf").and_return(@pacman_conf)
+ @stdout.stub!(:each).and_yield("customrepo/nano 1.2.3-4").
+ and_yield(" My custom package")
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+
+ @provider.load_current_resource
+ @provider.candidate_version.should eql("1.2.3-4")
+ end
+
+ it "should raise an exception if pacman fails" do
+ @status.should_receive(:exitstatus).and_return(2)
+ lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Package)
+ end
+
+ it "should not raise an exception if pacman succeeds" do
+ @status.should_receive(:exitstatus).and_return(0)
+ lambda { @provider.load_current_resource }.should_not raise_error(Chef::Exceptions::Package)
+ end
+
+ it "should raise an exception if pacman does not return a candidate version" do
+ @stdout.stub!(:each).and_yield("")
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ lambda { @provider.candidate_version }.should raise_error(Chef::Exceptions::Package)
+ end
+
+ it "should return the current resouce" do
+ @provider.load_current_resource.should eql(@current_resource)
+ end
+ end
+
+ describe Chef::Provider::Package::Pacman, "install_package" do
+ it "should run pacman install with the package name and version" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "pacman --sync --noconfirm --noprogressbar nano"
+ })
+ @provider.install_package("nano", "1.0")
+ end
+
+ it "should run pacman install with the package name and version and options if specified" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "pacman --sync --noconfirm --noprogressbar --debug nano"
+ })
+ @new_resource.stub!(:options).and_return("--debug")
+
+ @provider.install_package("nano", "1.0")
+ end
+ end
+
+ describe Chef::Provider::Package::Pacman, "upgrade_package" do
+ it "should run install_package with the name and version" do
+ @provider.should_receive(:install_package).with("nano", "1.0")
+ @provider.upgrade_package("nano", "1.0")
+ end
+ end
+
+ describe Chef::Provider::Package::Pacman, "remove_package" do
+ it "should run pacman remove with the package name" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "pacman --remove --noconfirm --noprogressbar nano"
+ })
+ @provider.remove_package("nano", "1.0")
+ end
+
+ it "should run pacman remove with the package name and options if specified" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "pacman --remove --noconfirm --noprogressbar --debug nano"
+ })
+ @new_resource.stub!(:options).and_return("--debug")
+
+ @provider.remove_package("nano", "1.0")
+ end
+ end
+
+ describe Chef::Provider::Package::Pacman, "purge_package" do
+ it "should run remove_package with the name and version" do
+ @provider.should_receive(:remove_package).with("nano", "1.0")
+ @provider.purge_package("nano", "1.0")
+ end
+
+ end
+end
diff --git a/spec/unit/provider/package/portage_spec.rb b/spec/unit/provider/package/portage_spec.rb
new file mode 100644
index 0000000000..e963934f4f
--- /dev/null
+++ b/spec/unit/provider/package/portage_spec.rb
@@ -0,0 +1,276 @@
+#
+# Author:: Caleb Tennis (<caleb.tennis@gmail.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::Package::Portage, "load_current_resource" do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Package.new("dev-util/git")
+ @new_resource_without_category = Chef::Resource::Package.new("git")
+ @current_resource = Chef::Resource::Package.new("dev-util/git")
+
+ @provider = Chef::Provider::Package::Portage.new(@new_resource, @run_context)
+ Chef::Resource::Package.stub!(:new).and_return(@current_resource)
+ end
+
+ describe "when determining the current state of the package" do
+
+ it "should create a current resource with the name of new_resource" do
+ ::Dir.stub!(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0"])
+ Chef::Resource::Package.should_receive(:new).and_return(@current_resource)
+ @provider.load_current_resource
+ end
+
+ it "should set the current resource package name to the new resource package name" do
+ ::Dir.stub!(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0"])
+ @current_resource.should_receive(:package_name).with(@new_resource.package_name)
+ @provider.load_current_resource
+ end
+
+ it "should return a current resource with the correct version if the package is found" do
+ ::Dir.stub!(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-foobar-0.9", "/var/db/pkg/dev-util/git-1.0.0"])
+ @provider.load_current_resource
+ @provider.current_resource.version.should == "1.0.0"
+ end
+
+ it "should return a current resource with the correct version if the package is found with revision" do
+ ::Dir.stub!(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0-r1"])
+ @provider.load_current_resource
+ @provider.current_resource.version.should == "1.0.0-r1"
+ end
+
+ it "should return a current resource with a nil version if the package is not found" do
+ ::Dir.stub!(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/notgit-1.0.0"])
+ @provider.load_current_resource
+ @provider.current_resource.version.should be_nil
+ end
+
+ it "should return a package name match from /var/db/pkg/* if a category isn't specified and a match is found" do
+ ::Dir.stub!(:[]).with("/var/db/pkg/*/git-*").and_return(["/var/db/pkg/dev-util/git-foobar-0.9", "/var/db/pkg/dev-util/git-1.0.0"])
+ @provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context)
+ @provider.load_current_resource
+ @provider.current_resource.version.should == "1.0.0"
+ end
+
+ it "should return a current resource with a nil version if a category isn't specified and a name match from /var/db/pkg/* is not found" do
+ ::Dir.stub!(:[]).with("/var/db/pkg/*/git-*").and_return(["/var/db/pkg/dev-util/notgit-1.0.0"])
+ @provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context)
+ @provider.load_current_resource
+ @provider.current_resource.version.should be_nil
+ end
+
+ it "should throw an exception if a category isn't specified and multiple packages are found" do
+ ::Dir.stub!(:[]).with("/var/db/pkg/*/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0", "/var/db/pkg/funny-words/git-1.0.0"])
+ @provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context)
+ lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Package)
+ end
+
+ it "should return a current resource with a nil version if a category is specified and multiple packages are found" do
+ ::Dir.stub!(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0", "/var/db/pkg/funny-words/git-1.0.0"])
+ @provider = Chef::Provider::Package::Portage.new(@new_resource, @run_context)
+ @provider.load_current_resource
+ @provider.current_resource.version.should be_nil
+ end
+
+ it "should return a current resource with a nil version if a category is not specified and multiple packages from the same category are found" do
+ ::Dir.stub!(:[]).with("/var/db/pkg/*/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0", "/var/db/pkg/dev-util/git-1.0.1"])
+ @provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context)
+ @provider.load_current_resource
+ @provider.current_resource.version.should be_nil
+ end
+ end
+
+ describe "once the state of the package is known" do
+
+ describe Chef::Provider::Package::Portage, "candidate_version" do
+ it "should return the candidate_version variable if already set" do
+ @provider.candidate_version = "1.0.0"
+ @provider.should_not_receive(:popen4)
+ @provider.candidate_version
+ end
+
+ it "should throw an exception if the exitstatus is not 0" do
+ @status = mock("Status", :exitstatus => 1)
+ @provider.stub!(:popen4).and_return(@status)
+ lambda { @provider.candidate_version }.should raise_error(Chef::Exceptions::Package)
+ end
+
+ it "should find the candidate_version if a category is specifed and there are no duplicates" do
+ output = <<EOF
+Searching...
+[ Results for search key : git ]
+[ Applications found : 14 ]
+
+* app-misc/digitemp [ Masked ]
+ Latest version available: 3.5.0
+ Latest version installed: [ Not Installed ]
+ Size of files: 261 kB
+ Homepage: http://www.digitemp.com/ http://www.ibutton.com/
+ Description: Temperature logging and reporting using Dallas Semiconductor's iButtons and 1-Wire protocol
+ License: GPL-2
+
+* dev-util/git
+ Latest version available: 1.6.0.6
+ Latest version installed: ignore
+ Size of files: 2,725 kB
+ Homepage: http://git.or.cz/
+ Description: GIT - the stupid content tracker, the revision control system heavily used by the Linux kernel team
+ License: GPL-2
+
+* dev-util/gitosis [ Masked ]
+ Latest version available: 0.2_p20080825
+ Latest version installed: [ Not Installed ]
+ Size of files: 31 kB
+ Homepage: http://eagain.net/gitweb/?p=gitosis.git;a=summary
+ Description: gitosis -- software for hosting git repositories
+ License: GPL-2
+EOF
+
+ @status = mock("Status", :exitstatus => 0)
+ @provider.should_receive(:popen4).and_yield(nil, nil, StringIO.new(output), nil).and_return(@status)
+ @provider.candidate_version.should == "1.6.0.6"
+ end
+
+ it "should find the candidate_version if a category is not specifed and there are no duplicates" do
+ output = <<EOF
+Searching...
+[ Results for search key : git ]
+[ Applications found : 14 ]
+
+* app-misc/digitemp [ Masked ]
+ Latest version available: 3.5.0
+ Latest version installed: [ Not Installed ]
+ Size of files: 261 kB
+ Homepage: http://www.digitemp.com/ http://www.ibutton.com/
+ Description: Temperature logging and reporting using Dallas Semiconductor's iButtons and 1-Wire protocol
+ License: GPL-2
+
+* dev-util/git
+ Latest version available: 1.6.0.6
+ Latest version installed: ignore
+ Size of files: 2,725 kB
+ Homepage: http://git.or.cz/
+ Description: GIT - the stupid content tracker, the revision control system heavily used by the Linux kernel team
+ License: GPL-2
+
+* dev-util/gitosis [ Masked ]
+ Latest version available: 0.2_p20080825
+ Latest version installed: [ Not Installed ]
+ Size of files: 31 kB
+ Homepage: http://eagain.net/gitweb/?p=gitosis.git;a=summary
+ Description: gitosis -- software for hosting git repositories
+ License: GPL-2
+EOF
+
+ @status = mock("Status", :exitstatus => 0)
+ @provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context)
+ @provider.should_receive(:popen4).and_yield(nil, nil, StringIO.new(output), nil).and_return(@status)
+ @provider.candidate_version.should == "1.6.0.6"
+ end
+
+ it "should throw an exception if a category is not specified and there are duplicates" do
+ output = <<EOF
+Searching...
+[ Results for search key : git ]
+[ Applications found : 14 ]
+
+* app-misc/digitemp [ Masked ]
+ Latest version available: 3.5.0
+ Latest version installed: [ Not Installed ]
+ Size of files: 261 kB
+ Homepage: http://www.digitemp.com/ http://www.ibutton.com/
+ Description: Temperature logging and reporting using Dallas Semiconductor's iButtons and 1-Wire protocol
+ License: GPL-2
+
+* app-misc/git
+ Latest version available: 4.3.20
+ Latest version installed: [ Not Installed ]
+ Size of files: 416 kB
+ Homepage: http://www.gnu.org/software/git/
+ Description: GNU Interactive Tools - increase speed and efficiency of most daily task
+ License: GPL-2
+
+* dev-util/git
+ Latest version available: 1.6.0.6
+ Latest version installed: ignore
+ Size of files: 2,725 kB
+ Homepage: http://git.or.cz/
+ Description: GIT - the stupid content tracker, the revision control system heavily used by the Linux kernel team
+ License: GPL-2
+
+* dev-util/gitosis [ Masked ]
+ Latest version available: 0.2_p20080825
+ Latest version installed: [ Not Installed ]
+ Size of files: 31 kB
+ Homepage: http://eagain.net/gitweb/?p=gitosis.git;a=summary
+ Description: gitosis -- software for hosting git repositories
+ License: GPL-2
+EOF
+
+ @status = mock("Status", :exitstatus => 0)
+ @provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context)
+ @provider.should_receive(:popen4).and_yield(nil, nil, StringIO.new(output), nil).and_return(@status)
+ lambda { @provider.candidate_version }.should raise_error(Chef::Exceptions::Package)
+ end
+
+ end
+
+ describe Chef::Provider::Package::Portage, "install_package" do
+ it "should install a normally versioned package using portage" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "emerge -g --color n --nospinner --quiet =dev-util/git-1.0.0"
+ })
+ @provider.install_package("dev-util/git", "1.0.0")
+ end
+
+ it "should install a tilde versioned package using portage" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "emerge -g --color n --nospinner --quiet ~dev-util/git-1.0.0"
+ })
+ @provider.install_package("dev-util/git", "~1.0.0")
+ end
+
+ it "should add options to the emerge command when specified" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "emerge -g --color n --nospinner --quiet --oneshot =dev-util/git-1.0.0"
+ })
+ @new_resource.stub!(:options).and_return("--oneshot")
+
+ @provider.install_package("dev-util/git", "1.0.0")
+ end
+ end
+
+ describe Chef::Provider::Package::Portage, "remove_package" do
+ it "should un-emerge the package with no version specified" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "emerge --unmerge --color n --nospinner --quiet dev-util/git"
+ })
+ @provider.remove_package("dev-util/git", nil)
+ end
+
+ it "should un-emerge the package with a version specified" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "emerge --unmerge --color n --nospinner --quiet =dev-util/git-1.0.0"
+ })
+ @provider.remove_package("dev-util/git", "1.0.0")
+ end
+ end
+ end
+end
diff --git a/spec/unit/provider/package/rpm_spec.rb b/spec/unit/provider/package/rpm_spec.rb
new file mode 100644
index 0000000000..9dd94a7441
--- /dev/null
+++ b/spec/unit/provider/package/rpm_spec.rb
@@ -0,0 +1,152 @@
+#
+# Author:: Joshua Timberman (<joshua@opscode.com>)
+# Author:: Daniel DeLeo (<dan@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'
+
+describe Chef::Provider::Package::Rpm do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Package.new("emacs")
+ @new_resource.source "/tmp/emacs-21.4-20.el5.i386.rpm"
+
+ @provider = Chef::Provider::Package::Rpm.new(@new_resource, @run_context)
+
+ @status = mock("Status", :exitstatus => 0)
+ ::File.stub!(:exists?).and_return(true)
+ end
+
+ describe "when determining the current state of the package" do
+
+ it "should create a current resource with the name of new_resource" do
+ @provider.stub!(:popen4).and_return(@status)
+ @provider.load_current_resource
+ @provider.current_resource.name.should == "emacs"
+ end
+
+ it "should set the current reource package name to the new resource package name" do
+ @provider.stub!(:popen4).and_return(@status)
+ @provider.load_current_resource
+ @provider.current_resource.package_name.should == 'emacs'
+ end
+
+ it "should raise an exception if a source is supplied but not found" do
+ ::File.stub!(:exists?).and_return(false)
+ lambda { @provider.run_action(:any) }.should raise_error(Chef::Exceptions::Package)
+ end
+
+ it "should get the source package version from rpm if provided" do
+ @stdout = StringIO.new("emacs 21.4-20.el5")
+ @provider.should_receive(:popen4).with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' /tmp/emacs-21.4-20.el5.i386.rpm").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.should_receive(:popen4).with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' emacs").and_return(@status)
+ @provider.load_current_resource
+ @provider.current_resource.package_name.should == "emacs"
+ @provider.new_resource.version.should == "21.4-20.el5"
+ end
+
+ it "should return the current version installed if found by rpm" do
+ @stdout = StringIO.new("emacs 21.4-20.el5")
+ @provider.should_receive(:popen4).with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' /tmp/emacs-21.4-20.el5.i386.rpm").and_return(@status)
+ @provider.should_receive(:popen4).with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' emacs").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.load_current_resource
+ @provider.current_resource.version.should == "21.4-20.el5"
+ end
+
+ it "should raise an exception if the source is not set but we are installing" do
+ new_resource = Chef::Resource::Package.new("emacs")
+ provider = Chef::Provider::Package::Rpm.new(new_resource, @run_context)
+ lambda { provider.run_action(:any) }.should raise_error(Chef::Exceptions::Package)
+ end
+
+ it "should raise an exception if rpm fails to run" do
+ status = mock("Status", :exitstatus => -1)
+ @provider.stub!(:popen4).and_return(status)
+ lambda { @provider.run_action(:any) }.should raise_error(Chef::Exceptions::Package)
+ end
+ end
+
+ describe "after the current resource is loaded" do
+ before do
+ @current_resource = Chef::Resource::Package.new("emacs")
+ @provider.current_resource = @current_resource
+ end
+
+ describe "when installing or upgrading" do
+ it "should run rpm -i with the package source to install" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "rpm -i /tmp/emacs-21.4-20.el5.i386.rpm"
+ })
+ @provider.install_package("emacs", "21.4-20.el5")
+ end
+
+ it "should run rpm -U with the package source to upgrade" do
+ @current_resource.version("21.4-19.el5")
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "rpm -U /tmp/emacs-21.4-20.el5.i386.rpm"
+ })
+ @provider.upgrade_package("emacs", "21.4-20.el5")
+ end
+
+ it "should install from a path when the package is a path and the source is nil" do
+ @new_resource = Chef::Resource::Package.new("/tmp/emacs-21.4-20.el5.i386.rpm")
+ @provider = Chef::Provider::Package::Rpm.new(@new_resource, @run_context)
+ @new_resource.source.should == "/tmp/emacs-21.4-20.el5.i386.rpm"
+ @current_resource = Chef::Resource::Package.new("emacs")
+ @provider.current_resource = @current_resource
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "rpm -i /tmp/emacs-21.4-20.el5.i386.rpm"
+ })
+ @provider.install_package("/tmp/emacs-21.4-20.el5.i386.rpm", "21.4-20.el5")
+ end
+
+ it "should uprgrade from a path when the package is a path and the source is nil" do
+ @new_resource = Chef::Resource::Package.new("/tmp/emacs-21.4-20.el5.i386.rpm")
+ @provider = Chef::Provider::Package::Rpm.new(@new_resource, @run_context)
+ @new_resource.source.should == "/tmp/emacs-21.4-20.el5.i386.rpm"
+ @current_resource = Chef::Resource::Package.new("emacs")
+ @current_resource.version("21.4-19.el5")
+ @provider.current_resource = @current_resource
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "rpm -U /tmp/emacs-21.4-20.el5.i386.rpm"
+ })
+ @provider.upgrade_package("/tmp/emacs-21.4-20.el5.i386.rpm", "21.4-20.el5")
+ end
+
+ it "installs with custom options specified in the resource" do
+ @provider.candidate_version = '11'
+ @new_resource.options("--dbpath /var/lib/rpm")
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "rpm --dbpath /var/lib/rpm -i /tmp/emacs-21.4-20.el5.i386.rpm"
+ })
+ @provider.install_package(@new_resource.name, @provider.candidate_version)
+ end
+ end
+
+ describe "when removing the package" do
+ it "should run rpm -e to remove the package" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "rpm -e emacs-21.4-20.el5"
+ })
+ @provider.remove_package("emacs", "21.4-20.el5")
+ end
+ end
+ end
+end
+
diff --git a/spec/unit/provider/package/rubygems_spec.rb b/spec/unit/provider/package/rubygems_spec.rb
new file mode 100644
index 0000000000..be4c3a99c4
--- /dev/null
+++ b/spec/unit/provider/package/rubygems_spec.rb
@@ -0,0 +1,614 @@
+#
+# Author:: David Balatero (dbalatero@gmail.com)
+#
+# Copyright:: Copyright (c) 2009 David Balatero
+# 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 'pp'
+
+module GemspecBackcompatCreator
+ def gemspec(name, version)
+ if Gem::Specification.new.method(:initialize).arity == 0
+ Gem::Specification.new { |s| s.name = name; s.version = version }
+ else
+ Gem::Specification.new(name, version)
+ end
+ end
+end
+
+require 'spec_helper'
+require 'ostruct'
+
+describe Chef::Provider::Package::Rubygems::CurrentGemEnvironment do
+ include GemspecBackcompatCreator
+
+ before do
+ @gem_env = Chef::Provider::Package::Rubygems::CurrentGemEnvironment.new
+ end
+
+ it "determines the gem paths from the in memory rubygems" do
+ @gem_env.gem_paths.should == Gem.path
+ end
+
+ it "determines the installed versions of gems from Gem.source_index" do
+ gems = [gemspec('rspec-core', Gem::Version.new('1.2.9')), gemspec('rspec-core', Gem::Version.new('1.3.0'))]
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.8.0')
+ Gem::Specification.should_receive(:find_all_by_name).with('rspec-core', Gem::Dependency.new('rspec-core').requirement).and_return(gems)
+ else
+ Gem.source_index.should_receive(:search).with(Gem::Dependency.new('rspec-core', nil)).and_return(gems)
+ end
+ @gem_env.installed_versions(Gem::Dependency.new('rspec-core', nil)).should == gems
+ end
+
+ it "determines the installed versions of gems from the source index (part2: the unmockening)" do
+ expected = ['rspec-core', Gem::Version.new(RSpec::Core::Version::STRING)]
+ actual = @gem_env.installed_versions(Gem::Dependency.new('rspec-core', nil)).map { |spec| [spec.name, spec.version] }
+ actual.should include(expected)
+ end
+
+ it "yields to a block with an alternate source list set" do
+ sources_in_block = nil
+ normal_sources = Gem.sources
+ begin
+ @gem_env.with_gem_sources("http://gems.example.org") do
+ sources_in_block = Gem.sources
+ raise RuntimeError, "sources should be reset even in case of an error"
+ end
+ rescue RuntimeError
+ end
+ sources_in_block.should == %w{http://gems.example.org}
+ Gem.sources.should == normal_sources
+ end
+
+ it "it doesnt alter the gem sources if none are set" do
+ sources_in_block = nil
+ normal_sources = Gem.sources
+ begin
+ @gem_env.with_gem_sources(nil) do
+ sources_in_block = Gem.sources
+ raise RuntimeError, "sources should be reset even in case of an error"
+ end
+ rescue RuntimeError
+ end
+ sources_in_block.should == normal_sources
+ Gem.sources.should == normal_sources
+ end
+
+ it "finds a matching gem candidate version" do
+ dep = Gem::Dependency.new('rspec', '>= 0')
+ dep_installer = Gem::DependencyInstaller.new
+ @gem_env.stub!(:dependency_installer).and_return(dep_installer)
+ latest = [[gemspec("rspec", Gem::Version.new("1.3.0")), "http://rubygems.org/"]]
+ dep_installer.should_receive(:find_gems_with_sources).with(dep).and_return(latest)
+ @gem_env.candidate_version_from_remote(Gem::Dependency.new('rspec', '>= 0')).should == Gem::Version.new('1.3.0')
+ end
+
+ it "gives the candidate version as nil if none is found" do
+ dep = Gem::Dependency.new('rspec', '>= 0')
+ latest = []
+ dep_installer = Gem::DependencyInstaller.new
+ @gem_env.stub!(:dependency_installer).and_return(dep_installer)
+ dep_installer.should_receive(:find_gems_with_sources).with(dep).and_return(latest)
+ @gem_env.candidate_version_from_remote(Gem::Dependency.new('rspec', '>= 0')).should be_nil
+ end
+
+ it "finds a matching candidate version from a .gem file when the path to the gem is supplied" do
+ location = CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem'
+ @gem_env.candidate_version_from_file(Gem::Dependency.new('chef-integration-test', '>= 0'), location).should == Gem::Version.new('0.1.0')
+ @gem_env.candidate_version_from_file(Gem::Dependency.new('chef-integration-test', '>= 0.2.0'), location).should be_nil
+ end
+
+ it "finds a matching gem from a specific gemserver when explicit sources are given" do
+ dep = Gem::Dependency.new('rspec', '>= 0')
+ latest = [[gemspec("rspec", Gem::Version.new("1.3.0")), "http://rubygems.org/"]]
+
+ @gem_env.should_receive(:with_gem_sources).with('http://gems.example.com').and_yield
+ dep_installer = Gem::DependencyInstaller.new
+ @gem_env.stub!(:dependency_installer).and_return(dep_installer)
+ dep_installer.should_receive(:find_gems_with_sources).with(dep).and_return(latest)
+ @gem_env.candidate_version_from_remote(Gem::Dependency.new('rspec', '>=0'), 'http://gems.example.com').should == Gem::Version.new('1.3.0')
+ end
+
+ it "installs a gem with a hash of options for the dependency installer" do
+ dep_installer = Gem::DependencyInstaller.new
+ @gem_env.should_receive(:dependency_installer).with(:install_dir => '/foo/bar').and_return(dep_installer)
+ @gem_env.should_receive(:with_gem_sources).with('http://gems.example.com').and_yield
+ dep_installer.should_receive(:install).with(Gem::Dependency.new('rspec', '>= 0'))
+ @gem_env.install(Gem::Dependency.new('rspec', '>= 0'), :install_dir => '/foo/bar', :sources => ['http://gems.example.com'])
+ end
+
+ it "builds an uninstaller for a gem with options set to avoid requiring user input" do
+ # default options for uninstaller should be:
+ # :ignore => true, :executables => true
+ Gem::Uninstaller.should_receive(:new).with('rspec', :ignore => true, :executables => true)
+ @gem_env.uninstaller('rspec')
+ end
+
+ it "uninstalls all versions of a gem" do
+ uninstaller = mock('gem uninstaller')
+ uninstaller.should_receive(:uninstall)
+ @gem_env.should_receive(:uninstaller).with('rspec', :all => true).and_return(uninstaller)
+ @gem_env.uninstall('rspec')
+ end
+
+ it "uninstalls a specific version of a gem" do
+ uninstaller = mock('gem uninstaller')
+ uninstaller.should_receive(:uninstall)
+ @gem_env.should_receive(:uninstaller).with('rspec', :version => '1.2.3').and_return(uninstaller)
+ @gem_env.uninstall('rspec', '1.2.3')
+ end
+
+end
+
+describe Chef::Provider::Package::Rubygems::AlternateGemEnvironment do
+ include GemspecBackcompatCreator
+
+ before do
+ Chef::Provider::Package::Rubygems::AlternateGemEnvironment.gempath_cache.clear
+ Chef::Provider::Package::Rubygems::AlternateGemEnvironment.platform_cache.clear
+ @gem_env = Chef::Provider::Package::Rubygems::AlternateGemEnvironment.new('/usr/weird/bin/gem')
+ end
+
+ it "determines the gem paths from shelling out to gem env" do
+ gem_env_output = ['/path/to/gems', '/another/path/to/gems'].join(File::PATH_SEPARATOR)
+ shell_out_result = OpenStruct.new(:stdout => gem_env_output)
+ @gem_env.should_receive(:shell_out!).with('/usr/weird/bin/gem env gempath').and_return(shell_out_result)
+ @gem_env.gem_paths.should == ['/path/to/gems', '/another/path/to/gems']
+ end
+
+ it "caches the gempaths by gem_binary" do
+ gem_env_output = ['/path/to/gems', '/another/path/to/gems'].join(File::PATH_SEPARATOR)
+ shell_out_result = OpenStruct.new(:stdout => gem_env_output)
+ @gem_env.should_receive(:shell_out!).with('/usr/weird/bin/gem env gempath').and_return(shell_out_result)
+ expected = ['/path/to/gems', '/another/path/to/gems']
+ @gem_env.gem_paths.should == ['/path/to/gems', '/another/path/to/gems']
+ Chef::Provider::Package::Rubygems::AlternateGemEnvironment.gempath_cache['/usr/weird/bin/gem'].should == expected
+ end
+
+ it "uses the cached result for gem paths when available" do
+ gem_env_output = ['/path/to/gems', '/another/path/to/gems'].join(File::PATH_SEPARATOR)
+ shell_out_result = OpenStruct.new(:stdout => gem_env_output)
+ @gem_env.should_not_receive(:shell_out!)
+ expected = ['/path/to/gems', '/another/path/to/gems']
+ Chef::Provider::Package::Rubygems::AlternateGemEnvironment.gempath_cache['/usr/weird/bin/gem']= expected
+ @gem_env.gem_paths.should == ['/path/to/gems', '/another/path/to/gems']
+ end
+
+ it "builds the gems source index from the gem paths" do
+ @gem_env.stub!(:gem_paths).and_return(['/path/to/gems', '/another/path/to/gems'])
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.8.0')
+ @gem_env.gem_specification
+ Gem::Specification.dirs.should == [ '/path/to/gems/specifications', '/another/path/to/gems/specifications' ]
+ else
+ Gem::SourceIndex.should_receive(:from_gems_in).with('/path/to/gems/specifications', '/another/path/to/gems/specifications')
+ @gem_env.gem_source_index
+ end
+ end
+
+ it "determines the installed versions of gems from the source index" do
+ gems = [gemspec('rspec', Gem::Version.new('1.2.9')), gemspec('rspec', Gem::Version.new('1.3.0'))]
+ rspec_dep = Gem::Dependency.new('rspec', nil)
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.8.0')
+ @gem_env.stub!(:gem_specification).and_return(Gem::Specification)
+ @gem_env.gem_specification.should_receive(:find_all_by_name).with(rspec_dep.name, rspec_dep.requirement).and_return(gems)
+ else
+ @gem_env.stub!(:gem_source_index).and_return(Gem.source_index)
+ @gem_env.gem_source_index.should_receive(:search).with(rspec_dep).and_return(gems)
+ end
+ @gem_env.installed_versions(Gem::Dependency.new('rspec', nil)).should == gems
+ end
+
+ it "determines the installed versions of gems from the source index (part2: the unmockening)" do
+ $stdout.stub!(:write)
+ path_to_gem = if windows?
+ `where gem`.split[-1]
+ else
+ `which gem`.strip
+ end
+ pending("cant find your gem executable") if path_to_gem.empty?
+ gem_env = Chef::Provider::Package::Rubygems::AlternateGemEnvironment.new(path_to_gem)
+ expected = ['rspec-core', Gem::Version.new(RSpec::Core::Version::STRING)]
+ actual = gem_env.installed_versions(Gem::Dependency.new('rspec-core', nil)).map { |s| [s.name, s.version] }
+ actual.should include(expected)
+ end
+
+ it "detects when the target gem environment is the jruby platform" do
+ gem_env_out=<<-JRUBY_GEM_ENV
+RubyGems Environment:
+ - RUBYGEMS VERSION: 1.3.6
+ - RUBY VERSION: 1.8.7 (2010-05-12 patchlevel 249) [java]
+ - INSTALLATION DIRECTORY: /Users/you/.rvm/gems/jruby-1.5.0
+ - RUBY EXECUTABLE: /Users/you/.rvm/rubies/jruby-1.5.0/bin/jruby
+ - EXECUTABLE DIRECTORY: /Users/you/.rvm/gems/jruby-1.5.0/bin
+ - RUBYGEMS PLATFORMS:
+ - ruby
+ - universal-java-1.6
+ - GEM PATHS:
+ - /Users/you/.rvm/gems/jruby-1.5.0
+ - /Users/you/.rvm/gems/jruby-1.5.0@global
+ - GEM CONFIGURATION:
+ - :update_sources => true
+ - :verbose => true
+ - :benchmark => false
+ - :backtrace => false
+ - :bulk_threshold => 1000
+ - "install" => "--env-shebang"
+ - "update" => "--env-shebang"
+ - "gem" => "--no-rdoc --no-ri"
+ - :sources => ["http://rubygems.org/", "http://gems.github.com/"]
+ - REMOTE SOURCES:
+ - http://rubygems.org/
+ - http://gems.github.com/
+JRUBY_GEM_ENV
+ @gem_env.should_receive(:shell_out!).with('/usr/weird/bin/gem env').and_return(mock('jruby_gem_env', :stdout => gem_env_out))
+ expected = ['ruby', Gem::Platform.new('universal-java-1.6')]
+ @gem_env.gem_platforms.should == expected
+ # it should also cache the result
+ Chef::Provider::Package::Rubygems::AlternateGemEnvironment.platform_cache['/usr/weird/bin/gem'].should == expected
+ end
+
+ it "uses the cached result for gem platforms if available" do
+ @gem_env.should_not_receive(:shell_out!)
+ expected = ['ruby', Gem::Platform.new('universal-java-1.6')]
+ Chef::Provider::Package::Rubygems::AlternateGemEnvironment.platform_cache['/usr/weird/bin/gem']= expected
+ @gem_env.gem_platforms.should == expected
+ end
+
+ it "uses the current gem platforms when the target env is not jruby" do
+ gem_env_out=<<-RBX_GEM_ENV
+RubyGems Environment:
+ - RUBYGEMS VERSION: 1.3.6
+ - RUBY VERSION: 1.8.7 (2010-05-14 patchlevel 174) [x86_64-apple-darwin10.3.0]
+ - INSTALLATION DIRECTORY: /Users/ddeleo/.rvm/gems/rbx-1.0.0-20100514
+ - RUBYGEMS PREFIX: /Users/ddeleo/.rvm/rubies/rbx-1.0.0-20100514
+ - RUBY EXECUTABLE: /Users/ddeleo/.rvm/rubies/rbx-1.0.0-20100514/bin/rbx
+ - EXECUTABLE DIRECTORY: /Users/ddeleo/.rvm/gems/rbx-1.0.0-20100514/bin
+ - RUBYGEMS PLATFORMS:
+ - ruby
+ - x86_64-darwin-10
+ - x86_64-rubinius-1.0
+ - GEM PATHS:
+ - /Users/ddeleo/.rvm/gems/rbx-1.0.0-20100514
+ - /Users/ddeleo/.rvm/gems/rbx-1.0.0-20100514@global
+ - GEM CONFIGURATION:
+ - :update_sources => true
+ - :verbose => true
+ - :benchmark => false
+ - :backtrace => false
+ - :bulk_threshold => 1000
+ - :sources => ["http://rubygems.org/", "http://gems.github.com/"]
+ - "gem" => "--no-rdoc --no-ri"
+ - REMOTE SOURCES:
+ - http://rubygems.org/
+ - http://gems.github.com/
+RBX_GEM_ENV
+ @gem_env.should_receive(:shell_out!).with('/usr/weird/bin/gem env').and_return(mock('rbx_gem_env', :stdout => gem_env_out))
+ @gem_env.gem_platforms.should == Gem.platforms
+ Chef::Provider::Package::Rubygems::AlternateGemEnvironment.platform_cache['/usr/weird/bin/gem'].should == Gem.platforms
+ end
+
+ it "yields to a block while masquerading as a different gems platform" do
+ original_platforms = Gem.platforms
+ platforms_in_block = nil
+ begin
+ @gem_env.with_gem_platforms(['ruby', Gem::Platform.new('sparc64-java-1.7')]) do
+ platforms_in_block = Gem.platforms
+ raise "gem platforms should get set to the correct value even when an error occurs"
+ end
+ rescue RuntimeError
+ end
+ platforms_in_block.should == ['ruby', Gem::Platform.new('sparc64-java-1.7')]
+ Gem.platforms.should == original_platforms
+ end
+
+end
+
+describe Chef::Provider::Package::Rubygems do
+ before(:each) do
+ @node = Chef::Node.new
+ @new_resource = Chef::Resource::GemPackage.new("rspec-core")
+ @spec_version = @new_resource.version RSpec::Core::Version::STRING
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ # We choose detect omnibus via RbConfig::CONFIG['bindir'] in Chef::Provider::Package::Rubygems.new
+ RbConfig::CONFIG.stub!(:[]).with('bindir').and_return("/usr/bin/ruby")
+ @provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+ end
+
+ it "triggers a gem configuration load so a later one will not stomp its config values" do
+ # ugly, is there a better way?
+ Gem.instance_variable_get(:@configuration).should_not be_nil
+ end
+
+ it "uses the CurrentGemEnvironment implementation when no gem_binary_path is provided" do
+ @provider.gem_env.should be_a_kind_of(Chef::Provider::Package::Rubygems::CurrentGemEnvironment)
+ end
+
+ it "uses the AlternateGemEnvironment implementation when a gem_binary_path is provided" do
+ @new_resource.gem_binary('/usr/weird/bin/gem')
+ provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+ provider.gem_env.gem_binary_location.should == '/usr/weird/bin/gem'
+ end
+
+ it "searches for a gem binary when running on Omnibus on Unix" do
+ platform_mock :unix do
+ RbConfig::CONFIG.stub!(:[]).with('bindir').and_return("/opt/chef/embedded/bin")
+ ENV.stub!(:[]).with('PATH').and_return("/usr/bin:/usr/sbin:/opt/chef/embedded/bin")
+ File.stub!(:exists?).with('/usr/bin/gem').and_return(false)
+ File.stub!(:exists?).with('/usr/sbin/gem').and_return(true)
+ File.stub!(:exists?).with('/opt/chef/embedded/bin/gem').and_return(true) # should not get here
+ provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+ provider.gem_env.gem_binary_location.should == '/usr/sbin/gem'
+ end
+ end
+
+ it "searches for a gem binary when running on Omnibus on Windows" do
+ platform_mock :windows do
+ RbConfig::CONFIG.stub!(:[]).with('bindir').and_return("d:/opscode/chef/embedded/bin")
+ ENV.stub!(:[]).with('PATH').and_return('C:\windows\system32;C:\windows;C:\Ruby186\bin;d:\opscode\chef\embedded\bin')
+ File.stub!(:exists?).with('C:\\windows\\system32\\gem').and_return(false)
+ File.stub!(:exists?).with('C:\\windows\\gem').and_return(false)
+ File.stub!(:exists?).with('C:\\Ruby186\\bin\\gem').and_return(true)
+ File.stub!(:exists?).with('d:\\opscode\\chef\\bin\\gem').and_return(false) # should not get here
+ File.stub!(:exists?).with('d:\\opscode\\chef\\embedded\\bin\\gem').and_return(false) # should not get here
+ provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+ provider.gem_env.gem_binary_location.should == 'C:\Ruby186\bin\gem'
+ end
+ end
+
+ it "smites you when you try to use a hash of install options with an explicit gem binary" do
+ @new_resource.gem_binary('/foo/bar')
+ @new_resource.options(:fail => :burger)
+ lambda {Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)}.should raise_error(ArgumentError)
+ end
+
+ it "converts the new resource into a gem dependency" do
+ @provider.gem_dependency.should == Gem::Dependency.new('rspec-core', @spec_version)
+ @new_resource.version('~> 1.2.0')
+ @provider.gem_dependency.should == Gem::Dependency.new('rspec-core', '~> 1.2.0')
+ end
+
+ describe "when determining the currently installed version" do
+
+ it "sets the current version to the version specified by the new resource if that version is installed" do
+ @provider.load_current_resource
+ @provider.current_resource.version.should == @spec_version
+ end
+
+ it "sets the current version to the highest installed version if the requested version is not installed" do
+ @new_resource.version('9000.0.2')
+ @provider.load_current_resource
+ @provider.current_resource.version.should == @spec_version
+ end
+
+ it "leaves the current version at nil if the package is not installed" do
+ @new_resource.package_name("no-such-gem-should-exist-with-this-name")
+ @provider.load_current_resource
+ @provider.current_resource.version.should be_nil
+ end
+
+ end
+
+ describe "when determining the candidate version to install" do
+
+ it "does not query for available versions when the current version is the target version" do
+ @provider.current_resource = @new_resource.dup
+ @provider.candidate_version.should be_nil
+ end
+
+ it "determines the candidate version by querying the remote gem servers" do
+ @new_resource.source('http://mygems.example.com')
+ version = Gem::Version.new(@spec_version)
+ @provider.gem_env.should_receive(:candidate_version_from_remote).
+ with(Gem::Dependency.new('rspec-core', @spec_version), "http://mygems.example.com").
+ and_return(version)
+ @provider.candidate_version.should == @spec_version
+ end
+
+ it "parses the gem's specification if the requested source is a file" do
+ @new_resource.package_name('chef-integration-test')
+ @new_resource.version('>= 0')
+ @new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
+ @provider.candidate_version.should == '0.1.0'
+ end
+
+ end
+
+ describe "when installing a gem" do
+ before do
+ @current_resource = Chef::Resource::GemPackage.new('rspec-core')
+ @provider.current_resource = @current_resource
+ @gem_dep = Gem::Dependency.new('rspec-core', @spec_version)
+ @provider.stub!(:load_current_resource)
+ end
+
+ describe "in the current gem environment" do
+ it "installs the gem via the gems api when no explicit options are used" do
+ @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil)
+ @provider.action_install.should be_true
+ @provider.converge
+ end
+
+ it "installs the gem via the gems api when a remote source is provided" do
+ @new_resource.source('http://gems.example.org')
+ sources = ['http://gems.example.org']
+ @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => sources)
+ @provider.action_install.should be_true
+ @provider.converge
+ end
+
+ it "installs the gem from file via the gems api when no explicit options are used" do
+ @new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
+ @provider.gem_env.should_receive(:install).with(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
+ @provider.action_install.should be_true
+ @provider.converge
+ end
+
+ it "installs the gem from file via the gems api when the package is a path and the source is nil" do
+ @new_resource = Chef::Resource::GemPackage.new(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
+ @provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ @new_resource.source.should == CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem'
+ @provider.gem_env.should_receive(:install).with(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
+ @provider.action_install.should be_true
+ @provider.converge
+ end
+
+ # this catches 'gem_package "foo"' when "./foo" is a file in the cwd, and instead of installing './foo' it fetches the remote gem
+ it "installs the gem via the gems api, when the package has no file separator characters in it, but a matching file exists in cwd" do
+ ::File.stub!(:exists?).and_return(true)
+ @new_resource.package_name('rspec-core')
+ @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil)
+ @provider.action_install.should be_true
+ @provider.converge
+ end
+
+ it "installs the gem by shelling out when options are provided as a String" do
+ @new_resource.options('-i /alt/install/location')
+ expected ="gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\" -i /alt/install/location"
+ @provider.should_receive(:shell_out!).with(expected, :env => nil)
+ @provider.action_install.should be_true
+ @provider.converge
+ end
+
+ it "installs the gem via the gems api when options are given as a Hash" do
+ @new_resource.options(:install_dir => '/alt/install/location')
+ @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil, :install_dir => '/alt/install/location')
+ @provider.action_install.should be_true
+ @provider.converge
+ end
+
+ describe "at a specific version" do
+ before do
+ @gem_dep = Gem::Dependency.new('rspec-core', @spec_version)
+ end
+
+ it "installs the gem via the gems api" do
+ @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil)
+ @provider.action_install.should be_true
+ @provider.converge
+ end
+ end
+ describe "at version specified with comparison operator" do
+ it "skips install if current version satisifies requested version" do
+ @current_resource.stub(:version).and_return("2.3.3")
+ @new_resource.stub(:version).and_return(">=2.3.0")
+
+ @provider.gem_env.should_not_receive(:install)
+ @provider.action_install
+ @provider.converge
+ end
+
+ it "allows user to specify gem version with fuzzy operator" do
+ @current_resource.stub(:version).and_return("2.3.3")
+ @new_resource.stub(:version).and_return("~>2.3.0")
+
+ @provider.gem_env.should_not_receive(:install)
+ @provider.action_install
+ @provider.converge
+ end
+ end
+ end
+
+ describe "in an alternate gem environment" do
+ it "installs the gem by shelling out to gem install" do
+ @new_resource.gem_binary('/usr/weird/bin/gem')
+ @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\"", :env=>nil)
+ @provider.action_install.should be_true
+ @provider.converge
+ end
+
+ it "installs the gem from file by shelling out to gem install" do
+ @new_resource.gem_binary('/usr/weird/bin/gem')
+ @new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
+ @new_resource.version('>= 0')
+ @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", :env=>nil)
+ @provider.action_install.should be_true
+ @provider.converge
+ end
+
+ it "installs the gem from file by shelling out to gem install when the package is a path and the source is nil" do
+ @new_resource = Chef::Resource::GemPackage.new(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
+ @provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ @new_resource.gem_binary('/usr/weird/bin/gem')
+ @new_resource.version('>= 0')
+ @new_resource.source.should == CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem'
+ @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", :env=>nil)
+ @provider.action_install.should be_true
+ @provider.converge
+ end
+ end
+
+ end
+
+ describe "when uninstalling a gem" do
+ before do
+ @new_resource = Chef::Resource::GemPackage.new("rspec")
+ @current_resource = @new_resource.dup
+ @current_resource.version('1.2.3')
+ @provider.new_resource = @new_resource
+ @provider.current_resource = @current_resource
+ end
+
+ describe "in the current gem environment" do
+ it "uninstalls via the api when no explicit options are used" do
+ # pre-reqs for action_remove to actually remove the package:
+ @provider.new_resource.version.should be_nil
+ @provider.current_resource.version.should_not be_nil
+ # the behavior we're testing:
+ @provider.gem_env.should_receive(:uninstall).with('rspec', nil)
+ @provider.action_remove
+ @provider.converge
+ end
+
+ it "uninstalls via the api when options are given as a Hash" do
+ # pre-reqs for action_remove to actually remove the package:
+ @provider.new_resource.version.should be_nil
+ @provider.current_resource.version.should_not be_nil
+ # the behavior we're testing:
+ @new_resource.options(:install_dir => '/alt/install/location')
+ @provider.gem_env.should_receive(:uninstall).with('rspec', nil, :install_dir => '/alt/install/location')
+ @provider.action_remove
+ @provider.converge
+ end
+
+ it "uninstalls via the gem command when options are given as a String" do
+ @new_resource.options('-i /alt/install/location')
+ @provider.should_receive(:shell_out!).with("gem uninstall rspec -q -x -I -a -i /alt/install/location", :env=>nil)
+ @provider.action_remove
+ @provider.converge
+ end
+
+ it "uninstalls a specific version of a gem when a version is provided" do
+ @new_resource.version('1.2.3')
+ @provider.gem_env.should_receive(:uninstall).with('rspec', '1.2.3')
+ @provider.action_remove
+ @provider.converge
+ end
+ end
+
+ describe "in an alternate gem environment" do
+ it "uninstalls via the gem command" do
+ @new_resource.gem_binary('/usr/weird/bin/gem')
+ @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem uninstall rspec -q -x -I -a", :env=>nil)
+ @provider.action_remove
+ @provider.converge
+ end
+ end
+ end
+end
+
diff --git a/spec/unit/provider/package/smartos_spec.rb b/spec/unit/provider/package/smartos_spec.rb
new file mode 100644
index 0000000000..b4cfe7e409
--- /dev/null
+++ b/spec/unit/provider/package/smartos_spec.rb
@@ -0,0 +1,83 @@
+#
+# Author:: Trevor O (trevoro@joyent.com)
+# Copyright:: Copyright (c) 2012 Opscode
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper"))
+require 'ostruct'
+
+describe Chef::Provider::Package::SmartOS, "load_current_resource" do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Package.new("varnish")
+ @current_resource = Chef::Resource::Package.new("varnish")
+
+
+ @status = mock("Status", :exitstatus => 0)
+ @provider = Chef::Provider::Package::SmartOS.new(@new_resource, @run_context)
+ Chef::Resource::Package.stub!(:new).and_return(@current_resource)
+ @stdin = StringIO.new
+ @stdout = "varnish-2.1.5nb2\n"
+ @stderr = StringIO.new
+ @pid = 10
+ @shell_out = OpenStruct.new(:stdout => @stdout, :stdin => @stdin, :stderr => @stderr, :status => @status, :exitstatus => 0)
+ end
+
+ describe "when loading current resource" do
+
+ it "should create a current resource with the name of the new_resource" do
+ @provider.should_receive(:shell_out!).and_return(@shell_out)
+ Chef::Resource::Package.should_receive(:new).and_return(@current_resource)
+ @provider.load_current_resource
+ end
+
+ it "should set the current resource package name" do
+ @provider.should_receive(:shell_out!).and_return(@shell_out)
+ @current_resource.should_receive(:package_name).with(@new_resource.package_name)
+ @provider.load_current_resource
+ end
+
+ it "should set the installed version if it is installed" do
+ @provider.should_receive(:shell_out!).and_return(@shell_out)
+ @provider.load_current_resource
+ @current_resource.version.should == "2.1.5nb2"
+ end
+
+ it "should set the installed version to nil if it's not installed" do
+ out = OpenStruct.new(:stdout => nil)
+ @provider.should_receive(:shell_out!).and_return(out)
+ @provider.load_current_resource
+ @current_resource.version.should == nil
+ end
+
+
+ end
+
+ describe "when manipulating a resource" do
+
+ it "run pkgin and install the package" do
+ out = OpenStruct.new(:stdout => nil)
+ @provider.should_receive(:shell_out!).with("pkg_info -E \"varnish*\"", {:env => nil, :returns=>[0,1]}).and_return(@shell_out)
+ @provider.should_receive(:shell_out!).with("pkgin -y install varnish-2.1.5nb2", {:env=>nil}).and_return(out)
+ @provider.load_current_resource
+ @provider.install_package("varnish", "2.1.5nb2")
+ end
+
+ end
+
+end
diff --git a/spec/unit/provider/package/solaris_spec.rb b/spec/unit/provider/package/solaris_spec.rb
new file mode 100644
index 0000000000..dd7cea2aaa
--- /dev/null
+++ b/spec/unit/provider/package/solaris_spec.rb
@@ -0,0 +1,181 @@
+#
+# Author:: Toomas Pelberg (<toomasp@gmx.net>)
+# Copyright:: Copyright (c) 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'
+
+describe Chef::Provider::Package::Solaris do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Package.new("SUNWbash")
+ @new_resource.source("/tmp/bash.pkg")
+
+ @provider = Chef::Provider::Package::Solaris.new(@new_resource, @run_context)
+ ::File.stub!(:exists?).and_return(true)
+ end
+
+ describe "assessing the current package status" do
+ before do
+ @pkginfo =<<-PKGINFO
+PKGINST: SUNWbash
+NAME: GNU Bourne-Again shell (bash)
+CATEGORY: system
+ARCH: sparc
+VERSION: 11.10.0,REV=2005.01.08.05.16
+BASEDIR: /
+VENDOR: Sun Microsystems, Inc.
+DESC: GNU Bourne-Again shell (bash) version 3.0
+PSTAMP: sfw10-patch20070430084444
+INSTDATE: Nov 04 2009 01:02
+HOTLINE: Please contact your local service provider
+PKGINFO
+
+ @status = mock("Status", :exitstatus => 0)
+ end
+
+ it "should create a current resource with the name of new_resource" do
+ @provider.stub!(:popen4).and_return(@status)
+ @provider.load_current_resource
+ @provider.current_resource.name.should == "SUNWbash"
+ end
+
+ it "should set the current reource package name to the new resource package name" do
+ @provider.stub!(:popen4).and_return(@status)
+ @provider.load_current_resource
+ @provider.current_resource.package_name.should == "SUNWbash"
+ end
+
+ it "should raise an exception if a source is supplied but not found" do
+ @provider.stub!(:popen4).and_return(@status)
+ ::File.stub!(:exists?).and_return(false)
+ @provider.define_resource_requirements
+ @provider.load_current_resource
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Package)
+ end
+
+
+ it "should get the source package version from pkginfo if provided" do
+ @stdout = StringIO.new(@pkginfo)
+ @stdin, @stderr = StringIO.new, StringIO.new
+ @provider.should_receive(:popen4).with("pkginfo -l -d /tmp/bash.pkg SUNWbash").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.should_receive(:popen4).with("pkginfo -l SUNWbash").and_return(@status)
+ @provider.load_current_resource
+
+ @provider.current_resource.package_name.should == "SUNWbash"
+ @new_resource.version.should == "11.10.0,REV=2005.01.08.05.16"
+ end
+
+ it "should return the current version installed if found by pkginfo" do
+ @stdout = StringIO.new(@pkginfo)
+ @stdin, @stderr = StringIO.new, StringIO.new
+ @provider.should_receive(:popen4).with("pkginfo -l -d /tmp/bash.pkg SUNWbash").and_return(@status)
+ @provider.should_receive(:popen4).with("pkginfo -l SUNWbash").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.load_current_resource
+ @provider.current_resource.version.should == "11.10.0,REV=2005.01.08.05.16"
+ end
+
+ it "should raise an exception if the source is not set but we are installing" do
+ @new_resource = Chef::Resource::Package.new("SUNWbash")
+ @provider = Chef::Provider::Package::Solaris.new(@new_resource, @run_context)
+ @provider.stub!(:popen4).and_return(@status)
+ lambda { @provider.run_action(:install) }.should raise_error(Chef::Exceptions::Package)
+ end
+
+ it "should raise an exception if pkginfo fails to run" do
+ @status = mock("Status", :exitstatus => -1)
+ @provider.stub!(:popen4).and_return(@status)
+ lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Package)
+ end
+
+ it "should return a current resource with a nil version if the package is not found" do
+ @stdout = StringIO.new
+ @provider.should_receive(:popen4).with("pkginfo -l -d /tmp/bash.pkg SUNWbash").and_return(@status)
+ @provider.should_receive(:popen4).with("pkginfo -l SUNWbash").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.load_current_resource
+ @provider.current_resource.version.should be_nil
+ end
+ end
+
+ describe "candidate_version" do
+ it "should return the candidate_version variable if already setup" do
+ @provider.candidate_version = "11.10.0,REV=2005.01.08.05.16"
+ @provider.should_not_receive(:popen4)
+ @provider.candidate_version
+ end
+
+ it "should lookup the candidate_version if the variable is not already set" do
+ @status = mock("Status", :exitstatus => 0)
+ @provider.stub!(:popen4).and_return(@status)
+ @provider.should_receive(:popen4)
+ @provider.candidate_version
+ end
+
+ it "should throw and exception if the exitstatus is not 0" do
+ @status = mock("Status", :exitstatus => 1)
+ @provider.stub!(:popen4).and_return(@status)
+ lambda { @provider.candidate_version }.should raise_error(Chef::Exceptions::Package)
+ end
+
+ end
+
+ describe "install and upgrade" do
+ it "should run pkgadd -n -d with the package source to install" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "pkgadd -n -d /tmp/bash.pkg all"
+ })
+ @provider.install_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16")
+ end
+
+ it "should run pkgadd -n -d when the package is a path to install" do
+ @new_resource = Chef::Resource::Package.new("/tmp/bash.pkg")
+ @provider = Chef::Provider::Package::Solaris.new(@new_resource, @run_context)
+ @new_resource.source.should == "/tmp/bash.pkg"
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "pkgadd -n -d /tmp/bash.pkg all"
+ })
+ @provider.install_package("/tmp/bash.pkg", "11.10.0,REV=2005.01.08.05.16")
+ end
+
+ it "should run pkgadd -n -a /tmp/myadmin -d with the package options -a /tmp/myadmin" do
+ @new_resource.stub!(:options).and_return("-a /tmp/myadmin")
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "pkgadd -n -a /tmp/myadmin -d /tmp/bash.pkg all"
+ })
+ @provider.install_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16")
+ end
+ end
+
+ describe "remove" do
+ it "should run pkgrm -n to remove the package" do
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "pkgrm -n SUNWbash"
+ })
+ @provider.remove_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16")
+ end
+
+ it "should run pkgrm -n -a /tmp/myadmin with options -a /tmp/myadmin" do
+ @new_resource.stub!(:options).and_return("-a /tmp/myadmin")
+ @provider.should_receive(:run_command_with_systems_locale).with({
+ :command => "pkgrm -n -a /tmp/myadmin SUNWbash"
+ })
+ @provider.remove_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16")
+ end
+
+ end
+end
diff --git a/spec/unit/provider/package/yum_spec.rb b/spec/unit/provider/package/yum_spec.rb
new file mode 100644
index 0000000000..0002ec39f3
--- /dev/null
+++ b/spec/unit/provider/package/yum_spec.rb
@@ -0,0 +1,1795 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::Package::Yum do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Package.new('cups')
+ @status = mock("Status", :exitstatus => 0)
+ @yum_cache = mock(
+ 'Chef::Provider::Yum::YumCache',
+ :reload_installed => true,
+ :reset => true,
+ :installed_version => "1.2.4-11.18.el5",
+ :candidate_version => "1.2.4-11.18.el5_2.3",
+ :package_available? => true,
+ :version_available? => true,
+ :allow_multi_install => [ "kernel" ],
+ :package_repository => "base"
+ )
+ Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ @stderr = StringIO.new
+ @pid = mock("PID")
+ end
+
+ describe "when loading the current system state" do
+ it "should create a current resource with the name of the new_resource" do
+ @provider.load_current_resource
+ @provider.current_resource.name.should == "cups"
+ end
+
+ it "should set the current resources package name to the new resources package name" do
+ @provider.load_current_resource
+ @provider.current_resource.package_name.should == "cups"
+ end
+
+ it "should set the installed version to nil on the current resource if no installed package" do
+ @yum_cache.stub!(:installed_version).and_return(nil)
+ @provider.load_current_resource
+ @provider.current_resource.version.should be_nil
+ end
+
+ it "should set the installed version if yum has one" do
+ @provider.load_current_resource
+ @provider.current_resource.version.should == "1.2.4-11.18.el5"
+ end
+
+ it "should set the candidate version if yum info has one" do
+ @provider.load_current_resource
+ @provider.candidate_version.should eql("1.2.4-11.18.el5_2.3")
+ end
+
+ it "should return the current resouce" do
+ @provider.load_current_resource.should eql(@provider.current_resource)
+ end
+
+ describe "when arch in package_name" do
+ it "should set the arch if no existing package_name is found and new_package_name+new_arch is available" do
+ @new_resource = Chef::Resource::YumPackage.new('testing.noarch')
+ @yum_cache = mock(
+ 'Chef::Provider::Yum::YumCache'
+ )
+ @yum_cache.stub!(:installed_version) do |package_name, arch|
+ # nothing installed for package_name/new_package_name
+ nil
+ end
+ @yum_cache.stub!(:candidate_version) do |package_name, arch|
+ if package_name == "testing.noarch" || package_name == "testing.more.noarch"
+ nil
+ # candidate for new_package_name
+ elsif package_name == "testing" || package_name == "testing.more"
+ "1.1"
+ end
+ end
+ @yum_cache.stub!(:package_available?).and_return(true)
+ Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ @provider.load_current_resource
+ @provider.new_resource.package_name.should == "testing"
+ @provider.new_resource.arch.should == "noarch"
+ @provider.arch.should == "noarch"
+
+ @new_resource = Chef::Resource::YumPackage.new('testing.more.noarch')
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ @provider.load_current_resource
+ @provider.new_resource.package_name.should == "testing.more"
+ @provider.new_resource.arch.should == "noarch"
+ @provider.arch.should == "noarch"
+ end
+
+ it "should not set the arch when an existing package_name is found" do
+ @new_resource = Chef::Resource::YumPackage.new('testing.beta3')
+ @yum_cache = mock(
+ 'Chef::Provider::Yum::YumCache'
+ )
+ @yum_cache.stub!(:installed_version) do |package_name, arch|
+ # installed for package_name
+ if package_name == "testing.beta3" || package_name == "testing.beta3.more"
+ "1.1"
+ elsif package_name == "testing" || package_name == "testing.beta3"
+ nil
+ end
+ end
+ @yum_cache.stub!(:candidate_version) do |package_name, arch|
+ # no candidate for package_name/new_package_name
+ nil
+ end
+ @yum_cache.stub!(:package_available?).and_return(true)
+ Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ # annoying side effect of the fun stub'ing above
+ @provider.load_current_resource
+ @provider.new_resource.package_name.should == "testing.beta3"
+ @provider.new_resource.arch.should == nil
+ @provider.arch.should == nil
+
+ @new_resource = Chef::Resource::YumPackage.new('testing.beta3.more')
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ @provider.load_current_resource
+ @provider.new_resource.package_name.should == "testing.beta3.more"
+ @provider.new_resource.arch.should == nil
+ @provider.arch.should == nil
+ end
+
+ it "should not set the arch when no existing package_name or new_package_name+new_arch is found" do
+ @new_resource = Chef::Resource::YumPackage.new('testing.beta3')
+ @yum_cache = mock(
+ 'Chef::Provider::Yum::YumCache'
+ )
+ @yum_cache.stub!(:installed_version) do |package_name, arch|
+ # nothing installed for package_name/new_package_name
+ nil
+ end
+ @yum_cache.stub!(:candidate_version) do |package_name, arch|
+ # no candidate for package_name/new_package_name
+ nil
+ end
+ @yum_cache.stub!(:package_available?).and_return(true)
+ Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ @provider.load_current_resource
+ @provider.new_resource.package_name.should == "testing.beta3"
+ @provider.new_resource.arch.should == nil
+ @provider.arch.should == nil
+
+ @new_resource = Chef::Resource::YumPackage.new('testing.beta3.more')
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ @provider.load_current_resource
+ @provider.new_resource.package_name.should == "testing.beta3.more"
+ @provider.new_resource.arch.should == nil
+ @provider.arch.should == nil
+ end
+
+ it "should ensure it doesn't clobber an existing arch if passed" do
+ @new_resource = Chef::Resource::YumPackage.new('testing.i386')
+ @new_resource.arch("x86_64")
+ @yum_cache = mock(
+ 'Chef::Provider::Yum::YumCache'
+ )
+ @yum_cache.stub!(:installed_version) do |package_name, arch|
+ # nothing installed for package_name/new_package_name
+ nil
+ end
+ @yum_cache.stub!(:candidate_version) do |package_name, arch|
+ if package_name == "testing.noarch"
+ nil
+ # candidate for new_package_name
+ elsif package_name == "testing"
+ "1.1"
+ end
+ end.and_return("something")
+ @yum_cache.stub!(:package_available?).and_return(true)
+ Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ @provider.load_current_resource
+ @provider.new_resource.package_name.should == "testing.i386"
+ @provider.new_resource.arch.should == "x86_64"
+ end
+ end
+
+ it "should flush the cache if :before is true" do
+ @new_resource.stub!(:flush_cache).and_return({:after => false, :before => true})
+ @yum_cache.should_receive(:reload).once
+ @provider.load_current_resource
+ end
+
+ it "should flush the cache if :before is false" do
+ @new_resource.stub!(:flush_cache).and_return({:after => false, :before => false})
+ @yum_cache.should_not_receive(:reload)
+ @provider.load_current_resource
+ end
+
+ it "should search provides if package name can't be found then set package_name to match" do
+ @yum_cache = mock(
+ 'Chef::Provider::Yum::YumCache',
+ :reload_installed => true,
+ :reset => true,
+ :installed_version => "1.2.4-11.18.el5",
+ :candidate_version => "1.2.4-11.18.el5",
+ :package_available? => false,
+ :version_available? => true
+ )
+ Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+ pkg = Chef::Provider::Package::Yum::RPMPackage.new("test-package", "1.2.4-11.18.el5", "x86_64", [])
+ @yum_cache.should_receive(:packages_from_require).and_return([pkg])
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ @provider.load_current_resource
+ @new_resource.package_name.should == "test-package"
+ end
+
+ it "should search provides if package name can't be found, warn about multiple matches, but use the first one" do
+ @yum_cache = mock(
+ 'Chef::Provider::Yum::YumCache',
+ :reload_installed => true,
+ :reset => true,
+ :installed_version => "1.2.4-11.18.el5",
+ :candidate_version => "1.2.4-11.18.el5",
+ :package_available? => false,
+ :version_available? => true
+ )
+ Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+ pkg_x = Chef::Provider::Package::Yum::RPMPackage.new("test-package-x", "1.2.4-11.18.el5", "x86_64", [])
+ pkg_y = Chef::Provider::Package::Yum::RPMPackage.new("test-package-y", "1.2.6-11.3.el5", "i386", [])
+ @yum_cache.should_receive(:packages_from_require).and_return([pkg_x, pkg_y])
+ Chef::Log.should_receive(:warn).exactly(1).times.with(%r{matched multiple Provides})
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ @provider.load_current_resource
+ @new_resource.package_name.should == "test-package-x"
+ end
+
+ it "should search provides if no package is available - if no match in installed provides then load the complete set" do
+ @yum_cache = mock(
+ 'Chef::Provider::Yum::YumCache',
+ :reload_installed => true,
+ :reset => true,
+ :installed_version => "1.2.4-11.18.el5",
+ :candidate_version => "1.2.4-11.18.el5",
+ :package_available? => false,
+ :version_available? => true
+ )
+ Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+ @yum_cache.should_receive(:packages_from_require).twice.and_return([])
+ @yum_cache.should_receive(:reload_provides)
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ @provider.load_current_resource
+ end
+
+ it "should search provides if no package is available and not load the complete set if action is :remove or :purge" do
+ @yum_cache = mock(
+ 'Chef::Provider::Yum::YumCache',
+ :reload_installed => true,
+ :reset => true,
+ :installed_version => "1.2.4-11.18.el5",
+ :candidate_version => "1.2.4-11.18.el5",
+ :package_available? => false,
+ :version_available? => true
+ )
+ Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ @yum_cache.should_receive(:packages_from_require).once.and_return([])
+ @yum_cache.should_not_receive(:reload_provides)
+ @new_resource.action(:remove)
+ @provider.load_current_resource
+ @yum_cache.should_receive(:packages_from_require).once.and_return([])
+ @yum_cache.should_not_receive(:reload_provides)
+ @new_resource.action(:purge)
+ @provider.load_current_resource
+ end
+
+ it "should search provides if no package is available - if no match in provides leave the name intact" do
+ @yum_cache = mock(
+ 'Chef::Provider::Yum::YumCache',
+ :reload_provides => true,
+ :reload_installed => true,
+ :reset => true,
+ :installed_version => "1.2.4-11.18.el5",
+ :candidate_version => "1.2.4-11.18.el5",
+ :package_available? => false,
+ :version_available? => true
+ )
+ Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+ @yum_cache.should_receive(:packages_from_require).twice.and_return([])
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ @provider.load_current_resource
+ @new_resource.package_name.should == "cups"
+ end
+ end
+
+ describe "when installing a package" do
+ it "should run yum install with the package name and version" do
+ @provider.load_current_resource
+ Chef::Provider::Package::Yum::RPMUtils.stub!(:rpmvercmp).and_return(-1)
+ @provider.should_receive(:yum_command).with(
+ "yum -d0 -e0 -y install emacs-1.0"
+ )
+ @provider.install_package("emacs", "1.0")
+ end
+
+ it "should run yum localinstall if given a path to an rpm" do
+ @new_resource.stub!(:source).and_return("/tmp/emacs-21.4-20.el5.i386.rpm")
+ @provider.should_receive(:yum_command).with(
+ "yum -d0 -e0 -y localinstall /tmp/emacs-21.4-20.el5.i386.rpm"
+ )
+ @provider.install_package("emacs", "21.4-20.el5")
+ end
+
+ it "should run yum localinstall if given a path to an rpm as the package" do
+ @new_resource = Chef::Resource::Package.new("/tmp/emacs-21.4-20.el5.i386.rpm")
+ ::File.stub!(:exists?).and_return(true)
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ @new_resource.source.should == "/tmp/emacs-21.4-20.el5.i386.rpm"
+ @provider.should_receive(:yum_command).with(
+ "yum -d0 -e0 -y localinstall /tmp/emacs-21.4-20.el5.i386.rpm"
+ )
+ @provider.install_package("/tmp/emacs-21.4-20.el5.i386.rpm", "21.4-20.el5")
+ end
+
+ it "should run yum install with the package name, version and arch" do
+ @provider.load_current_resource
+ @new_resource.stub!(:arch).and_return("i386")
+ Chef::Provider::Package::Yum::RPMUtils.stub!(:rpmvercmp).and_return(-1)
+ @provider.should_receive(:yum_command).with(
+ "yum -d0 -e0 -y install emacs-21.4-20.el5.i386"
+ )
+ @provider.install_package("emacs", "21.4-20.el5")
+ end
+
+ it "installs the package with the options given in the resource" do
+ @provider.load_current_resource
+ @provider.candidate_version = '11'
+ @new_resource.stub!(:options).and_return("--disablerepo epmd")
+ Chef::Provider::Package::Yum::RPMUtils.stub!(:rpmvercmp).and_return(-1)
+ @provider.should_receive(:yum_command).with(
+ "yum -d0 -e0 -y --disablerepo epmd install cups-11"
+ )
+ @provider.install_package(@new_resource.name, @provider.candidate_version)
+ end
+
+ it "should raise an exception if the package is not available" do
+ @yum_cache = mock(
+ 'Chef::Provider::Yum::YumCache',
+ :reload_from_cache => true,
+ :reset => true,
+ :installed_version => "1.2.4-11.18.el5",
+ :candidate_version => "1.2.4-11.18.el5_2.3",
+ :package_available? => true,
+ :version_available? => nil
+ )
+ Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ lambda { @provider.install_package("lolcats", "0.99") }.should raise_error(Chef::Exceptions::Package, %r{Version .* not found})
+ end
+
+ it "should raise an exception if candidate version is older than the installed version and allow_downgrade is false" do
+ @new_resource.stub!(:allow_downgrade).and_return(false)
+ @yum_cache = mock(
+ 'Chef::Provider::Yum::YumCache',
+ :reload_installed => true,
+ :reset => true,
+ :installed_version => "1.2.4-11.18.el5",
+ :candidate_version => "1.2.4-11.15.el5",
+ :package_available? => true,
+ :version_available? => true,
+ :allow_multi_install => [ "kernel" ]
+ )
+ Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ @provider.load_current_resource
+ lambda { @provider.install_package("cups", "1.2.4-11.15.el5") }.should raise_error(Chef::Exceptions::Package, %r{is newer than candidate package})
+ end
+
+ it "should not raise an exception if candidate version is older than the installed version and the package is list in yum's installonlypkg option" do
+ @yum_cache = mock(
+ 'Chef::Provider::Yum::YumCache',
+ :reload_installed => true,
+ :reset => true,
+ :installed_version => "1.2.4-11.18.el5",
+ :candidate_version => "1.2.4-11.15.el5",
+ :package_available? => true,
+ :version_available? => true,
+ :allow_multi_install => [ "cups" ],
+ :package_repository => "base"
+ )
+ Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ @provider.load_current_resource
+ @provider.should_receive(:yum_command).with(
+ "yum -d0 -e0 -y install cups-1.2.4-11.15.el5"
+ )
+ @provider.install_package("cups", "1.2.4-11.15.el5")
+ end
+
+ it "should run yum downgrade if candidate version is older than the installed version and allow_downgrade is true" do
+ @new_resource.stub!(:allow_downgrade).and_return(true)
+ @yum_cache = mock(
+ 'Chef::Provider::Yum::YumCache',
+ :reload_installed => true,
+ :reset => true,
+ :installed_version => "1.2.4-11.18.el5",
+ :candidate_version => "1.2.4-11.15.el5",
+ :package_available? => true,
+ :version_available? => true,
+ :allow_multi_install => [],
+ :package_repository => "base"
+ )
+ Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ @provider.load_current_resource
+ @provider.should_receive(:yum_command).with(
+ "yum -d0 -e0 -y downgrade cups-1.2.4-11.15.el5"
+ )
+ @provider.install_package("cups", "1.2.4-11.15.el5")
+ end
+
+ it "should run yum install then flush the cache if :after is true" do
+ @new_resource.stub!(:flush_cache).and_return({:after => true, :before => false})
+ @provider.load_current_resource
+ Chef::Provider::Package::Yum::RPMUtils.stub!(:rpmvercmp).and_return(-1)
+ @provider.should_receive(:yum_command).with(
+ "yum -d0 -e0 -y install emacs-1.0"
+ )
+ @yum_cache.should_receive(:reload).once
+ @provider.install_package("emacs", "1.0")
+ end
+
+ it "should run yum install then not flush the cache if :after is false" do
+ @new_resource.stub!(:flush_cache).and_return({:after => false, :before => false})
+ @provider.load_current_resource
+ Chef::Provider::Package::Yum::RPMUtils.stub!(:rpmvercmp).and_return(-1)
+ @provider.should_receive(:yum_command).with(
+ "yum -d0 -e0 -y install emacs-1.0"
+ )
+ @yum_cache.should_not_receive(:reload)
+ @provider.install_package("emacs", "1.0")
+ end
+ end
+
+ describe "when upgrading a package" do
+ it "should run yum install if the package is installed and a version is given" do
+ @provider.load_current_resource
+ @provider.candidate_version = '11'
+ Chef::Provider::Package::Yum::RPMUtils.stub!(:rpmvercmp).and_return(-1)
+ @provider.should_receive(:yum_command).with(
+ "yum -d0 -e0 -y install cups-11"
+ )
+ @provider.upgrade_package(@new_resource.name, @provider.candidate_version)
+ end
+
+ it "should run yum install if the package is not installed" do
+ @provider.load_current_resource
+ @current_resource = Chef::Resource::Package.new('cups')
+ @provider.candidate_version = '11'
+ Chef::Provider::Package::Yum::RPMUtils.stub!(:rpmvercmp).and_return(-1)
+ @provider.should_receive(:yum_command).with(
+ "yum -d0 -e0 -y install cups-11"
+ )
+ @provider.upgrade_package(@new_resource.name, @provider.candidate_version)
+ end
+
+ it "should raise an exception if candidate version is older than the installed version" do
+ @yum_cache = mock(
+ 'Chef::Provider::Yum::YumCache',
+ :reload_installed => true,
+ :reset => true,
+ :installed_version => "1.2.4-11.18.el5",
+ :candidate_version => "1.2.4-11.15.el5",
+ :package_available? => true,
+ :version_available? => true,
+ :allow_multi_install => [ "kernel" ]
+ )
+ Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ @provider.load_current_resource
+ lambda { @provider.upgrade_package("cups", "1.2.4-11.15.el5") }.should raise_error(Chef::Exceptions::Package, %r{is newer than candidate package})
+ end
+
+ # Test our little workaround, some crossover into Chef::Provider::Package territory
+ it "should call action_upgrade in the parent if the current resource version is nil" do
+ @yum_cache.stub!(:installed_version).and_return(nil)
+ @provider.load_current_resource
+ @current_resource = Chef::Resource::Package.new('cups')
+ @provider.candidate_version = '11'
+ @provider.should_receive(:upgrade_package).with(
+ "cups",
+ "11"
+ )
+ @provider.action_upgrade
+ @provider.converge
+ end
+
+ it "should call action_upgrade in the parent if the candidate version is nil" do
+ @provider.load_current_resource
+ @current_resource = Chef::Resource::Package.new('cups')
+ @provider.candidate_version = nil
+ @provider.should_not_receive(:upgrade_package)
+ @provider.action_upgrade
+ @provider.converge
+ end
+
+ it "should call action_upgrade in the parent if the candidate is newer" do
+ @provider.load_current_resource
+ @current_resource = Chef::Resource::Package.new('cups')
+ @provider.candidate_version = '11'
+ @provider.should_receive(:upgrade_package).with(
+ "cups",
+ "11"
+ )
+ @provider.action_upgrade
+ @provider.converge
+ end
+
+ it "should not call action_upgrade in the parent if the candidate is older" do
+ @yum_cache.stub!(:installed_version).and_return("12")
+ @provider.load_current_resource
+ @current_resource = Chef::Resource::Package.new('cups')
+ @provider.candidate_version = '11'
+ @provider.should_not_receive(:upgrade_package)
+ @provider.action_upgrade
+ @provider.converge
+ end
+ end
+
+ describe "when removing a package" do
+ it "should run yum remove with the package name" do
+ @provider.should_receive(:yum_command).with(
+ "yum -d0 -e0 -y remove emacs-1.0"
+ )
+ @provider.remove_package("emacs", "1.0")
+ end
+
+ it "should run yum remove with the package name and arch" do
+ @new_resource.stub!(:arch).and_return("x86_64")
+ @provider.should_receive(:yum_command).with(
+ "yum -d0 -e0 -y remove emacs-1.0.x86_64"
+ )
+ @provider.remove_package("emacs", "1.0")
+ end
+ end
+
+ describe "when purging a package" do
+ it "should run yum remove with the package name" do
+ @provider.should_receive(:yum_command).with(
+ "yum -d0 -e0 -y remove emacs-1.0"
+ )
+ @provider.purge_package("emacs", "1.0")
+ end
+ end
+
+ describe "when running yum" do
+ it "should run yum once if it exits with a return code of 0" do
+ @status = mock("Status", :exitstatus => 0)
+ @provider.stub!(:output_of_command).and_return([@status, "", ""])
+ @provider.should_receive(:output_of_command).once.with(
+ "yum -d0 -e0 -y install emacs-1.0",
+ {}
+ )
+ @provider.yum_command("yum -d0 -e0 -y install emacs-1.0")
+ end
+
+ it "should run yum once if it exits with a return code > 0 and no scriptlet failures" do
+ @status = mock("Status", :exitstatus => 2)
+ @provider.stub!(:output_of_command).and_return([@status, "failure failure", "problem problem"])
+ @provider.should_receive(:output_of_command).once.with(
+ "yum -d0 -e0 -y install emacs-1.0",
+ {}
+ )
+ lambda { @provider.yum_command("yum -d0 -e0 -y install emacs-1.0") }.should raise_error(Chef::Exceptions::Exec)
+ end
+
+ it "should run yum once if it exits with a return code of 1 and %pre scriptlet failures" do
+ @status = mock("Status", :exitstatus => 1)
+ @provider.stub!(:output_of_command).and_return([@status, "error: %pre(demo-1-1.el5.centos.x86_64) scriptlet failed, exit status 2", ""])
+ @provider.should_receive(:output_of_command).once.with(
+ "yum -d0 -e0 -y install emacs-1.0",
+ {}
+ )
+ # will still raise an exception, can't stub out the subsequent call
+ lambda { @provider.yum_command("yum -d0 -e0 -y install emacs-1.0") }.should raise_error(Chef::Exceptions::Exec)
+ end
+
+ it "should run yum twice if it exits with a return code of 1 and %post scriptlet failures" do
+ @status = mock("Status", :exitstatus => 1)
+ @provider.stub!(:output_of_command).and_return([@status, "error: %post(demo-1-1.el5.centos.x86_64) scriptlet failed, exit status 2", ""])
+ @provider.should_receive(:output_of_command).twice.with(
+ "yum -d0 -e0 -y install emacs-1.0",
+ {}
+ )
+ # will still raise an exception, can't stub out the subsequent call
+ lambda { @provider.yum_command("yum -d0 -e0 -y install emacs-1.0") }.should raise_error(Chef::Exceptions::Exec)
+ end
+ end
+end
+
+describe Chef::Provider::Package::Yum::RPMUtils do
+ describe "version_parse" do
+ before do
+ @rpmutils = Chef::Provider::Package::Yum::RPMUtils
+ end
+
+ it "parses known good epoch strings" do
+ [
+ [ "0:3.3", [ 0, "3.3", nil ] ],
+ [ "9:1.7.3", [ 9, "1.7.3", nil ] ],
+ [ "15:20020927", [ 15, "20020927", nil ] ]
+ ].each do |x, y|
+ @rpmutils.version_parse(x).should == y
+ end
+ end
+
+ it "parses strange epoch strings" do
+ [
+ [ ":3.3", [ 0, "3.3", nil ] ],
+ [ "-1:1.7.3", [ nil, nil, "1:1.7.3" ] ],
+ [ "-:20020927", [ nil, nil, ":20020927" ] ]
+ ].each do |x, y|
+ @rpmutils.version_parse(x).should == y
+ end
+ end
+
+ it "parses known good version strings" do
+ [
+ [ "3.3", [ nil, "3.3", nil ] ],
+ [ "1.7.3", [ nil, "1.7.3", nil ] ],
+ [ "20020927", [ nil, "20020927", nil ] ]
+ ].each do |x, y|
+ @rpmutils.version_parse(x).should == y
+ end
+ end
+
+ it "parses strange version strings" do
+ [
+ [ "3..3", [ nil, "3..3", nil ] ],
+ [ "0001.7.3", [ nil, "0001.7.3", nil ] ],
+ [ "20020927,3", [ nil, "20020927,3", nil ] ]
+ ].each do |x, y|
+ @rpmutils.version_parse(x).should == y
+ end
+ end
+
+ it "parses known good version release strings" do
+ [
+ [ "3.3-0.pre3.1.60.el5_5.1", [ nil, "3.3", "0.pre3.1.60.el5_5.1" ] ],
+ [ "1.7.3-1jpp.2.el5", [ nil, "1.7.3", "1jpp.2.el5" ] ],
+ [ "20020927-46.el5", [ nil, "20020927", "46.el5" ] ]
+ ].each do |x, y|
+ @rpmutils.version_parse(x).should == y
+ end
+ end
+
+ it "parses strange version release strings" do
+ [
+ [ "3.3-", [ nil, "3.3", nil ] ],
+ [ "-1jpp.2.el5", [ nil, nil, "1jpp.2.el5" ] ],
+ [ "-0020020927-46.el5", [ nil, "-0020020927", "46.el5" ] ]
+ ].each do |x, y|
+ @rpmutils.version_parse(x).should == y
+ end
+ end
+ end
+
+ describe "rpmvercmp" do
+ before do
+ @rpmutils = Chef::Provider::Package::Yum::RPMUtils
+ end
+
+ it "should validate version compare logic for standard examples" do
+ [
+ # numeric
+ [ "0.0.2", "0.0.1", 1 ],
+ [ "0.2.0", "0.1.0", 1 ],
+ [ "2.0.0", "1.0.0", 1 ],
+ [ "0.0.1", "0.0.1", 0 ],
+ [ "0.0.1", "0.0.2", -1 ],
+ [ "0.1.0", "0.2.0", -1 ],
+ [ "1.0.0", "2.0.0", -1 ],
+ # alpha
+ [ "bb", "aa", 1 ],
+ [ "ab", "aa", 1 ],
+ [ "aa", "aa", 0 ],
+ [ "aa", "bb", -1 ],
+ [ "aa", "ab", -1 ],
+ [ "BB", "AA", 1 ],
+ [ "AA", "AA", 0 ],
+ [ "AA", "BB", -1 ],
+ [ "aa", "AA", 1 ],
+ [ "AA", "aa", -1 ],
+ # alphanumeric
+ [ "0.0.1b", "0.0.1a", 1 ],
+ [ "0.1b.0", "0.1a.0", 1 ],
+ [ "1b.0.0", "1a.0.0", 1 ],
+ [ "0.0.1a", "0.0.1a", 0 ],
+ [ "0.0.1a", "0.0.1b", -1 ],
+ [ "0.1a.0", "0.1b.0", -1 ],
+ [ "1a.0.0", "1b.0.0", -1 ],
+ # alphanumeric against alphanumeric
+ [ "0.0.1", "0.0.a", 1 ],
+ [ "0.1.0", "0.a.0", 1 ],
+ [ "1.0.0", "a.0.0", 1 ],
+ [ "0.0.a", "0.0.a", 0 ],
+ [ "0.0.a", "0.0.1", -1 ],
+ [ "0.a.0", "0.1.0", -1 ],
+ [ "a.0.0", "1.0.0", -1 ],
+ # alphanumeric against numeric
+ [ "0.0.2", "0.0.1a", 1 ],
+ [ "0.0.2a", "0.0.1", 1 ],
+ [ "0.0.1", "0.0.2a", -1 ],
+ [ "0.0.1a", "0.0.2", -1 ],
+ # length
+ [ "0.0.1aa", "0.0.1a", 1 ],
+ [ "0.0.1aa", "0.0.1aa", 0 ],
+ [ "0.0.1a", "0.0.1aa", -1 ],
+ ].each do |x, y, result|
+ @rpmutils.rpmvercmp(x,y).should == result
+ end
+ end
+
+ it "should validate version compare logic for strange examples" do
+ [
+ [ "2,0,0", "1.0.0", 1 ],
+ [ "0.0.1", "0,0.1", 0 ],
+ [ "1.0.0", "2,0,0", -1 ],
+ [ "002.0.0", "001.0.0", 1 ],
+ [ "001..0.1", "001..0.0", 1 ],
+ [ "-001..1", "-001..0", 1 ],
+ [ "1.0.1", nil, 1 ],
+ [ nil, nil, 0 ],
+ [ nil, "1.0.1", -1 ],
+ [ "1.0.1", "", 1 ],
+ [ "", "", 0 ],
+ [ "", "1.0.1", -1 ]
+ ].each do |x, y, result|
+ @rpmutils.rpmvercmp(x,y).should == result
+ end
+ end
+
+ it "tests isalnum good input" do
+ [ 'a', 'z', 'A', 'Z', '0', '9' ].each do |t|
+ @rpmutils.isalnum(t).should == true
+ end
+ end
+
+ it "tests isalnum bad input" do
+ [ '-', '.', '!', '^', ':', '_' ].each do |t|
+ @rpmutils.isalnum(t).should == false
+ end
+ end
+
+ it "tests isalpha good input" do
+ [ 'a', 'z', 'A', 'Z', ].each do |t|
+ @rpmutils.isalpha(t).should == true
+ end
+ end
+
+ it "tests isalpha bad input" do
+ [ '0', '9', '-', '.', '!', '^', ':', '_' ].each do |t|
+ @rpmutils.isalpha(t).should == false
+ end
+ end
+
+ it "tests isdigit good input" do
+ [ '0', '9', ].each do |t|
+ @rpmutils.isdigit(t).should == true
+ end
+ end
+
+ it "tests isdigit bad input" do
+ [ 'A', 'z', '-', '.', '!', '^', ':', '_' ].each do |t|
+ @rpmutils.isdigit(t).should == false
+ end
+ end
+ end
+
+end
+
+describe Chef::Provider::Package::Yum::RPMVersion do
+ describe "new - with parsing" do
+ before do
+ @rpmv = Chef::Provider::Package::Yum::RPMVersion.new("1:1.6.5-9.36.el5")
+ end
+
+ it "should expose evr (name-version-release) available" do
+ @rpmv.e.should == 1
+ @rpmv.v.should == "1.6.5"
+ @rpmv.r.should == "9.36.el5"
+
+ @rpmv.evr.should == "1:1.6.5-9.36.el5"
+ end
+
+ it "should output a version-release string" do
+ @rpmv.to_s.should == "1.6.5-9.36.el5"
+ end
+ end
+
+ describe "new - no parsing" do
+ before do
+ @rpmv = Chef::Provider::Package::Yum::RPMVersion.new("1", "1.6.5", "9.36.el5")
+ end
+
+ it "should expose evr (name-version-release) available" do
+ @rpmv.e.should == 1
+ @rpmv.v.should == "1.6.5"
+ @rpmv.r.should == "9.36.el5"
+
+ @rpmv.evr.should == "1:1.6.5-9.36.el5"
+ end
+
+ it "should output a version-release string" do
+ @rpmv.to_s.should == "1.6.5-9.36.el5"
+ end
+ end
+
+ it "should raise an error unless passed 1 or 3 args" do
+ lambda {
+ Chef::Provider::Package::Yum::RPMVersion.new()
+ }.should raise_error(ArgumentError)
+ lambda {
+ Chef::Provider::Package::Yum::RPMVersion.new("1:1.6.5-9.36.el5")
+ }.should_not raise_error
+ lambda {
+ Chef::Provider::Package::Yum::RPMVersion.new("1:1.6.5-9.36.el5", "extra")
+ }.should raise_error(ArgumentError)
+ lambda {
+ Chef::Provider::Package::Yum::RPMVersion.new("1", "1.6.5", "9.36.el5")
+ }.should_not raise_error
+ lambda {
+ Chef::Provider::Package::Yum::RPMVersion.new("1", "1.6.5", "9.36.el5", "extra")
+ }.should raise_error(ArgumentError)
+ end
+
+ # thanks version_class_spec.rb!
+ describe "compare" do
+ it "should sort based on complete epoch-version-release data" do
+ [
+ # smaller, larger
+ [ "0:1.6.5-9.36.el5",
+ "1:1.6.5-9.36.el5" ],
+ [ "0:2.3-15.el5",
+ "0:3.3-15.el5" ],
+ [ "0:alpha9.8-27.2",
+ "0:beta9.8-27.2" ],
+ [ "0:0.09-14jpp.3",
+ "0:0.09-15jpp.3" ],
+ [ "0:0.9.0-0.6.20110211.el5",
+ "0:0.9.0-0.6.20120211.el5" ],
+ [ "0:1.9.1-4.el5",
+ "0:1.9.1-5.el5" ],
+ [ "0:1.4.10-7.20090624svn.el5",
+ "0:1.4.10-7.20090625svn.el5" ],
+ [ "0:2.3.4-2.el5",
+ "0:2.3.4-2.el6" ]
+ ].each do |smaller, larger|
+ sm = Chef::Provider::Package::Yum::RPMVersion.new(smaller)
+ lg = Chef::Provider::Package::Yum::RPMVersion.new(larger)
+ sm.should be < lg
+ lg.should be > sm
+ sm.should_not == lg
+ end
+ end
+
+ it "should sort based on partial epoch-version-release data" do
+ [
+ # smaller, larger
+ [ ":1.6.5-9.36.el5",
+ "1:1.6.5-9.36.el5" ],
+ [ "2.3-15.el5",
+ "3.3-15.el5" ],
+ [ "alpha9.8",
+ "beta9.8" ],
+ [ "14jpp",
+ "15jpp" ],
+ [ "0.9.0-0.6",
+ "0.9.0-0.7" ],
+ [ "0:1.9",
+ "3:1.9" ],
+ [ "2.3-2.el5",
+ "2.3-2.el6" ]
+ ].each do |smaller, larger|
+ sm = Chef::Provider::Package::Yum::RPMVersion.new(smaller)
+ lg = Chef::Provider::Package::Yum::RPMVersion.new(larger)
+ sm.should be < lg
+ lg.should be > sm
+ sm.should_not == lg
+ end
+ end
+
+ it "should verify equality of complete epoch-version-release data" do
+ [
+ [ "0:1.6.5-9.36.el5",
+ "0:1.6.5-9.36.el5" ],
+ [ "0:2.3-15.el5",
+ "0:2.3-15.el5" ],
+ [ "0:alpha9.8-27.2",
+ "0:alpha9.8-27.2" ]
+ ].each do |smaller, larger|
+ sm = Chef::Provider::Package::Yum::RPMVersion.new(smaller)
+ lg = Chef::Provider::Package::Yum::RPMVersion.new(larger)
+ sm.should be == lg
+ end
+ end
+
+ it "should verify equality of partial epoch-version-release data" do
+ [
+ [ ":1.6.5-9.36.el5",
+ "0:1.6.5-9.36.el5" ],
+ [ "2.3-15.el5",
+ "2.3-15.el5" ],
+ [ "alpha9.8-3",
+ "alpha9.8-3" ]
+ ].each do |smaller, larger|
+ sm = Chef::Provider::Package::Yum::RPMVersion.new(smaller)
+ lg = Chef::Provider::Package::Yum::RPMVersion.new(larger)
+ sm.should be == lg
+ end
+ end
+ end
+
+ describe "partial compare" do
+ it "should compare based on partial epoch-version-release data" do
+ [
+ # smaller, larger
+ [ "0:1.1.1-1",
+ "1:" ],
+ [ "0:1.1.1-1",
+ "0:1.1.2" ],
+ [ "0:1.1.1-1",
+ "0:1.1.2-1" ],
+ [ "0:",
+ "1:1.1.1-1" ],
+ [ "0:1.1.1",
+ "0:1.1.2-1" ],
+ [ "0:1.1.1-1",
+ "0:1.1.2-1" ],
+ ].each do |smaller, larger|
+ sm = Chef::Provider::Package::Yum::RPMVersion.new(smaller)
+ lg = Chef::Provider::Package::Yum::RPMVersion.new(larger)
+ sm.partial_compare(lg).should be == -1
+ lg.partial_compare(sm).should be == 1
+ sm.partial_compare(lg).should_not be == 0
+ end
+ end
+
+ it "should verify equality based on partial epoch-version-release data" do
+ [
+ [ "0:",
+ "0:1.1.1-1" ],
+ [ "0:1.1.1",
+ "0:1.1.1-1" ],
+ [ "0:1.1.1-1",
+ "0:1.1.1-1" ],
+ ].each do |smaller, larger|
+ sm = Chef::Provider::Package::Yum::RPMVersion.new(smaller)
+ lg = Chef::Provider::Package::Yum::RPMVersion.new(larger)
+ sm.partial_compare(lg).should be == 0
+ end
+ end
+ end
+
+end
+
+describe Chef::Provider::Package::Yum::RPMPackage do
+ describe "new - with parsing" do
+ before do
+ @rpm = Chef::Provider::Package::Yum::RPMPackage.new("testing", "1:1.6.5-9.36.el5", "x86_64", [])
+ end
+
+ it "should expose nevra (name-epoch-version-release-arch) available" do
+ @rpm.name.should == "testing"
+ @rpm.version.e.should == 1
+ @rpm.version.v.should == "1.6.5"
+ @rpm.version.r.should == "9.36.el5"
+ @rpm.arch.should == "x86_64"
+
+ @rpm.nevra.should == "testing-1:1.6.5-9.36.el5.x86_64"
+ @rpm.to_s.should == @rpm.nevra
+ end
+
+ it "should always have at least one provide, itself" do
+ @rpm.provides.size.should == 1
+ @rpm.provides[0].name == "testing"
+ @rpm.provides[0].version.evr == "1:1.6.5-9.36.el5"
+ @rpm.provides[0].flag == :==
+ end
+ end
+
+ describe "new - no parsing" do
+ before do
+ @rpm = Chef::Provider::Package::Yum::RPMPackage.new("testing", "1", "1.6.5", "9.36.el5", "x86_64", [])
+ end
+
+ it "should expose nevra (name-epoch-version-release-arch) available" do
+ @rpm.name.should == "testing"
+ @rpm.version.e.should == 1
+ @rpm.version.v.should == "1.6.5"
+ @rpm.version.r.should == "9.36.el5"
+ @rpm.arch.should == "x86_64"
+
+ @rpm.nevra.should == "testing-1:1.6.5-9.36.el5.x86_64"
+ @rpm.to_s.should == @rpm.nevra
+ end
+
+ it "should always have at least one provide, itself" do
+ @rpm.provides.size.should == 1
+ @rpm.provides[0].name == "testing"
+ @rpm.provides[0].version.evr == "1:1.6.5-9.36.el5"
+ @rpm.provides[0].flag == :==
+ end
+ end
+
+ it "should raise an error unless passed 4 or 6 args" do
+ lambda {
+ Chef::Provider::Package::Yum::RPMPackage.new()
+ }.should raise_error(ArgumentError)
+ lambda {
+ Chef::Provider::Package::Yum::RPMPackage.new("testing")
+ }.should raise_error(ArgumentError)
+ lambda {
+ Chef::Provider::Package::Yum::RPMPackage.new("testing", "1:1.6.5-9.36.el5")
+ }.should raise_error(ArgumentError)
+ lambda {
+ Chef::Provider::Package::Yum::RPMPackage.new("testing", "1:1.6.5-9.36.el5", "x86_64")
+ }.should raise_error(ArgumentError)
+ lambda {
+ Chef::Provider::Package::Yum::RPMPackage.new("testing", "1:1.6.5-9.36.el5", "x86_64", [])
+ }.should_not raise_error
+ lambda {
+ Chef::Provider::Package::Yum::RPMPackage.new("testing", "1", "1.6.5", "9.36.el5", "x86_64")
+ }.should raise_error(ArgumentError)
+ lambda {
+ Chef::Provider::Package::Yum::RPMPackage.new("testing", "1", "1.6.5", "9.36.el5", "x86_64", [])
+ }.should_not raise_error
+ lambda {
+ Chef::Provider::Package::Yum::RPMPackage.new("testing", "1", "1.6.5", "9.36.el5", "x86_64", [], "extra")
+ }.should raise_error(ArgumentError)
+ end
+
+ describe "<=>" do
+ it "should sort alphabetically based on package name" do
+ [
+ [ "a-test",
+ "b-test" ],
+ [ "B-test",
+ "a-test" ],
+ [ "A-test",
+ "B-test" ],
+ [ "Aa-test",
+ "aA-test" ],
+ [ "1test",
+ "2test" ],
+ ].each do |smaller, larger|
+ sm = Chef::Provider::Package::Yum::RPMPackage.new(smaller, "0:0.0.1-1", "x86_64", [])
+ lg = Chef::Provider::Package::Yum::RPMPackage.new(larger, "0:0.0.1-1", "x86_64", [])
+ sm.should be < lg
+ lg.should be > sm
+ sm.should_not == lg
+ end
+ end
+
+ it "should sort alphabetically based on package arch" do
+ [
+ [ "i386",
+ "x86_64" ],
+ [ "i386",
+ "noarch" ],
+ [ "noarch",
+ "x86_64" ],
+ ].each do |smaller, larger|
+ sm = Chef::Provider::Package::Yum::RPMPackage.new("test-package", "0:0.0.1-1", smaller, [])
+ lg = Chef::Provider::Package::Yum::RPMPackage.new("test-package", "0:0.0.1-1", larger, [])
+ sm.should be < lg
+ lg.should be > sm
+ sm.should_not == lg
+ end
+ end
+ end
+
+end
+
+describe Chef::Provider::Package::Yum::RPMDbPackage do
+ before(:each) do
+ # name, version, arch, installed, available, repoid
+ @rpm_x = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-b", "0:1.6.5-9.36.el5", "noarch", [], false, true, "base")
+ @rpm_y = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-b", "0:1.6.5-9.36.el5", "noarch", [], true, true, "extras")
+ @rpm_z = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-b", "0:1.6.5-9.36.el5", "noarch", [], true, false, "other")
+ end
+
+ describe "initialize" do
+ it "should return a Chef::Provider::Package::Yum::RPMDbPackage object" do
+ @rpm_x.should be_kind_of(Chef::Provider::Package::Yum::RPMDbPackage)
+ end
+ end
+
+ describe "available" do
+ it "should return true" do
+ @rpm_x.available.should be == true
+ @rpm_y.available.should be == true
+ @rpm_z.available.should be == false
+ end
+ end
+
+ describe "installed" do
+ it "should return true" do
+ @rpm_x.installed.should be == false
+ @rpm_y.installed.should be == true
+ @rpm_z.installed.should be == true
+ end
+ end
+
+ describe "repoid" do
+ it "should return the source repository repoid" do
+ @rpm_x.repoid.should be == "base"
+ @rpm_y.repoid.should be == "extras"
+ @rpm_z.repoid.should be == "other"
+ end
+ end
+end
+
+describe Chef::Provider::Package::Yum::RPMDependency do
+ describe "new - with parsing" do
+ before do
+ @rpmdep = Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :==)
+ end
+
+ it "should expose name, version, flag available" do
+ @rpmdep.name.should == "testing"
+ @rpmdep.version.e.should == 1
+ @rpmdep.version.v.should == "1.6.5"
+ @rpmdep.version.r.should == "9.36.el5"
+ @rpmdep.flag.should == :==
+ end
+ end
+
+ describe "new - no parsing" do
+ before do
+ @rpmdep = Chef::Provider::Package::Yum::RPMDependency.new("testing", "1", "1.6.5", "9.36.el5", :==)
+ end
+
+ it "should expose name, version, flag available" do
+ @rpmdep.name.should == "testing"
+ @rpmdep.version.e.should == 1
+ @rpmdep.version.v.should == "1.6.5"
+ @rpmdep.version.r.should == "9.36.el5"
+ @rpmdep.flag.should == :==
+ end
+ end
+
+ it "should raise an error unless passed 3 or 5 args" do
+ lambda {
+ Chef::Provider::Package::Yum::RPMDependency.new()
+ }.should raise_error(ArgumentError)
+ lambda {
+ Chef::Provider::Package::Yum::RPMDependency.new("testing")
+ }.should raise_error(ArgumentError)
+ lambda {
+ Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5")
+ }.should raise_error(ArgumentError)
+ lambda {
+ Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :==)
+ }.should_not raise_error
+ lambda {
+ Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :==, "extra")
+ }.should raise_error(ArgumentError)
+ lambda {
+ Chef::Provider::Package::Yum::RPMDependency.new("testing", "1", "1.6.5", "9.36.el5", :==)
+ }.should_not raise_error
+ lambda {
+ Chef::Provider::Package::Yum::RPMDependency.new("testing", "1", "1.6.5", "9.36.el5", :==, "extra")
+ }.should raise_error(ArgumentError)
+ end
+
+ describe "parse" do
+ it "should parse a name, flag, version string into a valid RPMDependency object" do
+ @rpmdep = Chef::Provider::Package::Yum::RPMDependency.parse("testing >= 1:1.6.5-9.36.el5")
+
+ @rpmdep.name.should == "testing"
+ @rpmdep.version.e.should == 1
+ @rpmdep.version.v.should == "1.6.5"
+ @rpmdep.version.r.should == "9.36.el5"
+ @rpmdep.flag.should == :>=
+ end
+
+ it "should parse a name into a valid RPMDependency object" do
+ @rpmdep = Chef::Provider::Package::Yum::RPMDependency.parse("testing")
+
+ @rpmdep.name.should == "testing"
+ @rpmdep.version.e.should == nil
+ @rpmdep.version.v.should == nil
+ @rpmdep.version.r.should == nil
+ @rpmdep.flag.should == :==
+ end
+
+ it "should parse an invalid string into the name of a RPMDependency object" do
+ @rpmdep = Chef::Provider::Package::Yum::RPMDependency.parse("testing blah >")
+
+ @rpmdep.name.should == "testing blah >"
+ @rpmdep.version.e.should == nil
+ @rpmdep.version.v.should == nil
+ @rpmdep.version.r.should == nil
+ @rpmdep.flag.should == :==
+ end
+
+ it "should parse various valid flags" do
+ [
+ [ ">", :> ],
+ [ ">=", :>= ],
+ [ "=", :== ],
+ [ "==", :== ],
+ [ "<=", :<= ],
+ [ "<", :< ]
+ ].each do |before, after|
+ @rpmdep = Chef::Provider::Package::Yum::RPMDependency.parse("testing #{before} 1:1.1-1")
+ @rpmdep.flag.should == after
+ end
+ end
+
+ it "should parse various invalid flags and treat them as names" do
+ [
+ [ "<>", :== ],
+ [ "!=", :== ],
+ [ ">>", :== ],
+ [ "<<", :== ],
+ [ "!", :== ],
+ [ "~", :== ]
+ ].each do |before, after|
+ @rpmdep = Chef::Provider::Package::Yum::RPMDependency.parse("testing #{before} 1:1.1-1")
+ @rpmdep.name.should == "testing #{before} 1:1.1-1"
+ @rpmdep.flag.should == after
+ end
+ end
+ end
+
+ describe "satisfy?" do
+ it "should raise an error unless a RPMDependency is passed" do
+ @rpmprovide = Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :==)
+ @rpmrequire = Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :>=)
+ lambda {
+ @rpmprovide.satisfy?("hi")
+ }.should raise_error(ArgumentError)
+ lambda {
+ @rpmprovide.satisfy?(@rpmrequire)
+ }.should_not raise_error
+ end
+
+ it "should validate dependency satisfaction logic for standard examples" do
+ [
+ # names
+ [ "test", "test", true ],
+ [ "test", "foo", false ],
+ # full: epoch:version-relese
+ [ "testing = 1:1.1-1", "testing > 1:1.1-0", true ],
+ [ "testing = 1:1.1-1", "testing >= 1:1.1-0", true ],
+ [ "testing = 1:1.1-1", "testing >= 1:1.1-1", true ],
+ [ "testing = 1:1.1-1", "testing = 1:1.1-1", true ],
+ [ "testing = 1:1.1-1", "testing == 1:1.1-1", true ],
+ [ "testing = 1:1.1-1", "testing <= 1:1.1-1", true ],
+ [ "testing = 1:1.1-1", "testing <= 1:1.1-0", false ],
+ [ "testing = 1:1.1-1", "testing < 1:1.1-0", false ],
+ # partial: epoch:version
+ [ "testing = 1:1.1", "testing > 1:1.0", true ],
+ [ "testing = 1:1.1", "testing >= 1:1.0", true ],
+ [ "testing = 1:1.1", "testing >= 1:1.1", true ],
+ [ "testing = 1:1.1", "testing = 1:1.1", true ],
+ [ "testing = 1:1.1", "testing == 1:1.1", true ],
+ [ "testing = 1:1.1", "testing <= 1:1.1", true ],
+ [ "testing = 1:1.1", "testing <= 1:1.0", false ],
+ [ "testing = 1:1.1", "testing < 1:1.0", false ],
+ # partial: epoch
+ [ "testing = 1:", "testing > 0:", true ],
+ [ "testing = 1:", "testing >= 0:", true ],
+ [ "testing = 1:", "testing >= 1:", true ],
+ [ "testing = 1:", "testing = 1:", true ],
+ [ "testing = 1:", "testing == 1:", true ],
+ [ "testing = 1:", "testing <= 1:", true ],
+ [ "testing = 1:", "testing <= 0:", false ],
+ [ "testing = 1:", "testing < 0:", false ],
+ # mix and match!
+ [ "testing = 1:1.1-1", "testing == 1:1.1", true ],
+ [ "testing = 1:1.1-1", "testing == 1:", true ],
+ ].each do |prov, req, result|
+ @rpmprovide = Chef::Provider::Package::Yum::RPMDependency.parse(prov)
+ @rpmrequire = Chef::Provider::Package::Yum::RPMDependency.parse(req)
+
+ @rpmprovide.satisfy?(@rpmrequire).should == result
+ @rpmrequire.satisfy?(@rpmprovide).should == result
+ end
+ end
+ end
+
+end
+
+# thanks resource_collection_spec.rb!
+describe Chef::Provider::Package::Yum::RPMDb do
+ before(:each) do
+ @rpmdb = Chef::Provider::Package::Yum::RPMDb.new
+ # name, version, arch, installed, available
+ deps_v = [
+ Chef::Provider::Package::Yum::RPMDependency.parse("libz.so.1()(64bit)"),
+ Chef::Provider::Package::Yum::RPMDependency.parse("test-package-a = 0:1.6.5-9.36.el5")
+ ]
+ deps_z = [
+ Chef::Provider::Package::Yum::RPMDependency.parse("libz.so.1()(64bit)"),
+ Chef::Provider::Package::Yum::RPMDependency.parse("config(test) = 0:1.6.5-9.36.el5"),
+ Chef::Provider::Package::Yum::RPMDependency.parse("test-package-c = 0:1.6.5-9.36.el5")
+ ]
+ @rpm_v = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-a", "0:1.6.5-9.36.el5", "i386", deps_v, true, false, "base")
+ @rpm_w = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-b", "0:1.6.5-9.36.el5", "i386", [], true, true, "extras")
+ @rpm_x = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-b", "0:1.6.5-9.36.el5", "x86_64", [], false, true, "extras")
+ @rpm_y = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-b", "1:1.6.5-9.36.el5", "x86_64", [], true, true, "extras")
+ @rpm_z = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-c", "0:1.6.5-9.36.el5", "noarch", deps_z, true, true, "base")
+ @rpm_z_mirror = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-c", "0:1.6.5-9.36.el5", "noarch", deps_z, true, true, "base")
+ end
+
+ describe "initialize" do
+ it "should return a Chef::Provider::Package::Yum::RPMDb object" do
+ @rpmdb.should be_kind_of(Chef::Provider::Package::Yum::RPMDb)
+ end
+ end
+
+ describe "push" do
+ it "should accept an RPMDbPackage object through pushing" do
+ lambda { @rpmdb.push(@rpm_w) }.should_not raise_error
+ end
+
+ it "should accept multiple RPMDbPackage object through pushing" do
+ lambda { @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z) }.should_not raise_error
+ end
+
+ it "should only accept an RPMDbPackage object" do
+ lambda { @rpmdb.push("string") }.should raise_error
+ end
+
+ it "should add the package to the package db" do
+ @rpmdb.push(@rpm_w)
+ @rpmdb["test-package-b"].should_not be == nil
+ end
+
+ it "should add conditionally add the package to the available list" do
+ @rpmdb.available_size.should be == 0
+ @rpmdb.push(@rpm_v, @rpm_w)
+ @rpmdb.available_size.should be == 1
+ end
+
+ it "should add conditionally add the package to the installed list" do
+ @rpmdb.installed_size.should be == 0
+ @rpmdb.push(@rpm_w, @rpm_x)
+ @rpmdb.installed_size.should be == 1
+ end
+
+ it "should have a total of 2 packages in the RPMDb" do
+ @rpmdb.size.should be == 0
+ @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z)
+ @rpmdb.size.should be == 2
+ end
+
+ it "should keep the Array unique when a duplicate is pushed" do
+ @rpmdb.push(@rpm_z, @rpm_z_mirror)
+ @rpmdb["test-package-c"].size.should be == 1
+ end
+
+ it "should register the package provides in the provides index" do
+ @rpmdb.push(@rpm_v, @rpm_w, @rpm_z)
+ @rpmdb.lookup_provides("test-package-a")[0].should be == @rpm_v
+ @rpmdb.lookup_provides("config(test)")[0].should be == @rpm_z
+ @rpmdb.lookup_provides("libz.so.1()(64bit)")[0].should be == @rpm_v
+ @rpmdb.lookup_provides("libz.so.1()(64bit)")[1].should be == @rpm_z
+ end
+ end
+
+ describe "<<" do
+ it "should accept an RPMPackage object through the << operator" do
+ lambda { @rpmdb << @rpm_w }.should_not raise_error
+ end
+ end
+
+ describe "lookup" do
+ it "should return an Array of RPMPackage objects by index" do
+ @rpmdb << @rpm_w
+ @rpmdb.lookup("test-package-b").should be_kind_of(Array)
+ end
+ end
+
+ describe "[]" do
+ it "should return an Array of RPMPackage objects though the [index] operator" do
+ @rpmdb << @rpm_w
+ @rpmdb["test-package-b"].should be_kind_of(Array)
+ end
+
+ it "should return an Array of 3 RPMPackage objects" do
+ @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z)
+ @rpmdb["test-package-b"].size.should be == 3
+ end
+
+ it "should return an Array of RPMPackage objects sorted from newest to oldest" do
+ @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z)
+ @rpmdb["test-package-b"][0].should be == @rpm_y
+ @rpmdb["test-package-b"][1].should be == @rpm_x
+ @rpmdb["test-package-b"][2].should be == @rpm_w
+ end
+ end
+
+ describe "lookup_provides" do
+ it "should return an Array of RPMPackage objects by index" do
+ @rpmdb << @rpm_z
+ x = @rpmdb.lookup_provides("config(test)")
+ x.should be_kind_of(Array)
+ x[0].should be == @rpm_z
+ end
+ end
+
+ describe "clear" do
+ it "should clear the RPMDb" do
+ @rpmdb.should_receive(:clear_available).once
+ @rpmdb.should_receive(:clear_installed).once
+ @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z)
+ @rpmdb.size.should_not be == 0
+ @rpmdb.lookup_provides("config(test)").should be_kind_of(Array)
+ @rpmdb.clear
+ @rpmdb.lookup_provides("config(test)").should be == nil
+ @rpmdb.size.should be == 0
+ end
+ end
+
+ describe "clear_available" do
+ it "should clear the available list" do
+ @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z)
+ @rpmdb.available_size.should_not be == 0
+ @rpmdb.clear_available
+ @rpmdb.available_size.should be == 0
+ end
+ end
+
+ describe "available?" do
+ it "should return true if a package is available" do
+ @rpmdb.available?(@rpm_w).should be == false
+ @rpmdb.push(@rpm_v, @rpm_w)
+ @rpmdb.available?(@rpm_v).should be == false
+ @rpmdb.available?(@rpm_w).should be == true
+ end
+ end
+
+ describe "clear_installed" do
+ it "should clear the installed list" do
+ @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z)
+ @rpmdb.installed_size.should_not be == 0
+ @rpmdb.clear_installed
+ @rpmdb.installed_size.should be == 0
+ end
+ end
+
+ describe "installed?" do
+ it "should return true if a package is installed" do
+ @rpmdb.installed?(@rpm_w).should be == false
+ @rpmdb.push(@rpm_w, @rpm_x)
+ @rpmdb.installed?(@rpm_w).should be == true
+ @rpmdb.installed?(@rpm_x).should be == false
+ end
+ end
+
+ describe "whatprovides" do
+ it "should raise an error unless a RPMDependency is passed" do
+ @rpmprovide = Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :==)
+ @rpmrequire = Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :>=)
+ lambda {
+ @rpmdb.whatprovides("hi")
+ }.should raise_error(ArgumentError)
+ lambda {
+ @rpmdb.whatprovides(@rpmrequire)
+ }.should_not raise_error
+ end
+
+ it "should return an Array of packages statisfying a RPMDependency" do
+ @rpmdb.push(@rpm_v, @rpm_w, @rpm_z)
+
+ @rpmrequire = Chef::Provider::Package::Yum::RPMDependency.parse("test-package-a >= 1.6.5")
+ x = @rpmdb.whatprovides(@rpmrequire)
+ x.should be_kind_of(Array)
+ x[0].should be == @rpm_v
+
+ @rpmrequire = Chef::Provider::Package::Yum::RPMDependency.parse("libz.so.1()(64bit)")
+ x = @rpmdb.whatprovides(@rpmrequire)
+ x.should be_kind_of(Array)
+ x[0].should be == @rpm_v
+ x[1].should be == @rpm_z
+ end
+ end
+
+end
+
+describe Chef::Provider::Package::Yum::YumCache do
+ # allow for the reset of a Singleton
+ # thanks to Ian White (http://blog.ardes.com/2006/12/11/testing-singletons-with-ruby)
+ class << Chef::Provider::Package::Yum::YumCache
+ def reset_instance
+ Singleton.send :__init__, self
+ self
+ end
+ end
+
+ before(:each) do
+ yum_dump_good_output = <<EOF
+[option installonlypkgs] kernel kernel-bigmem kernel-enterprise
+erlang-mochiweb 0 1.4.1 5.el5 x86_64 ['erlang-mochiweb = 1.4.1-5.el5', 'mochiweb = 1.4.1-5.el5'] i installed
+zip 0 2.31 2.el5 x86_64 ['zip = 2.31-2.el5'] r base
+zisofs-tools 0 1.0.6 3.2.2 x86_64 [] a extras
+zlib 0 1.2.3 3 x86_64 ['zlib = 1.2.3-3', 'libz.so.1()(64bit)'] r base
+zlib 0 1.2.3 3 i386 ['zlib = 1.2.3-3', 'libz.so.1'] r base
+zlib-devel 0 1.2.3 3 i386 [] a extras
+zlib-devel 0 1.2.3 3 x86_64 ['zlib-devel = 1.2.3-3'] r base
+znc 0 0.098 1.el5 x86_64 [] a base
+znc-devel 0 0.098 1.el5 i386 [] a extras
+znc-devel 0 0.098 1.el5 x86_64 [] a base
+znc-extra 0 0.098 1.el5 x86_64 [] a base
+znc-modtcl 0 0.098 1.el5 x86_64 [] a base
+znc-test.beta1 0 0.098 1.el5 x86_64 [] a extras
+znc-test.test.beta1 0 0.098 1.el5 x86_64 [] a base
+EOF
+
+ yum_dump_bad_output_separators = <<EOF
+zip 0 2.31 2.el5 x86_64 ['zip = 2.31-2.el5'] r base
+zlib 0 1.2.3 3 x86_64 ['zlib = 1.2.3-3', 'libz.so.1()(64bit)'] i base bad
+zlib-devel 0 1.2.3 3 i386 [] a extras
+bad zlib-devel 0 1.2.3 3 x86_64 ['zlib-devel = 1.2.3-3'] i installed
+znc-modtcl 0 0.098 1.el5 x86_64 [] a base bad
+EOF
+
+ yum_dump_bad_output_type = <<EOF
+zip 0 2.31 2.el5 x86_64 ['zip = 2.31-2.el5'] r base
+zlib 0 1.2.3 3 x86_64 ['zlib = 1.2.3-3', 'libz.so.1()(64bit)'] c base
+zlib-devel 0 1.2.3 3 i386 [] a extras
+zlib-devel 0 1.2.3 3 x86_64 ['zlib-devel = 1.2.3-3'] bad installed
+znc-modtcl 0 0.098 1.el5 x86_64 [] a base
+EOF
+
+ yum_dump_error = <<EOF
+yum-dump Config Error: File contains no section headers.
+file: file://///etc/yum.repos.d/CentOS-Base.repo, line: 12
+'qeqwewe\n'
+EOF
+
+ @status = mock("Status", :exitstatus => 0)
+ @status_bad = mock("Status", :exitstatus => 1)
+ @stdin = mock("STDIN", :nil_object => true)
+ @stdout = mock("STDOUT", :nil_object => true)
+ @stdout_good = yum_dump_good_output.split("\n")
+ @stdout_bad_type = yum_dump_bad_output_type.split("\n")
+ @stdout_bad_separators = yum_dump_bad_output_separators.split("\n")
+ @stderr = mock("STDERR", :nil_object => true)
+ @stderr.stub!(:readlines).and_return(yum_dump_error.split("\n"))
+ @pid = mock("PID", :nil_object => true)
+
+ # new singleton each time
+ Chef::Provider::Package::Yum::YumCache.reset_instance
+ @yc = Chef::Provider::Package::Yum::YumCache.instance
+ # load valid data
+ @yc.stub!(:popen4).and_yield(@pid, @stdin, @stdout_good, @stderr).and_return(@status)
+ end
+
+ describe "initialize" do
+ it "should return a Chef::Provider::Package::Yum::YumCache object" do
+ @yc.should be_kind_of(Chef::Provider::Package::Yum::YumCache)
+ end
+
+ it "should register reload for start of Chef::Client runs" do
+ Chef::Provider::Package::Yum::YumCache.reset_instance
+ Chef::Client.should_receive(:when_run_starts) do |&b|
+ b.should_not be_nil
+ end
+ @yc = Chef::Provider::Package::Yum::YumCache.instance
+ end
+ end
+
+ describe "refresh" do
+ it "should implicitly call yum-dump.py only once by default after being instantiated" do
+ @yc.should_receive(:popen4).once
+ @yc.installed_version("zlib")
+ @yc.reset
+ @yc.installed_version("zlib")
+ end
+
+ it "should run yum-dump.py using the system python when next_refresh is for :all" do
+ @yc.reload
+ @yc.should_receive(:popen4).with(%r{^/usr/bin/python .*/yum-dump.py --options --installed-provides$}, :waitlast=>true)
+ @yc.refresh
+ end
+
+ it "should run yum-dump.py with the installed flag when next_refresh is for :installed" do
+ @yc.reload_installed
+ @yc.should_receive(:popen4).with(%r{^/usr/bin/python .*/yum-dump.py --installed$}, :waitlast=>true)
+ @yc.refresh
+ end
+
+ it "should run yum-dump.py with the all-provides flag when next_refresh is for :provides" do
+ @yc.reload_provides
+ @yc.should_receive(:popen4).with(%r{^/usr/bin/python .*/yum-dump.py --options --all-provides$}, :waitlast=>true)
+ @yc.refresh
+ end
+
+ it "should warn about invalid data with too many separators" do
+ @yc.stub!(:popen4).and_yield(@pid, @stdin, @stdout_bad_separators, @stderr).and_return(@status)
+ Chef::Log.should_receive(:warn).exactly(3).times.with(%r{Problem parsing})
+ @yc.refresh
+ end
+
+ it "should warn about invalid data with an incorrect type" do
+ @yc.stub!(:popen4).and_yield(@pid, @stdin, @stdout_bad_type, @stderr).and_return(@status)
+ Chef::Log.should_receive(:warn).exactly(2).times.with(%r{Problem parsing})
+ @yc.refresh
+ end
+
+ it "should warn about no output from yum-dump.py" do
+ @yc.stub!(:popen4).and_yield(@pid, @stdin, [], @stderr).and_return(@status)
+ Chef::Log.should_receive(:warn).exactly(1).times.with(%r{no output from yum-dump.py})
+ @yc.refresh
+ end
+
+ it "should raise exception yum-dump.py exits with a non zero status" do
+ @yc.stub!(:popen4).and_yield(@pid, @stdin, [], @stderr).and_return(@status_bad)
+ lambda { @yc.refresh}.should raise_error(Chef::Exceptions::Package, %r{CentOS-Base.repo, line: 12})
+ end
+
+ it "should parse type 'i' into an installed state for a package" do
+ @yc.available_version("erlang-mochiweb").should be == nil
+ @yc.installed_version("erlang-mochiweb").should_not be == nil
+ end
+
+ it "should parse type 'a' into an available state for a package" do
+ @yc.available_version("znc").should_not be == nil
+ @yc.installed_version("znc").should be == nil
+ end
+
+ it "should parse type 'r' into an installed and available states for a package" do
+ @yc.available_version("zip").should_not be == nil
+ @yc.installed_version("zip").should_not be == nil
+ end
+
+ it "should parse installonlypkgs from yum-dump.py options output" do
+ @yc.allow_multi_install.should be == %w{kernel kernel-bigmem kernel-enterprise}
+ end
+ end
+
+ describe "installed_version" do
+ it "should take one or two arguments" do
+ lambda { @yc.installed_version("zip") }.should_not raise_error(ArgumentError)
+ lambda { @yc.installed_version("zip", "i386") }.should_not raise_error(ArgumentError)
+ lambda { @yc.installed_version("zip", "i386", "extra") }.should raise_error(ArgumentError)
+ end
+
+ it "should return version-release for matching package regardless of arch" do
+ @yc.installed_version("zip", "x86_64").should be == "2.31-2.el5"
+ @yc.installed_version("zip", nil).should be == "2.31-2.el5"
+ end
+
+ it "should return version-release for matching package and arch" do
+ @yc.installed_version("zip", "x86_64").should be == "2.31-2.el5"
+ @yc.installed_version("zisofs-tools", "i386").should be == nil
+ end
+
+ it "should return nil for an unmatched package" do
+ @yc.installed_version(nil, nil).should be == nil
+ @yc.installed_version("test1", nil).should be == nil
+ @yc.installed_version("test2", "x86_64").should be == nil
+ end
+ end
+
+ describe "available_version" do
+ it "should take one or two arguments" do
+ lambda { @yc.available_version("zisofs-tools") }.should_not raise_error(ArgumentError)
+ lambda { @yc.available_version("zisofs-tools", "i386") }.should_not raise_error(ArgumentError)
+ lambda { @yc.available_version("zisofs-tools", "i386", "extra") }.should raise_error(ArgumentError)
+ end
+
+ it "should return version-release for matching package regardless of arch" do
+ @yc.available_version("zip", "x86_64").should be == "2.31-2.el5"
+ @yc.available_version("zip", nil).should be == "2.31-2.el5"
+ end
+
+ it "should return version-release for matching package and arch" do
+ @yc.available_version("zip", "x86_64").should be == "2.31-2.el5"
+ @yc.available_version("zisofs-tools", "i386").should be == nil
+ end
+
+ it "should return nil for an unmatched package" do
+ @yc.available_version(nil, nil).should be == nil
+ @yc.available_version("test1", nil).should be == nil
+ @yc.available_version("test2", "x86_64").should be == nil
+ end
+ end
+
+ describe "version_available?" do
+ it "should take two or three arguments" do
+ lambda { @yc.version_available?("zisofs-tools") }.should raise_error(ArgumentError)
+ lambda { @yc.version_available?("zisofs-tools", "1.0.6-3.2.2") }.should_not raise_error(ArgumentError)
+ lambda { @yc.version_available?("zisofs-tools", "1.0.6-3.2.2", "x86_64") }.should_not raise_error(ArgumentError)
+ end
+
+ it "should return true if our package-version-arch is available" do
+ @yc.version_available?("zisofs-tools", "1.0.6-3.2.2", "x86_64").should be == true
+ end
+
+ it "should return true if our package-version, no arch, is available" do
+ @yc.version_available?("zisofs-tools", "1.0.6-3.2.2", nil).should be == true
+ @yc.version_available?("zisofs-tools", "1.0.6-3.2.2").should be == true
+ end
+
+ it "should return false if our package-version-arch isn't available" do
+ @yc.version_available?("zisofs-tools", "1.0.6-3.2.2", "pretend").should be == false
+ @yc.version_available?("zisofs-tools", "pretend", "x86_64").should be == false
+ @yc.version_available?("pretend", "1.0.6-3.2.2", "x86_64").should be == false
+ end
+
+ it "should return false if our package-version, no arch, isn't available" do
+ @yc.version_available?("zisofs-tools", "pretend", nil).should be == false
+ @yc.version_available?("zisofs-tools", "pretend").should be == false
+ @yc.version_available?("pretend", "1.0.6-3.2.2").should be == false
+ end
+ end
+
+ describe "package_repository" do
+ it "should take two or three arguments" do
+ lambda { @yc.package_repository("zisofs-tools") }.should raise_error(ArgumentError)
+ lambda { @yc.package_repository("zisofs-tools", "1.0.6-3.2.2") }.should_not raise_error(ArgumentError)
+ lambda { @yc.package_repository("zisofs-tools", "1.0.6-3.2.2", "x86_64") }.should_not raise_error(ArgumentError)
+ end
+
+ it "should return repoid for package-version-arch" do
+ @yc.package_repository("zlib-devel", "1.2.3-3", "i386").should be == "extras"
+ @yc.package_repository("zlib-devel", "1.2.3-3", "x86_64").should be == "base"
+ end
+
+ it "should return repoid for package-version, no arch" do
+ @yc.package_repository("zisofs-tools", "1.0.6-3.2.2", nil).should be == "extras"
+ @yc.package_repository("zisofs-tools", "1.0.6-3.2.2").should be == "extras"
+ end
+
+ it "should return nil when no match for package-version-arch" do
+ @yc.package_repository("zisofs-tools", "1.0.6-3.2.2", "pretend").should be == nil
+ @yc.package_repository("zisofs-tools", "pretend", "x86_64").should be == nil
+ @yc.package_repository("pretend", "1.0.6-3.2.2", "x86_64").should be == nil
+ end
+
+ it "should return nil when no match for package-version, no arch" do
+ @yc.package_repository("zisofs-tools", "pretend", nil).should be == nil
+ @yc.package_repository("zisofs-tools", "pretend").should be == nil
+ @yc.package_repository("pretend", "1.0.6-3.2.2").should be == nil
+ end
+ end
+
+ describe "reset" do
+ it "should empty the installed and available packages RPMDb" do
+ @yc.available_version("zip", "x86_64").should be == "2.31-2.el5"
+ @yc.installed_version("zip", "x86_64").should be == "2.31-2.el5"
+ @yc.reset
+ @yc.available_version("zip", "x86_64").should be == nil
+ @yc.installed_version("zip", "x86_64").should be == nil
+ end
+ end
+
+ describe "package_available?" do
+ it "should return true a package name is available" do
+ @yc.package_available?("zisofs-tools").should be == true
+ @yc.package_available?("moo").should be == false
+ @yc.package_available?(nil).should be == false
+ end
+
+ it "should return true a package name + arch is available" do
+ @yc.package_available?("zlib-devel.i386").should be == true
+ @yc.package_available?("zisofs-tools.x86_64").should be == true
+ @yc.package_available?("znc-test.beta1.x86_64").should be == true
+ @yc.package_available?("znc-test.beta1").should be == true
+ @yc.package_available?("znc-test.test.beta1").should be == true
+ @yc.package_available?("moo.i386").should be == false
+ @yc.package_available?("zisofs-tools.beta").should be == false
+ @yc.package_available?("znc-test.test").should be == false
+ end
+ end
+
+end
diff --git a/spec/unit/provider/package/zypper_spec.rb b/spec/unit/provider/package/zypper_spec.rb
new file mode 100644
index 0000000000..fab78f4917
--- /dev/null
+++ b/spec/unit/provider/package/zypper_spec.rb
@@ -0,0 +1,159 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::Package::Zypper do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Package.new("cups")
+
+ @current_resource = Chef::Resource::Package.new("cups")
+
+ @status = mock("Status", :exitstatus => 0)
+
+ @provider = Chef::Provider::Package::Zypper.new(@new_resource, @run_context)
+ Chef::Resource::Package.stub!(:new).and_return(@current_resource)
+ @provider.stub!(:popen4).and_return(@status)
+ @stderr = StringIO.new
+ @stdout = StringIO.new
+ @pid = mock("PID")
+ @provider.stub!(:`).and_return("2.0")
+ end
+
+ describe "when loading the current package state" do
+ it "should create a current resource with the name of the new_resource" do
+ Chef::Resource::Package.should_receive(:new).and_return(@current_resource)
+ @provider.load_current_resource
+ end
+
+ it "should set the current resources package name to the new resources package name" do
+ @current_resource.should_receive(:package_name).with(@new_resource.package_name)
+ @provider.load_current_resource
+ end
+
+ it "should run zypper info with the package name" do
+ @provider.should_receive(:popen4).with("zypper info #{@new_resource.package_name}").and_return(@status)
+ @provider.load_current_resource
+ end
+
+ it "should set the installed version to nil on the current resource if zypper info installed version is (none)" do
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @current_resource.should_receive(:version).with(nil).and_return(true)
+ @provider.load_current_resource
+ end
+
+ it "should set the installed version if zypper info has one" do
+ @stdout = StringIO.new("Version: 1.0\nInstalled: Yes\n")
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @current_resource.should_receive(:version).with("1.0").and_return(true)
+ @provider.load_current_resource
+ end
+
+ it "should set the candidate version if zypper info has one" do
+ @stdout = StringIO.new("Version: 1.0\nInstalled: No\nStatus: out-of-date (version 0.9 installed)")
+
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.load_current_resource
+ @provider.candidate_version.should eql("1.0")
+ end
+
+ it "should raise an exception if zypper info fails" do
+ @status.should_receive(:exitstatus).and_return(1)
+ lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Package)
+ end
+
+ it "should not raise an exception if zypper info succeeds" do
+ @status.should_receive(:exitstatus).and_return(0)
+ lambda { @provider.load_current_resource }.should_not raise_error(Chef::Exceptions::Package)
+ end
+
+ it "should return the current resouce" do
+ @provider.load_current_resource.should eql(@current_resource)
+ end
+ end
+
+ describe "install_package" do
+ it "should run zypper install with the package name and version" do
+ @provider.should_receive(:run_command).with({
+ :command => "zypper -n --no-gpg-checks install -l emacs=1.0",
+ })
+ @provider.install_package("emacs", "1.0")
+ end
+ end
+
+ describe "upgrade_package" do
+ it "should run zypper update with the package name and version" do
+ @provider.should_receive(:run_command).with({
+ :command => "zypper -n --no-gpg-checks install -l emacs=1.0",
+ })
+ @provider.upgrade_package("emacs", "1.0")
+ end
+ end
+
+ describe "remove_package" do
+ it "should run zypper remove with the package name" do
+ @provider.should_receive(:run_command).with({
+ :command => "zypper -n --no-gpg-checks remove emacs=1.0",
+ })
+ @provider.remove_package("emacs", "1.0")
+ end
+ end
+
+ describe "purge_package" do
+ it "should run remove_package with the name and version" do
+ @provider.should_receive(:remove_package).with("emacs", "1.0")
+ @provider.purge_package("emacs", "1.0")
+ end
+ end
+
+ describe "on an older zypper" do
+ before(:each) do
+ @provider.stub!(:`).and_return("0.11.6")
+ end
+
+ describe "install_package" do
+ it "should run zypper install with the package name and version" do
+ @provider.should_receive(:run_command).with({
+ :command => "zypper install -y emacs"
+ })
+ @provider.install_package("emacs", "1.0")
+ end
+ end
+
+ describe "upgrade_package" do
+ it "should run zypper update with the package name and version" do
+ @provider.should_receive(:run_command).with({
+ :command => "zypper install -y emacs"
+ })
+ @provider.upgrade_package("emacs", "1.0")
+ end
+ end
+
+ describe "remove_package" do
+ it "should run zypper remove with the package name" do
+ @provider.should_receive(:run_command).with({
+ :command => "zypper remove -y emacs"
+ })
+ @provider.remove_package("emacs", "1.0")
+ end
+ end
+ end
+end
diff --git a/spec/unit/provider/package_spec.rb b/spec/unit/provider/package_spec.rb
new file mode 100644
index 0000000000..4052bc1ffd
--- /dev/null
+++ b/spec/unit/provider/package_spec.rb
@@ -0,0 +1,429 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::Package do
+ before do
+ #Terrible, but we need to implement a pseduo-filesystem for testing
+ #to not have this line. Only affects updating state fields.
+ Chef::Provider::CookbookFile.any_instance.stub(:update_new_file_state)
+
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Package.new('emacs')
+ @current_resource = Chef::Resource::Package.new('emacs')
+ @provider = Chef::Provider::Package.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+
+ @provider.candidate_version = "1.0"
+ end
+
+ describe "when installing a package" do
+ before(:each) do
+ @provider.current_resource = @current_resource
+ @provider.stub!(:install_package).and_return(true)
+ end
+
+ it "should raise a Chef::Exceptions::Package if no version is specified, and no candidate is available" do
+ @provider.candidate_version = nil
+ lambda { @provider.run_action(:install) }.should raise_error(Chef::Exceptions::Package)
+ end
+
+ it "should call preseed_package if a response_file is given" do
+ @new_resource.response_file("foo")
+ @provider.should_receive(:get_preseed_file).with(
+ @new_resource.name,
+ @provider.candidate_version
+ ).and_return("/var/cache/preseed-test")
+
+ @provider.should_receive(:preseed_package).with(
+ "/var/cache/preseed-test"
+ ).and_return(true)
+ @provider.run_action(:install)
+ end
+
+ it "should not call preseed_package if a response_file is not given" do
+ @provider.should_not_receive(:preseed_package)
+ @provider.run_action(:install)
+ end
+
+ it "should install the package at the candidate_version if it is not already installed" do
+ @provider.should_receive(:install_package).with(
+ @new_resource.name,
+ @provider.candidate_version
+ ).and_return(true)
+ @provider.run_action(:install)
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should install the package at the version specified if it is not already installed" do
+ @new_resource.version("1.0")
+ @provider.should_receive(:install_package).with(
+ @new_resource.name,
+ @new_resource.version
+ ).and_return(true)
+ @provider.run_action(:install)
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should install the package at the version specified if a different version is installed" do
+ @new_resource.version("1.0")
+ @current_resource.stub!(:version).and_return("0.99")
+ @provider.should_receive(:install_package).with(
+ @new_resource.name,
+ @new_resource.version
+ ).and_return(true)
+ @provider.run_action(:install)
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should not install the package if it is already installed and no version is specified" do
+ @current_resource.version("1.0")
+ @provider.should_not_receive(:install_package)
+ @provider.run_action(:install)
+ @new_resource.should_not be_updated_by_last_action
+ end
+
+ it "should not install the package if it is already installed at the version specified" do
+ @current_resource.version("1.0")
+ @new_resource.version("1.0")
+ @provider.should_not_receive(:install_package)
+ @provider.run_action(:install)
+ @new_resource.should_not be_updated_by_last_action
+ end
+
+ it "should call the candidate_version accessor only once if the package is already installed and no version is specified" do
+ @current_resource.version("1.0")
+ @provider.stub!(:candidate_version).and_return("1.0")
+ @provider.run_action(:install)
+ end
+
+ it "should call the candidate_version accessor only once if the package is already installed at the version specified" do
+ @current_resource.version("1.0")
+ @new_resource.version("1.0")
+ @provider.run_action(:install)
+ end
+
+ it "should set the resource to updated if it installs the package" do
+ @provider.run_action(:install)
+ @new_resource.should be_updated
+ end
+
+ end
+
+ describe "when upgrading the package" do
+ before(:each) do
+ @provider.stub!(:upgrade_package).and_return(true)
+ end
+
+ it "should upgrade the package if the current version is not the candidate version" do
+ @provider.should_receive(:upgrade_package).with(
+ @new_resource.name,
+ @provider.candidate_version
+ ).and_return(true)
+ @provider.run_action(:upgrade)
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should set the resource to updated if it installs the package" do
+ @provider.run_action(:upgrade)
+ @new_resource.should be_updated
+ end
+
+ it "should not install the package if the current version is the candidate version" do
+ @current_resource.version "1.0"
+ @provider.should_not_receive(:upgrade_package)
+ @provider.run_action(:upgrade)
+ @new_resource.should_not be_updated_by_last_action
+ end
+
+ it "should print the word 'uninstalled' if there was no original version" do
+ @current_resource.stub!(:version).and_return(nil)
+ Chef::Log.should_receive(:info).with("package[emacs] upgraded from uninstalled to 1.0")
+ @provider.run_action(:upgrade)
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should raise a Chef::Exceptions::Package if current version and candidate are nil" do
+ @current_resource.stub!(:version).and_return(nil)
+ @provider.candidate_version = nil
+ lambda { @provider.run_action(:upgrade) }.should raise_error(Chef::Exceptions::Package)
+ end
+
+ it "should not install the package if candidate version is nil" do
+ @current_resource.version "1.0"
+ @provider.candidate_version = nil
+ @provider.should_not_receive(:upgrade_package)
+ @provider.run_action(:upgrade)
+ @new_resource.should_not be_updated_by_last_action
+ end
+ end
+
+ describe "When removing the package" do
+ before(:each) do
+ @provider.stub!(:remove_package).and_return(true)
+ @current_resource.version '1.4.2'
+ end
+
+ it "should remove the package if it is installed" do
+ @provider.should be_removing_package
+ @provider.should_receive(:remove_package).with('emacs', nil)
+ @provider.run_action(:remove)
+ @new_resource.should be_updated
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should remove the package at a specific version if it is installed at that version" do
+ @new_resource.version "1.4.2"
+ @provider.should be_removing_package
+ @provider.should_receive(:remove_package).with('emacs', '1.4.2')
+ @provider.run_action(:remove)
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should not remove the package at a specific version if it is not installed at that version" do
+ @new_resource.version "1.0"
+ @provider.should_not be_removing_package
+ @provider.should_not_receive(:remove_package)
+ @provider.run_action(:remove)
+ @new_resource.should_not be_updated_by_last_action
+ end
+
+ it "should not remove the package if it is not installed" do
+ @provider.should_not_receive(:remove_package)
+ @current_resource.stub!(:version).and_return(nil)
+ @provider.run_action(:remove)
+ @new_resource.should_not be_updated_by_last_action
+ end
+
+ it "should set the resource to updated if it removes the package" do
+ @provider.run_action(:remove)
+ @new_resource.should be_updated
+ end
+
+ end
+
+ describe "When purging the package" do
+ before(:each) do
+ @provider.stub!(:purge_package).and_return(true)
+ @current_resource.version '1.4.2'
+ end
+
+ it "should purge the package if it is installed" do
+ @provider.should be_removing_package
+ @provider.should_receive(:purge_package).with('emacs', nil)
+ @provider.run_action(:purge)
+ @new_resource.should be_updated
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should purge the package at a specific version if it is installed at that version" do
+ @new_resource.version "1.4.2"
+ @provider.should be_removing_package
+ @provider.should_receive(:purge_package).with('emacs', '1.4.2')
+ @provider.run_action(:purge)
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should not purge the package at a specific version if it is not installed at that version" do
+ @new_resource.version "1.0"
+ @provider.should_not be_removing_package
+ @provider.should_not_receive(:purge_package)
+ @provider.run_action(:purge)
+ @new_resource.should_not be_updated_by_last_action
+ end
+
+ it "should not purge the package if it is not installed" do
+ @current_resource.instance_variable_set(:@version, nil)
+ @provider.should_not be_removing_package
+
+ @provider.should_not_receive(:purge_package)
+ @provider.run_action(:purge)
+ @new_resource.should_not be_updated_by_last_action
+ end
+
+ it "should set the resource to updated if it purges the package" do
+ @provider.run_action(:purge)
+ @new_resource.should be_updated
+ end
+
+ end
+
+ describe "when reconfiguring the package" do
+ before(:each) do
+ @provider.stub!(:reconfig_package).and_return(true)
+ end
+
+ it "should info log, reconfigure the package and update the resource" do
+ @current_resource.stub!(:version).and_return('1.0')
+ @new_resource.stub!(:response_file).and_return(true)
+ @provider.should_receive(:get_preseed_file).and_return('/var/cache/preseed-test')
+ @provider.stub!(:preseed_package).and_return(true)
+ @provider.stub!(:reconfig_package).and_return(true)
+ Chef::Log.should_receive(:info).with("package[emacs] reconfigured")
+ @provider.should_receive(:reconfig_package)
+ @provider.run_action(:reconfig)
+ @new_resource.should be_updated
+ @new_resource.should be_updated_by_last_action
+ end
+
+ it "should debug log and not reconfigure the package if the package is not installed" do
+ @current_resource.stub!(:version).and_return(nil)
+ Chef::Log.should_receive(:debug).with("package[emacs] is NOT installed - nothing to do")
+ @provider.should_not_receive(:reconfig_package)
+ @provider.run_action(:reconfig)
+ @new_resource.should_not be_updated_by_last_action
+ end
+
+ it "should debug log and not reconfigure the package if no response_file is given" do
+ @current_resource.stub!(:version).and_return('1.0')
+ @new_resource.stub!(:response_file).and_return(nil)
+ Chef::Log.should_receive(:debug).with("package[emacs] no response_file provided - nothing to do")
+ @provider.should_not_receive(:reconfig_package)
+ @provider.run_action(:reconfig)
+ @new_resource.should_not be_updated_by_last_action
+ end
+
+ it "should debug log and not reconfigure the package if the response_file has not changed" do
+ @current_resource.stub!(:version).and_return('1.0')
+ @new_resource.stub!(:response_file).and_return(true)
+ @provider.should_receive(:get_preseed_file).and_return(false)
+ @provider.stub!(:preseed_package).and_return(false)
+ Chef::Log.should_receive(:debug).with("package[emacs] preseeding has not changed - nothing to do")
+ @provider.should_not_receive(:reconfig_package)
+ @provider.run_action(:reconfig)
+ @new_resource.should_not be_updated_by_last_action
+ end
+ end
+
+ describe "when running commands to be implemented by subclasses" do
+ it "should raises UnsupportedAction for install" do
+ lambda { @provider.install_package('emacs', '1.4.2') }.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+
+ it "should raises UnsupportedAction for upgrade" do
+ lambda { @provider.upgrade_package('emacs', '1.4.2') }.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+
+ it "should raises UnsupportedAction for remove" do
+ lambda { @provider.remove_package('emacs', '1.4.2') }.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+
+ it "should raises UnsupportedAction for purge" do
+ lambda { @provider.purge_package('emacs', '1.4.2') }.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+
+ it "should raise UnsupportedAction for preseed_package" do
+ preseed_file = "/tmp/sun-jdk-package-preseed-file.seed"
+ lambda { @provider.preseed_package(preseed_file) }.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+
+ it "should raise UnsupportedAction for reconfig" do
+ lambda { @provider.reconfig_package('emacs', '1.4.2') }.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+ end
+
+ describe "when given a response file" do
+ before(:each) do
+ @cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks"))
+ Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, @cookbook_repo) }
+
+ @node = Chef::Node.new
+ cl = Chef::CookbookLoader.new(@cookbook_repo)
+ cl.load_cookbooks
+ @cookbook_collection = Chef::CookbookCollection.new(cl)
+ @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+
+ @node.automatic_attrs[:platform] = 'PLATFORM: just testing'
+ @node.automatic_attrs[:platform_version] = 'PLATFORM VERSION: just testing'
+
+ @new_resource.response_file('java.response')
+ @new_resource.cookbook_name = 'java'
+ end
+
+ describe "creating the cookbook file resource to fetch the response file" do
+ before do
+ Chef::FileCache.should_receive(:create_cache_path).with('preseed/java').and_return("/tmp/preseed/java")
+ end
+ it "sets the preseed resource's runcontext to its own run context" do
+ Chef::FileCache.rspec_reset
+ Chef::FileCache.stub!(:create_cache_path).and_return("/tmp/preseed/java")
+ @provider.preseed_resource('java', '6').run_context.should_not be_nil
+ @provider.preseed_resource('java', '6').run_context.should equal(@provider.run_context)
+ end
+
+ it "should set the cookbook name of the remote file to the new resources cookbook name" do
+ @provider.preseed_resource('java', '6').cookbook_name.should == 'java'
+ end
+
+ it "should set remote files source to the new resources response file" do
+ @provider.preseed_resource('java', '6').source.should == 'java.response'
+ end
+
+ it "should never back up the cached response file" do
+ @provider.preseed_resource('java', '6').backup.should be_false
+ end
+
+ it "sets the install path of the resource to $file_cache/$cookbook/$pkg_name-$pkg_version.seed" do
+ @provider.preseed_resource('java', '6').path.should == '/tmp/preseed/java/java-6.seed'
+ end
+ end
+
+ describe "when installing the preseed file to the cache location" do
+ before do
+ @node.automatic_attrs[:platform] = :just_testing
+ @node.automatic_attrs[:platform_version] = :just_testing
+
+ @response_file_destination = Dir.tmpdir + '/preseed--java--java-6.seed'
+
+ @response_file_resource = Chef::Resource::CookbookFile.new(@response_file_destination, @run_context)
+ @response_file_resource.cookbook_name = 'java'
+ @response_file_resource.backup(false)
+ @response_file_resource.source('java.response')
+
+
+ @provider.should_receive(:preseed_resource).with('java', '6').and_return(@response_file_resource)
+ end
+
+ after do
+ FileUtils.rm(@response_file_destination) if ::File.exist?(@response_file_destination)
+ end
+
+ it "creates the preseed file in the cache" do
+ @response_file_resource.should_receive(:run_action).with(:create)
+ @provider.get_preseed_file("java", "6")
+ end
+
+ it "returns the path to the response file if the response file was updated" do
+ @provider.get_preseed_file("java", "6").should == @response_file_destination
+ end
+
+ it "should return false if the response file has not been updated" do
+ @response_file_resource.updated_by_last_action(false)
+ @response_file_resource.should_not be_updated_by_last_action
+ # don't let the response_file_resource set updated to true
+ @response_file_resource.should_receive(:run_action).with(:create)
+ @provider.get_preseed_file("java", "6").should be(false)
+ end
+
+ end
+
+ end
+end
diff --git a/spec/unit/provider/remote_directory_spec.rb b/spec/unit/provider/remote_directory_spec.rb
new file mode 100644
index 0000000000..19a17c269f
--- /dev/null
+++ b/spec/unit/provider/remote_directory_spec.rb
@@ -0,0 +1,204 @@
+#
+# Author:: Daniel DeLeo (<dan@kallistec.com>)
+# Copyright:: Copyright (c) 2010 Daniel DeLeo
+# 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 'digest/md5'
+require 'tmpdir'
+require 'chef/mixin/file_class'
+
+class Chef::CFCCheck
+ include Chef::Mixin::FileClass
+end
+
+describe Chef::Provider::RemoteDirectory do
+ before do
+ Chef::FileAccessControl.any_instance.stub(:set_all)
+ #Terrible, but we need to implement a pseduo-filesystem for testing
+ #to not have this line. Only affects updating state fields.
+ Chef::Provider::CookbookFile.any_instance.stub(:update_new_file_state)
+
+ @resource = Chef::Resource::RemoteDirectory.new("/tmp/tafty")
+ # in CHEF_SPEC_DATA/cookbooks/openldap/files/default/remotedir
+ @resource.source "remotedir"
+ @resource.cookbook('openldap')
+
+ @cookbook_repo = ::File.expand_path(::File.join(CHEF_SPEC_DATA, "cookbooks"))
+ Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, @cookbook_repo) }
+
+ @node = Chef::Node.new
+ cl = Chef::CookbookLoader.new(@cookbook_repo)
+ cl.load_cookbooks
+ @cookbook_collection = Chef::CookbookCollection.new(cl)
+
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+
+ @provider = Chef::Provider::RemoteDirectory.new(@resource, @run_context)
+ @provider.current_resource = @resource.clone
+ end
+
+ describe "when access control is configured on the resource" do
+ before do
+ @resource.mode "0750"
+ @resource.group "wheel"
+ @resource.owner "root"
+
+ @resource.files_mode "0640"
+ @resource.files_group "staff"
+ @resource.files_owner "toor"
+ @resource.files_backup 23
+
+ @resource.source "remotedir_root"
+ end
+
+ it "configures access control on intermediate directorys" do
+ directory_resource = @provider.send(:resource_for_directory, "/tmp/intermediate_dir")
+ directory_resource.path.should == "/tmp/intermediate_dir"
+ directory_resource.mode.should == "0750"
+ directory_resource.group.should == "wheel"
+ directory_resource.owner.should == "root"
+ directory_resource.recursive.should be_true
+ end
+
+ it "configures access control on files in the directory" do
+ @resource.cookbook "berlin_style_tasty_cupcakes"
+ cookbook_file = @provider.send(:cookbook_file_resource,
+ "/target/destination/path.txt",
+ "relative/source/path.txt")
+ cookbook_file.cookbook_name.should == "berlin_style_tasty_cupcakes"
+ cookbook_file.source.should == "remotedir_root/relative/source/path.txt"
+ cookbook_file.mode.should == "0640"
+ cookbook_file.group.should == "staff"
+ cookbook_file.owner.should == "toor"
+ cookbook_file.backup.should == 23
+ end
+ end
+
+ describe "when creating the remote directory" do
+ before do
+ @node.automatic_attrs[:platform] = :just_testing
+ @node.automatic_attrs[:platform_version] = :just_testing
+
+ @destination_dir = Dir.mktmpdir << "/remote_directory_test"
+ @resource.path(@destination_dir)
+ end
+
+ after {FileUtils.rm_rf(@destination_dir)}
+
+ it "transfers the directory with all contents" do
+ @provider.run_action(:create)
+ ::File.exist?(@destination_dir + '/remote_dir_file1.txt').should be_true
+ ::File.exist?(@destination_dir + '/remote_dir_file2.txt').should be_true
+ ::File.exist?(@destination_dir + '/remotesubdir/remote_subdir_file1.txt').should be_true
+ ::File.exist?(@destination_dir + '/remotesubdir/remote_subdir_file2.txt').should be_true
+ ::File.exist?(@destination_dir + '/remotesubdir/.a_dotfile').should be_true
+ ::File.exist?(@destination_dir + '/.a_dotdir/.a_dotfile_in_a_dotdir').should be_true
+ end
+
+ describe "only if it is missing" do
+ it "should not overwrite existing files" do
+ @resource.overwrite(true)
+ @provider.run_action(:create)
+
+ File.open(@destination_dir + '/remote_dir_file1.txt', 'a') {|f| f.puts "blah blah blah" }
+ File.open(@destination_dir + '/remotesubdir/remote_subdir_file1.txt', 'a') {|f| f.puts "blah blah blah" }
+ file1md5 = Digest::MD5.hexdigest(File.read(@destination_dir + '/remote_dir_file1.txt'))
+ subdirfile1md5 = Digest::MD5.hexdigest(File.read(@destination_dir + '/remotesubdir/remote_subdir_file1.txt'))
+
+ @provider.run_action(:create_if_missing)
+
+ file1md5.eql?(Digest::MD5.hexdigest(File.read(@destination_dir + '/remote_dir_file1.txt'))).should be_true
+ subdirfile1md5.eql?(Digest::MD5.hexdigest(File.read(@destination_dir + '/remotesubdir/remote_subdir_file1.txt'))).should be_true
+ end
+ end
+
+ describe "with purging enabled" do
+ before {@resource.purge(true)}
+
+ it "removes existing files if purge is true" do
+ @provider.run_action(:create)
+ FileUtils.touch(@destination_dir + '/marked_for_death.txt')
+ FileUtils.touch(@destination_dir + '/remotesubdir/marked_for_death_again.txt')
+ @provider.run_action(:create)
+
+ ::File.exist?(@destination_dir + '/remote_dir_file1.txt').should be_true
+ ::File.exist?(@destination_dir + '/remote_dir_file2.txt').should be_true
+ ::File.exist?(@destination_dir + '/remotesubdir/remote_subdir_file1.txt').should be_true
+ ::File.exist?(@destination_dir + '/remotesubdir/remote_subdir_file2.txt').should be_true
+
+ ::File.exist?(@destination_dir + '/marked_for_death.txt').should be_false
+ ::File.exist?(@destination_dir + '/remotesubdir/marked_for_death_again.txt').should be_false
+ end
+
+ it "removes files in subdirectories before files above" do
+ @provider.run_action(:create)
+ FileUtils.mkdir_p(@destination_dir + '/a/multiply/nested/directory/')
+ FileUtils.touch(@destination_dir + '/a/foo.txt')
+ FileUtils.touch(@destination_dir + '/a/multiply/bar.txt')
+ FileUtils.touch(@destination_dir + '/a/multiply/nested/baz.txt')
+ FileUtils.touch(@destination_dir + '/a/multiply/nested/directory/qux.txt')
+ @provider.run_action(:create)
+ ::File.exist?(@destination_dir + '/a/foo.txt').should be_false
+ ::File.exist?(@destination_dir + '/a/multiply/bar.txt').should be_false
+ ::File.exist?(@destination_dir + '/a/multiply/nested/baz.txt').should be_false
+ ::File.exist?(@destination_dir + '/a/multiply/nested/directory/qux.txt').should be_false
+ end
+
+ it "removes directory symlinks properly" do
+ symlinked_dir_path = @destination_dir + '/symlinked_dir'
+ @provider.action = :create
+ @provider.run_action
+
+ @fclass = Chef::CFCCheck.new
+
+ Dir.mktmpdir do |tmp_dir|
+ begin
+ @fclass.file_class.symlink(tmp_dir.dup, symlinked_dir_path)
+ ::File.exist?(symlinked_dir_path).should be_true
+
+ @provider.run_action
+
+ ::File.exist?(symlinked_dir_path).should be_false
+ ::File.exist?(tmp_dir).should be_true
+ rescue Chef::Exceptions::Win32APIError => e
+ pending "This must be run as an Administrator to create symlinks"
+ end
+ end
+ end
+ end
+
+ describe "with overwrite disabled" do
+ before {@resource.purge(false)}
+ before {@resource.overwrite(false)}
+
+ it "leaves modifications alone" do
+ @provider.run_action(:create)
+ ::File.open(@destination_dir + '/remote_dir_file1.txt', 'a') {|f| f.puts "blah blah blah" }
+ ::File.open(@destination_dir + '/remotesubdir/remote_subdir_file1.txt', 'a') {|f| f.puts "blah blah blah" }
+ file1md5 = Digest::MD5.hexdigest(::File.read(@destination_dir + '/remote_dir_file1.txt'))
+ subdirfile1md5 = Digest::MD5.hexdigest(::File.read(@destination_dir + '/remotesubdir/remote_subdir_file1.txt'))
+ @provider.stub!(:update_new_file_state)
+ @provider.run_action(:create)
+ file1md5.eql?(Digest::MD5.hexdigest(::File.read(@destination_dir + '/remote_dir_file1.txt'))).should be_true
+ subdirfile1md5.eql?(Digest::MD5.hexdigest(::File.read(@destination_dir + '/remotesubdir/remote_subdir_file1.txt'))).should be_true
+ end
+ end
+
+ end
+end
+
diff --git a/spec/unit/provider/remote_file_spec.rb b/spec/unit/provider/remote_file_spec.rb
new file mode 100644
index 0000000000..78d7e77121
--- /dev/null
+++ b/spec/unit/provider/remote_file_spec.rb
@@ -0,0 +1,324 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::RemoteFile, "action_create" do
+ before(:each) do
+ @resource = Chef::Resource::RemoteFile.new("seattle")
+ @resource.path(File.expand_path(File.join(CHEF_SPEC_DATA, "seattle.txt")))
+ @resource.source("http://foo")
+ @node = Chef::Node.new
+ @node.name "latte"
+
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @provider = Chef::Provider::RemoteFile.new(@resource, @run_context)
+ #To prevent the current_resource.checksum from being overridden.
+ @provider.stub!(:load_current_resource)
+ end
+
+ describe "when checking if the file is at the target version" do
+ it "considers the current file to be at the target version if it exists and matches the user-provided checksum" do
+ @provider.current_resource = @resource.dup
+ @resource.checksum("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa")
+ @provider.current_resource.checksum("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa")
+ @provider.current_resource_matches_target_checksum?.should be_true
+ end
+ end
+
+ describe "when fetching the file from the remote" do
+ before(:each) do
+ @tempfile = Tempfile.new("chef-rspec-remote_file_spec-line#{__LINE__}--")
+
+ @rest = mock(Chef::REST, { })
+ Chef::REST.stub!(:new).and_return(@rest)
+ @rest.stub!(:streaming_request).and_return(@tempfile)
+ @rest.stub!(:create_url) { |url| url }
+ @resource.cookbook_name = "monkey"
+
+ @provider.stub!(:checksum).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa")
+ @provider.current_resource = @resource.clone
+ @provider.current_resource.checksum("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa")
+ File.stub!(:exists?).and_return(true)
+ FileUtils.stub!(:cp).and_return(true)
+ Chef::Platform.stub!(:find_platform_and_version).and_return([ :mac_os_x, "10.5.1" ])
+ end
+
+ after do
+ @tempfile.close!
+ end
+
+ before do
+ @resource.source("http://opscode.com/seattle.txt")
+ end
+
+ describe "and the target location's enclosing directory does not exist" do
+ before do
+ @resource.path("/tmp/this/path/does/not/exist/file.txt")
+ end
+
+ it "raises a specific error describing the problem" do
+ lambda {@provider.run_action(:create)}.should raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+ end
+ end
+
+ shared_examples_for "source specified with multiple URIs" do
+ it "should try to download the next URI when the first one fails" do
+ @rest.should_receive(:streaming_request).with("http://foo", {}).once.and_raise(SocketError)
+ @rest.should_receive(:streaming_request).with("http://bar", {}).once.and_return(@tempfile)
+ @provider.run_action(:create)
+ end
+
+ it "should raise an exception when all the URIs fail" do
+ @rest.should_receive(:streaming_request).with("http://foo", {}).once.and_raise(SocketError)
+ @rest.should_receive(:streaming_request).with("http://bar", {}).once.and_raise(SocketError)
+ lambda { @provider.run_action(:create) }.should raise_error(SocketError)
+ end
+
+ it "should download from only one URI when the first one works" do
+ @rest.should_receive(:streaming_request).once.and_return(@tempfile)
+ @provider.run_action(:create)
+ end
+
+ end
+
+ describe "and the source specifies multiple URIs using multiple arguments" do
+ it_should_behave_like "source specified with multiple URIs"
+
+ before(:each) do
+ @resource.source("http://foo", "http://bar")
+ end
+ end
+
+ describe "and the source specifies multiple URIs using an array" do
+ it_should_behave_like "source specified with multiple URIs"
+
+ before(:each) do
+ @resource.source([ "http://foo", "http://bar" ])
+ end
+ end
+
+ describe "and the resource specifies a checksum" do
+
+ describe "and the existing file matches the checksum exactly" do
+ before do
+ @resource.checksum("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa")
+ end
+
+ it "does not download the file" do
+ @rest.should_not_receive(:fetch).with("http://opscode.com/seattle.txt").and_return(@tempfile)
+ @provider.run_action(:create)
+ end
+
+ it "does not update the resource" do
+ @provider.run_action(:create)
+ @provider.new_resource.should_not be_updated
+ end
+
+ end
+
+ describe "and the existing file matches the given partial checksum" do
+ before do
+ @resource.checksum("0fd012fd")
+ end
+
+ it "should not download the file if the checksum is a partial match from the beginning" do
+ @rest.should_not_receive(:fetch).with("http://opscode.com/seattle.txt").and_return(@tempfile)
+ @provider.run_action(:create)
+ end
+
+ it "does not update the resource" do
+ @provider.run_action(:create)
+ @provider.new_resource.should_not be_updated
+ end
+
+ end
+
+ describe "and the existing file doesn't match the given checksum" do
+ it "downloads the file" do
+ @resource.checksum("this hash doesn't match")
+ @rest.should_receive(:streaming_request).with("http://opscode.com/seattle.txt", {}).and_return(@tempfile)
+ @provider.stub!(:update_new_file_state)
+ @provider.run_action(:create)
+ end
+
+ it "does not consider the checksum a match if the matching string is offset" do
+ # i.e., the existing file is "0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa"
+ @resource.checksum("fd012fd")
+ @rest.should_receive(:streaming_request).with("http://opscode.com/seattle.txt", {}).and_return(@tempfile)
+ @provider.stub!(:update_new_file_state)
+ @provider.run_action(:create)
+ end
+ end
+
+ end
+
+ describe "and the resource doesn't specify a checksum" do
+ it "should download the file from the remote URL" do
+ @resource.checksum(nil)
+ @rest.should_receive(:streaming_request).with("http://opscode.com/seattle.txt", {}).and_return(@tempfile)
+ @provider.run_action(:create)
+ end
+ end
+
+ # CHEF-3140
+ # Some servers return tarballs as content type tar and encoding gzip, which
+ # is totally wrong. When this happens and gzip isn't disabled, Chef::REST
+ # will decompress the file for you, which is not at all what you expected
+ # to happen (you end up with an uncomressed tar archive instead of the
+ # gzipped tar archive you expected). To work around this behavior, we
+ # detect when users are fetching gzipped files and turn off gzip in
+ # Chef::REST.
+
+ context "and the target file is a tarball" do
+ before do
+ @resource.path(File.expand_path(File.join(CHEF_SPEC_DATA, "seattle.tar.gz")))
+ Chef::REST.should_receive(:new).with("http://opscode.com/seattle.txt", nil, nil, :disable_gzip => true).and_return(@rest)
+ end
+
+ it "disables gzip in the http client" do
+ @provider.action_create
+ end
+
+ end
+
+ context "and the source appears to be a tarball" do
+ before do
+ @resource.source("http://example.com/tarball.tgz")
+ Chef::REST.should_receive(:new).with("http://example.com/tarball.tgz", nil, nil, :disable_gzip => true).and_return(@rest)
+ end
+
+ it "disables gzip in the http client" do
+ @provider.action_create
+ end
+ end
+
+ it "should raise an exception if it's any other kind of retriable response than 304" do
+ r = Net::HTTPMovedPermanently.new("one", "two", "three")
+ e = Net::HTTPRetriableError.new("301", r)
+ @rest.stub!(:streaming_request).and_raise(e)
+ lambda { @provider.run_action(:create) }.should raise_error(Net::HTTPRetriableError)
+ end
+
+ it "should raise an exception if anything else happens" do
+ r = Net::HTTPBadRequest.new("one", "two", "three")
+ e = Net::HTTPServerException.new("fake exception", r)
+ @rest.stub!(:streaming_request).and_raise(e)
+ lambda { @provider.run_action(:create) }.should raise_error(Net::HTTPServerException)
+ end
+
+ it "should checksum the raw file" do
+ @provider.should_receive(:checksum).with(@tempfile.path).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa")
+ @provider.run_action(:create)
+ end
+
+ describe "when the target file does not exist" do
+ before do
+ ::File.stub!(:exists?).with(@resource.path).and_return(false)
+ @provider.stub!(:get_from_server).and_return(@tempfile)
+ end
+
+ it "should copy the raw file to the new resource" do
+ FileUtils.should_receive(:cp).with(@tempfile.path, @resource.path).and_return(true)
+ @provider.stub!(:update_new_file_state)
+ @provider.run_action(:create)
+ end
+
+ it "should set the new resource to updated" do
+ @provider.stub!(:update_new_file_state)
+ @provider.run_action(:create)
+ @resource.should be_updated
+ end
+
+ describe "and create_if_missing is invoked" do
+ it "should invoke action_create" do
+ @provider.should_receive(:action_create)
+ @provider.run_action(:create_if_missing)
+ end
+ end
+ end
+
+ describe "when the target file already exists" do
+ before do
+ ::File.stub!(:exists?).with(@resource.path).and_return(true)
+ @provider.stub!(:diff_current).and_return([
+ "--- /tmp/foo 2012-08-30 21:28:17.632782551 +0000",
+ "+++ /tmp/bar 2012-08-30 21:28:20.816975437 +0000",
+ "@@ -1 +1 @@",
+ "-foo bar",
+ "+bar foo"
+ ])
+ @provider.stub!(:get_from_server).and_return(@tempfile)
+ end
+
+ describe "and create_if_missing is invoked" do
+ it "should take no action" do
+ @provider.should_not_receive(:action_create)
+ @provider.run_action(:create_if_missing)
+ end
+ end
+
+ describe "and the file downloaded from the remote is identical to the current" do
+ it "shouldn't backup the original file" do
+ @provider.should_not_receive(:backup).with(@resource.path)
+ @provider.run_action(:create)
+ end
+
+ it "doesn't mark the resource as updated" do
+ @provider.run_action(:create)
+ @provider.new_resource.should_not be_updated
+ end
+ end
+
+ describe "and the checksum doesn't match" do
+ before do
+ sha2_256 = "0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa-NO_MATCHY"
+ @provider.current_resource.checksum(sha2_256)
+ end
+
+ it "should backup the original file" do
+ @provider.stub!(:update_new_file_state)
+ @provider.should_receive(:backup).with(@resource.path).and_return(true)
+ @provider.run_action(:create)
+ end
+
+ it "should copy the raw file to the new resource" do
+ @provider.stub!(:update_new_file_state)
+ FileUtils.should_receive(:cp).with(@tempfile.path, @resource.path).and_return(true)
+ @provider.run_action(:create)
+ end
+
+ it "should set the new resource to updated" do
+ @provider.stub!(:update_new_file_state)
+ @provider.run_action(:create)
+ @resource.should be_updated
+ end
+ end
+
+ it "should set permissions" do
+ @provider.should_receive(:set_all_access_controls).and_return(true)
+ @provider.run_action(:create)
+ end
+
+
+ end
+
+ end
+end
diff --git a/spec/unit/provider/route_spec.rb b/spec/unit/provider/route_spec.rb
new file mode 100644
index 0000000000..3c5db0b7a1
--- /dev/null
+++ b/spec/unit/provider/route_spec.rb
@@ -0,0 +1,230 @@
+#
+# Author:: Bryan McLellan (btm@loftninjas.org)
+# Copyright:: Copyright (c) 2009 Bryan McLellan
+# 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::Route do
+ before do
+ @node = Chef::Node.new
+ @cookbook_collection = Chef::CookbookCollection.new([])
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+
+ @new_resource = Chef::Resource::Route.new('10.0.0.10')
+ @new_resource.gateway "10.0.0.9"
+ @current_resource = Chef::Resource::Route.new('10.0.0.10')
+ @current_resource.gateway "10.0.0.9"
+
+ @provider = Chef::Provider::Route.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ end
+
+ describe Chef::Provider::Route, "hex2ip" do
+ it "should return nil if ip address is invalid" do
+ @provider.hex2ip('foo').should be_nil # does not even look like an ip
+ @provider.hex2ip('ABCDEFGH').should be_nil # 8 chars, but invalid
+ end
+
+ it "should return quad-dotted notation for a valid IP" do
+ @provider.hex2ip('01234567').should == '103.69.35.1'
+ @provider.hex2ip('0064a8c0').should == '192.168.100.0'
+ @provider.hex2ip('00FFFFFF').should == '255.255.255.0'
+ end
+ end
+
+
+ describe Chef::Provider::Route, "load_current_resource" do
+ context "on linux" do
+ before do
+ @node.automatic_attrs[:os] = 'linux'
+ routing_table = "Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT\n" +
+ "eth0 0064A8C0 0984A8C0 0003 0 0 0 00FFFFFF 0 0 0\n"
+ route_file = StringIO.new(routing_table)
+ File.stub!(:open).with("/proc/net/route", "r").and_return(route_file)
+ end
+
+ it "should set is_running to false when a route is not detected" do
+ resource = Chef::Resource::Route.new('10.10.10.0/24')
+ resource.stub!(:gateway).and_return("10.0.0.1")
+ resource.stub!(:device).and_return("eth0")
+ provider = Chef::Provider::Route.new(resource, @run_context)
+
+ provider.load_current_resource
+ provider.is_running.should be_false
+ end
+
+ it "should detect existing routes and set is_running attribute correctly" do
+ resource = Chef::Resource::Route.new('192.168.100.0/24')
+ resource.stub!(:gateway).and_return("192.168.132.9")
+ resource.stub!(:device).and_return("eth0")
+ provider = Chef::Provider::Route.new(resource, @run_context)
+
+ provider.load_current_resource
+ provider.is_running.should be_true
+ end
+
+ it "should use gateway value when matching routes" do
+ resource = Chef::Resource::Route.new('192.168.100.0/24')
+ resource.stub!(:gateway).and_return("10.10.10.10")
+ resource.stub!(:device).and_return("eth0")
+ provider = Chef::Provider::Route.new(resource, @run_context)
+
+ provider.load_current_resource
+ provider.is_running.should be_false
+ end
+ end
+ end
+
+ describe Chef::Provider::Route, "action_add" do
+ it "should add the route if it does not exist" do
+ @provider.stub!(:run_command).and_return(true)
+ @current_resource.stub!(:gateway).and_return(nil)
+ @provider.should_receive(:generate_command).once.with(:add)
+ @provider.should_receive(:generate_config)
+ @provider.run_action(:add)
+ @new_resource.should be_updated
+ end
+
+ it "should not add the route if it exists" do
+ @provider.stub!(:run_command).and_return(true)
+ @provider.stub!(:is_running).and_return(true)
+ @provider.should_not_receive(:generate_command).with(:add)
+ @provider.should_receive(:generate_config)
+ @provider.run_action(:add)
+ @new_resource.should_not be_updated
+ end
+ end
+
+ describe Chef::Provider::Route, "action_delete" do
+ it "should delete the route if it exists" do
+ @provider.stub!(:run_command).and_return(true)
+ @provider.should_receive(:generate_command).once.with(:delete)
+ @provider.stub!(:is_running).and_return(true)
+ @provider.run_action(:delete)
+ @new_resource.should be_updated
+ end
+
+ it "should not delete the route if it does not exist" do
+ @current_resource.stub!(:gateway).and_return(nil)
+ @provider.stub!(:run_command).and_return(true)
+ @provider.should_not_receive(:generate_command).with(:add)
+ @provider.run_action(:delete)
+ @new_resource.should_not be_updated
+ end
+ end
+
+ describe Chef::Provider::Route, "generate_command for action_add" do
+ it "should include a netmask when a one is specified" do
+ @new_resource.stub!(:netmask).and_return('255.255.0.0')
+ @provider.generate_command(:add).should match(/\/\d{1,2}\s/)
+ end
+
+ it "should not include a netmask when a one is specified" do
+ @new_resource.stub!(:netmask).and_return(nil)
+ @provider.generate_command(:add).should_not match(/\/\d{1,2}\s/)
+ end
+
+ it "should include ' via $gateway ' when a gateway is specified" do
+ @provider.generate_command(:add).should match(/\svia\s#{@new_resource.gateway}\s/)
+ end
+
+ it "should not include ' via $gateway ' when a gateway is not specified" do
+ @new_resource.stub!(:gateway).and_return(nil)
+ @provider.generate_command(:add).should_not match(/\svia\s#{@new_resource.gateway}\s/)
+ end
+ end
+
+ describe Chef::Provider::Route, "generate_command for action_delete" do
+ it "should include a netmask when a one is specified" do
+ @new_resource.stub!(:netmask).and_return('255.255.0.0')
+ @provider.generate_command(:delete).should match(/\/\d{1,2}\s/)
+ end
+
+ it "should not include a netmask when a one is specified" do
+ @new_resource.stub!(:netmask).and_return(nil)
+ @provider.generate_command(:delete).should_not match(/\/\d{1,2}\s/)
+ end
+
+ it "should include ' via $gateway ' when a gateway is specified" do
+ @provider.generate_command(:delete).should match(/\svia\s#{@new_resource.gateway}\s/)
+ end
+
+ it "should not include ' via $gateway ' when a gateway is not specified" do
+ @new_resource.stub!(:gateway).and_return(nil)
+ @provider.generate_command(:delete).should_not match(/\svia\s#{@new_resource.gateway}\s/)
+ end
+ end
+
+ describe Chef::Provider::Route, "config_file_contents for action_add" do
+ it "should include a netmask when a one is specified" do
+ @new_resource.stub!(:netmask).and_return('255.255.0.0')
+ @provider.config_file_contents(:add, { :target => @new_resource.target, :netmask => @new_resource.netmask}).should match(/\/\d{1,2}.*\n$/)
+ end
+
+ it "should not include a netmask when a one is specified" do
+ @provider.config_file_contents(:add, { :target => @new_resource.target}).should_not match(/\/\d{1,2}.*\n$/)
+ end
+
+ it "should include ' via $gateway ' when a gateway is specified" do
+ @provider.config_file_contents(:add, { :target => @new_resource.target, :gateway => @new_resource.gateway}).should match(/\svia\s#{@new_resource.gateway}\n/)
+ end
+
+ it "should not include ' via $gateway ' when a gateway is not specified" do
+ @provider.generate_command(:add).should_not match(/\svia\s#{@new_resource.gateway}\n/)
+ end
+ end
+
+ describe Chef::Provider::Route, "config_file_contents for action_delete" do
+ it "should return an empty string" do
+ @provider.config_file_contents(:delete).should match(/^$/)
+ end
+ end
+
+ describe Chef::Provider::Route, "generate_config method" do
+ %w[ centos redhat fedora ].each do |platform|
+ it "should write a route file on #{platform} platform" do
+ @node.automatic_attrs[:platform] = platform
+
+ route_file = StringIO.new
+ File.should_receive(:new).with("/etc/sysconfig/network-scripts/route-eth0", "w").and_return(route_file)
+ #Chef::Log.should_receive(:debug).with("route[10.0.0.10] writing route.eth0\n10.0.0.10 via 10.0.0.9\n")
+ @run_context.resource_collection << @new_resource
+
+ @provider.generate_config
+ @provider.converge
+ end
+ end
+
+ it "should put all routes for a device in a route config file" do
+ @node.automatic_attrs[:platform] = 'centos'
+
+ route_file = StringIO.new
+ File.should_receive(:new).and_return(route_file)
+ @run_context.resource_collection << Chef::Resource::Route.new('192.168.1.0/24 via 192.168.0.1')
+ @run_context.resource_collection << Chef::Resource::Route.new('192.168.2.0/24 via 192.168.0.1')
+ @run_context.resource_collection << Chef::Resource::Route.new('192.168.3.0/24 via 192.168.0.1')
+
+ @provider.generate_config
+ @provider.converge
+ route_file.string.split("\n").should have(3).items
+ route_file.string.should match(/^192.168.1.0\/24 via 192.168.0.1$/)
+ route_file.string.should match(/^192.168.2.0\/24 via 192.168.0.1$/)
+ route_file.string.should match(/^192.168.3.0\/24 via 192.168.0.1$/)
+ end
+ end
+end
diff --git a/spec/unit/provider/ruby_block_spec.rb b/spec/unit/provider/ruby_block_spec.rb
new file mode 100644
index 0000000000..7fc58c9c70
--- /dev/null
+++ b/spec/unit/provider/ruby_block_spec.rb
@@ -0,0 +1,46 @@
+#
+# Author:: AJ Christensen (<aj@opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode
+# 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::RubyBlock, "initialize" do
+ before(:each) do
+ $evil_global_evil_laugh = :wahwah
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::RubyBlock.new("bloc party")
+ @new_resource.block { $evil_global_evil_laugh = :mwahahaha}
+ @provider = Chef::Provider::RubyBlock.new(@new_resource, @run_context)
+ end
+
+ it "should call the block and flag the resource as updated" do
+ @provider.run_action(:run)
+ $evil_global_evil_laugh.should == :mwahahaha
+ @new_resource.should be_updated
+ end
+
+ it "accepts `create' as an alias for `run'" do
+ # SEE ALSO: CHEF-3500
+ # "create" used to be the default action, it was renamed.
+ @provider.run_action(:create)
+ $evil_global_evil_laugh.should == :mwahahaha
+ @new_resource.should be_updated
+ end
+end
+
diff --git a/spec/unit/provider/script_spec.rb b/spec/unit/provider/script_spec.rb
new file mode 100644
index 0000000000..5111a94578
--- /dev/null
+++ b/spec/unit/provider/script_spec.rb
@@ -0,0 +1,96 @@
+#
+# Author:: Adam Jacob (adam@opscode.com)
+# Copyright:: Copyright (c) 2009 Opscode
+# 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::Script, "action_run" do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Script.new('run some perl code')
+ @new_resource.code "$| = 1; print 'i like beans'"
+ @new_resource.interpreter 'perl'
+
+ @provider = Chef::Provider::Script.new(@new_resource, @run_context)
+
+ @script_file = StringIO.new
+ @script_file.stub!(:path).and_return('/tmp/the_script_file')
+
+ @provider.stub!(:shell_out!).and_return(true)
+ end
+
+ it "creates a temporary file to store the script" do
+ @provider.script_file.should be_an_instance_of(Tempfile)
+ end
+
+ it "unlinks the tempfile when finished" do
+ tempfile_path = @provider.script_file.path
+ @provider.unlink_script_file
+ File.exist?(tempfile_path).should be_false
+ end
+
+ it "sets the owner and group for the script file" do
+ @new_resource.user 'toor'
+ @new_resource.group 'wheel'
+ @provider.stub!(:script_file).and_return(@script_file)
+ FileUtils.should_receive(:chown).with('toor', 'wheel', "/tmp/the_script_file")
+ @provider.set_owner_and_group
+ end
+
+ context "with the script file set to the correct owner and group" do
+ before do
+ @provider.stub!(:set_owner_and_group)
+ @provider.stub!(:script_file).and_return(@script_file)
+ end
+ describe "when writing the script to the file" do
+ it "should put the contents of the script in the temp file" do
+ @provider.action_run
+ @script_file.rewind
+ @script_file.string.should == "$| = 1; print 'i like beans'\n"
+ end
+
+ it "closes before executing the script and unlinks it when finished" do
+ @provider.action_run
+ @script_file.should be_closed
+ end
+
+ end
+
+ describe "when running the script" do
+ it 'should set the command to "interpreter" "tempfile"' do
+ @provider.action_run
+ @new_resource.command.should == '"perl" "/tmp/the_script_file"'
+ end
+
+ describe "with flags set on the resource" do
+ before do
+ @new_resource.flags '-f'
+ end
+
+ it "should set the command to 'interpreter flags tempfile'" do
+ @provider.action_run
+ @new_resource.command.should == '"perl" -f "/tmp/the_script_file"'
+ end
+
+ end
+
+ end
+ end
+
+end
diff --git a/spec/unit/provider/service/arch_service_spec.rb b/spec/unit/provider/service/arch_service_spec.rb
new file mode 100644
index 0000000000..a7afa28da1
--- /dev/null
+++ b/spec/unit/provider/service/arch_service_spec.rb
@@ -0,0 +1,330 @@
+#
+# Author:: Jan Zimmek (<jan.zimmek@web.de>)
+# Author:: AJ Christensen (<aj@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 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 'ostruct'
+
+
+# most of this code has been ripped from init_service_spec.rb
+# and is only slightly modified to match "arch" needs.
+
+describe Chef::Provider::Service::Arch, "load_current_resource" do
+ before(:each) do
+ @node = Chef::Node.new
+ @node.automatic_attrs[:command] = {:ps => "ps -ef"}
+
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Service.new("chef")
+ @new_resource.pattern("chef")
+ @new_resource.supports({:status => false})
+
+
+ @provider = Chef::Provider::Service::Arch.new(@new_resource, @run_context)
+
+ ::File.stub!(:exists?).with("/etc/rc.conf").and_return(true)
+ ::File.stub!(:read).with("/etc/rc.conf").and_return("DAEMONS=(network apache sshd)")
+ end
+
+ describe "when first created" do
+ it "should set the current resources service name to the new resources service name" do
+ @provider.stub(:shell_out).and_return(OpenStruct.new(:exitstatus => 0, :stdout => ""))
+ @provider.load_current_resource
+ @provider.current_resource.service_name.should == 'chef'
+ end
+ end
+
+
+ describe "when the service supports status" do
+ before do
+ @new_resource.supports({:status => true})
+ end
+
+ it "should run '/etc/rc.d/service_name status'" do
+ @provider.should_receive(:shell_out).with("/etc/rc.d/chef status").and_return(OpenStruct.new(:exitstatus => 0))
+ @provider.load_current_resource
+ end
+
+ it "should set running to true if the the status command returns 0" do
+ @provider.stub!(:shell_out).with("/etc/rc.d/chef status").and_return(OpenStruct.new(:exitstatus => 0))
+ @provider.load_current_resource
+ @provider.current_resource.running.should be_true
+ end
+
+ it "should set running to false if the status command returns anything except 0" do
+ @provider.stub!(:shell_out).with("/etc/rc.d/chef status").and_return(OpenStruct.new(:exitstatus => 1))
+ @provider.load_current_resource
+ @provider.current_resource.running.should be_false
+ end
+
+ it "should set running to false if the status command raises" do
+ @provider.stub!(:shell_out).with("/etc/rc.d/chef status").and_raise(Mixlib::ShellOut::ShellCommandFailed)
+ @provider.load_current_resource
+ @provider.current_resource.running.should be_false
+ end
+
+ end
+
+
+ describe "when a status command has been specified" do
+ before do
+ @new_resource.status_command("/etc/rc.d/chefhasmonkeypants status")
+ end
+
+ it "should run the services status command if one has been specified" do
+ @provider.should_receive(:shell_out).with("/etc/rc.d/chefhasmonkeypants status").and_return(OpenStruct.new(:exitstatus => 0))
+ @provider.load_current_resource
+ end
+
+ end
+
+ it "should raise error if the node has a nil ps attribute and no other means to get status" do
+ @node.automatic_attrs[:command] = {:ps => nil}
+ @provider.define_resource_requirements
+ @provider.action = :start
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+
+ it "should raise error if the node has an empty ps attribute and no other means to get status" do
+ @node.automatic_attrs[:command] = {:ps => ""}
+ @provider.define_resource_requirements
+ @provider.action = :start
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+
+
+ it "should fail if file /etc/rc.conf does not exist" do
+ ::File.stub!(:exists?).with("/etc/rc.conf").and_return(false)
+ lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Service)
+ end
+
+ it "should fail if file /etc/rc.conf does not contain DAEMONS array" do
+ ::File.stub!(:read).with("/etc/rc.conf").and_return("")
+ lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Service)
+ end
+
+ describe "when discovering service status with ps" do
+ before do
+ @stdout = StringIO.new(<<-DEFAULT_PS)
+aj 7842 5057 0 21:26 pts/2 00:00:06 vi init.rb
+aj 7903 5016 0 21:26 pts/5 00:00:00 /bin/bash
+aj 8119 6041 0 21:34 pts/3 00:00:03 vi init_service_spec.rb
+DEFAULT_PS
+ @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+ @provider.stub!(:shell_out!).and_return(@status)
+
+ @node.automatic_attrs[:command] = {:ps => "ps -ef"}
+ end
+
+ it "determines the service is running when it appears in ps" do
+ @stdout = StringIO.new(<<-RUNNING_PS)
+aj 7842 5057 0 21:26 pts/2 00:00:06 chef
+aj 7842 5057 0 21:26 pts/2 00:00:06 poos
+RUNNING_PS
+ @status.stub!(:stdout).and_return(@stdout)
+ @provider.load_current_resource
+ @provider.current_resource.running.should be_true
+ end
+
+ it "determines the service is not running when it does not appear in ps" do
+ @provider.stub!(:shell_out!).and_return(@status)
+ @provider.load_current_resource
+ @provider.current_resource.running.should be_false
+ end
+
+ it "should raise an exception if ps fails" do
+ @provider.stub!(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed)
+ @provider.load_current_resource
+ @provider.action = :start
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+ end
+
+ it "should return existing entries in DAEMONS array" do
+ ::File.stub!(:read).with("/etc/rc.conf").and_return("DAEMONS=(network !apache ssh)")
+ @provider.daemons.should == ['network', '!apache', 'ssh']
+ end
+
+ context "when the current service status is known" do
+ before do
+ @current_resource = Chef::Resource::Service.new("chef")
+ @provider.current_resource = @current_resource
+ end
+
+ describe Chef::Provider::Service::Arch, "enable_service" do
+ # before(:each) do
+ # @new_resource = mock("Chef::Resource::Service",
+ # :null_object => true,
+ # :name => "chef",
+ # :service_name => "chef",
+ # :running => false
+ # )
+ # @new_resource.stub!(:start_command).and_return(false)
+ #
+ # @provider = Chef::Provider::Service::Arch.new(@node, @new_resource)
+ # Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+ # end
+
+ it "should add chef to DAEMONS array" do
+ ::File.stub!(:read).with("/etc/rc.conf").and_return("DAEMONS=(network)")
+ @provider.should_receive(:update_daemons).with(['network', 'chef'])
+ @provider.enable_service()
+ end
+ end
+
+ describe Chef::Provider::Service::Arch, "disable_service" do
+ # before(:each) do
+ # @new_resource = mock("Chef::Resource::Service",
+ # :null_object => true,
+ # :name => "chef",
+ # :service_name => "chef",
+ # :running => false
+ # )
+ # @new_resource.stub!(:start_command).and_return(false)
+ #
+ # @provider = Chef::Provider::Service::Arch.new(@node, @new_resource)
+ # Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+ # end
+
+ it "should remove chef from DAEMONS array" do
+ ::File.stub!(:read).with("/etc/rc.conf").and_return("DAEMONS=(network chef)")
+ @provider.should_receive(:update_daemons).with(['network', '!chef'])
+ @provider.disable_service()
+ end
+ end
+
+
+ describe Chef::Provider::Service::Arch, "start_service" do
+ # before(:each) do
+ # @new_resource = mock("Chef::Resource::Service",
+ # :null_object => true,
+ # :name => "chef",
+ # :service_name => "chef",
+ # :running => false
+ # )
+ # @new_resource.stub!(:start_command).and_return(false)
+ #
+ # @provider = Chef::Provider::Service::Arch.new(@node, @new_resource)
+ # Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+ # end
+
+ it "should call the start command if one is specified" do
+ @new_resource.stub!(:start_command).and_return("/etc/rc.d/chef startyousillysally")
+ @provider.should_receive(:shell_out!).with("/etc/rc.d/chef startyousillysally")
+ @provider.start_service()
+ end
+
+ it "should call '/etc/rc.d/service_name start' if no start command is specified" do
+ @provider.should_receive(:shell_out!).with("/etc/rc.d/#{@new_resource.service_name} start")
+ @provider.start_service()
+ end
+ end
+
+ describe Chef::Provider::Service::Arch, "stop_service" do
+ # before(:each) do
+ # @new_resource = mock("Chef::Resource::Service",
+ # :null_object => true,
+ # :name => "chef",
+ # :service_name => "chef",
+ # :running => false
+ # )
+ # @new_resource.stub!(:stop_command).and_return(false)
+ #
+ # @provider = Chef::Provider::Service::Arch.new(@node, @new_resource)
+ # Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+ # end
+
+ it "should call the stop command if one is specified" do
+ @new_resource.stub!(:stop_command).and_return("/etc/rc.d/chef itoldyoutostop")
+ @provider.should_receive(:shell_out!).with("/etc/rc.d/chef itoldyoutostop")
+ @provider.stop_service()
+ end
+
+ it "should call '/etc/rc.d/service_name stop' if no stop command is specified" do
+ @provider.should_receive(:shell_out!).with("/etc/rc.d/#{@new_resource.service_name} stop")
+ @provider.stop_service()
+ end
+ end
+
+ describe Chef::Provider::Service::Arch, "restart_service" do
+ # before(:each) do
+ # @new_resource = mock("Chef::Resource::Service",
+ # :null_object => true,
+ # :name => "chef",
+ # :service_name => "chef",
+ # :running => false
+ # )
+ # @new_resource.stub!(:restart_command).and_return(false)
+ # @new_resource.stub!(:supports).and_return({:restart => false})
+ #
+ # @provider = Chef::Provider::Service::Arch.new(@node, @new_resource)
+ # Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+ # end
+
+ it "should call 'restart' on the service_name if the resource supports it" do
+ @new_resource.stub!(:supports).and_return({:restart => true})
+ @provider.should_receive(:shell_out!).with("/etc/rc.d/#{@new_resource.service_name} restart")
+ @provider.restart_service()
+ end
+
+ it "should call the restart_command if one has been specified" do
+ @new_resource.stub!(:restart_command).and_return("/etc/rc.d/chef restartinafire")
+ @provider.should_receive(:shell_out!).with("/etc/rc.d/#{@new_resource.service_name} restartinafire")
+ @provider.restart_service()
+ end
+
+ it "should just call stop, then start when the resource doesn't support restart and no restart_command is specified" do
+ @provider.should_receive(:stop_service)
+ @provider.should_receive(:sleep).with(1)
+ @provider.should_receive(:start_service)
+ @provider.restart_service()
+ end
+ end
+
+ describe Chef::Provider::Service::Arch, "reload_service" do
+ # before(:each) do
+ # @new_resource = mock("Chef::Resource::Service",
+ # :null_object => true,
+ # :name => "chef",
+ # :service_name => "chef",
+ # :running => false
+ # )
+ # @new_resource.stub!(:reload_command).and_return(false)
+ # @new_resource.stub!(:supports).and_return({:reload => false})
+ #
+ # @provider = Chef::Provider::Service::Arch.new(@node, @new_resource)
+ # Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+ # end
+
+ it "should call 'reload' on the service if it supports it" do
+ @new_resource.stub!(:supports).and_return({:reload => true})
+ @provider.should_receive(:shell_out!).with("/etc/rc.d/#{@new_resource.service_name} reload")
+ @provider.reload_service()
+ end
+
+ it "should should run the user specified reload command if one is specified and the service doesn't support reload" do
+ @new_resource.stub!(:reload_command).and_return("/etc/rc.d/chef lollerpants")
+ @provider.should_receive(:shell_out!).with("/etc/rc.d/#{@new_resource.service_name} lollerpants")
+ @provider.reload_service()
+ end
+ end
+ end
+end
diff --git a/spec/unit/provider/service/debian_service_spec.rb b/spec/unit/provider/service/debian_service_spec.rb
new file mode 100644
index 0000000000..bea9360561
--- /dev/null
+++ b/spec/unit/provider/service/debian_service_spec.rb
@@ -0,0 +1,254 @@
+#
+# Author:: AJ Christensen (<aj@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# 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::Service::Debian, "load_current_resource" do
+ before(:each) do
+ @node = Chef::Node.new
+ @node.automatic_attrs[:command] = {:ps => 'fuuuu'}
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Service.new("chef")
+
+ @current_resource = Chef::Resource::Service.new("chef")
+
+ @provider = Chef::Provider::Service::Debian.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+
+ @pid, @stdin, @stdout, @stderr = nil, nil, nil, nil
+
+ end
+
+ it "ensures /usr/sbin/update-rc.d is available" do
+ File.should_receive(:exists?).with("/usr/sbin/update-rc.d").and_return(false)
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements } .should raise_error(Chef::Exceptions::Service)
+ end
+
+ describe "when update-rc.d shows the init script linked to rc*.d/" do
+ before do
+ @provider.stub!(:assert_update_rcd_available)
+
+ result=<<-UPDATE_RC_D_SUCCESS
+Removing any system startup links for /etc/init.d/chef ...
+ /etc/rc0.d/K20chef
+ /etc/rc1.d/K20chef
+ /etc/rc2.d/S20chef
+ /etc/rc3.d/S20chef
+ /etc/rc4.d/S20chef
+ /etc/rc5.d/S20chef
+ /etc/rc6.d/K20chef
+ UPDATE_RC_D_SUCCESS
+ @stdout = StringIO.new(result)
+ @stderr = StringIO.new
+ @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+ @provider.stub!(:shell_out!).and_return(@status)
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ end
+
+ it "says the service is enabled" do
+ @provider.service_currently_enabled?(@provider.get_priority).should be_true
+ end
+
+ it "stores the 'enabled' state" do
+ Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+ @provider.load_current_resource.should equal(@current_resource)
+ @current_resource.enabled.should be_true
+ end
+ end
+
+ {"Debian/Lenny and older" => {
+ "linked" => {
+ "stdout" => " Removing any system startup links for /etc/init.d/chef ...
+ /etc/rc0.d/K20chef
+ /etc/rc1.d/K20chef
+ /etc/rc2.d/S20chef
+ /etc/rc3.d/S20chef
+ /etc/rc4.d/S20chef
+ /etc/rc5.d/S20chef
+ /etc/rc6.d/K20chef",
+ "stderr" => ""
+ },
+ "not linked" => {
+ "stdout" => " Removing any system startup links for /etc/init.d/chef ...",
+ "stderr" => ""
+ },
+ },
+ "Debian/Squeeze and earlier" => {
+ "linked" => {
+ "stdout" => "update-rc.d: using dependency based boot sequencing",
+ "stderr" => "insserv: remove service /etc/init.d/../rc0.d/K20chef-client
+insserv: remove service /etc/init.d/../rc1.d/K20chef-client
+insserv: remove service /etc/init.d/../rc2.d/S20chef-client
+insserv: remove service /etc/init.d/../rc3.d/S20chef-client
+insserv: remove service /etc/init.d/../rc4.d/S20chef-client
+insserv: remove service /etc/init.d/../rc5.d/S20chef-client
+insserv: remove service /etc/init.d/../rc6.d/K20chef-client
+insserv: dryrun, not creating .depend.boot, .depend.start, and .depend.stop"
+ },
+ "not linked" => {
+ "stdout" => "update-rc.d: using dependency based boot sequencing",
+ "stderr" => ""
+ }
+ }
+ }.each do |model, streams|
+ describe "when update-rc.d shows the init script linked to rc*.d/" do
+ before do
+ @provider.stub!(:assert_update_rcd_available)
+
+ @stdout = StringIO.new(streams["linked"]["stdout"])
+ @stderr = StringIO.new(streams["linked"]["stderr"])
+ @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+ @provider.stub!(:shell_out!).and_return(@status)
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ end
+
+ it "says the service is enabled" do
+ @provider.service_currently_enabled?(@provider.get_priority).should be_true
+ end
+
+ it "stores the 'enabled' state" do
+ Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+ @provider.load_current_resource.should equal(@current_resource)
+ @current_resource.enabled.should be_true
+ end
+
+ it "stores the start/stop priorities of the service" do
+ @provider.load_current_resource
+ expected_priorities = {"6"=>[:stop, "20"],
+ "0"=>[:stop, "20"],
+ "1"=>[:stop, "20"],
+ "2"=>[:start, "20"],
+ "3"=>[:start, "20"],
+ "4"=>[:start, "20"],
+ "5"=>[:start, "20"]}
+ @provider.current_resource.priority.should == expected_priorities
+ end
+ end
+
+ describe "when using squeeze/earlier and update-rc.d shows the init script isn't linked to rc*.d" do
+ before do
+ @provider.stub!(:assert_update_rcd_available)
+ @stdout = StringIO.new(streams["not linked"]["stdout"])
+ @stderr = StringIO.new(streams["not linked"]["stderr"])
+ @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+ @provider.stub!(:shell_out!).and_return(@status)
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ end
+
+ it "says the service is disabled" do
+ @provider.service_currently_enabled?(@provider.get_priority).should be_false
+ end
+
+ it "stores the 'disabled' state" do
+ Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+ @provider.load_current_resource.should equal(@current_resource)
+ @current_resource.enabled.should be_false
+ end
+ end
+ end
+
+ describe "when update-rc.d shows the init script isn't linked to rc*.d" do
+ before do
+ @provider.stub!(:assert_update_rcd_available)
+ @status = mock("Status", :exitstatus => 0)
+ @stdout = StringIO.new(" Removing any system startup links for /etc/init.d/chef ...")
+ @stderr = StringIO.new
+ @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+ @provider.stub!(:shell_out!).and_return(@status)
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ end
+
+ it "says the service is disabled" do
+ @provider.service_currently_enabled?(@provider.get_priority).should be_false
+ end
+
+ it "stores the 'disabled' state" do
+ Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+ @provider.load_current_resource.should equal(@current_resource)
+ @current_resource.enabled.should be_false
+ end
+ end
+
+ describe "when update-rc.d fails" do
+ before do
+ @status = mock("Status", :exitstatus => -1)
+ @provider.stub!(:popen4).and_return(@status)
+ end
+
+ it "raises an error" do
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+ end
+
+ describe "when enabling a service without priority" do
+ it "should call update-rc.d 'service_name' defaults" do
+ @provider.should_receive(:run_command).with({:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove"})
+ @provider.should_receive(:run_command).with({:command => "/usr/sbin/update-rc.d #{@new_resource.service_name} defaults"})
+ @provider.enable_service()
+ end
+ end
+
+ describe "when enabling a service with simple priority" do
+ before do
+ @new_resource.priority(75)
+ end
+
+ it "should call update-rc.d 'service_name' defaults" do
+ @provider.should_receive(:run_command).with({:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove"})
+ @provider.should_receive(:run_command).with({:command => "/usr/sbin/update-rc.d #{@new_resource.service_name} defaults 75 25"})
+ @provider.enable_service()
+ end
+ end
+
+ describe "when enabling a service with complex priorities" do
+ before do
+ @new_resource.priority(2 => [:start, 20], 3 => [:stop, 55])
+ end
+
+ it "should call update-rc.d 'service_name' defaults" do
+ @provider.should_receive(:run_command).with({:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove"})
+ @provider.should_receive(:run_command).with({:command => "/usr/sbin/update-rc.d #{@new_resource.service_name} start 20 2 . stop 55 3 . "})
+ @provider.enable_service()
+ end
+ end
+
+ describe "when disabling a service without a priority" do
+
+ it "should call update-rc.d -f 'service_name' remove + stop with a default priority" do
+ @provider.should_receive(:run_command).with({:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove"})
+ @provider.should_receive(:run_command).with({:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} stop 80 2 3 4 5 ."})
+ @provider.disable_service()
+ end
+ end
+
+ describe "when disabling a service with simple priority" do
+ before do
+ @new_resource.priority(75)
+ end
+
+ it "should call update-rc.d -f 'service_name' remove + stop with a specified priority" do
+ @provider.should_receive(:run_command).with({:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove"})
+ @provider.should_receive(:run_command).with({:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} stop #{100 - @new_resource.priority} 2 3 4 5 ."})
+ @provider.disable_service()
+ end
+ end
+end
diff --git a/spec/unit/provider/service/freebsd_service_spec.rb b/spec/unit/provider/service/freebsd_service_spec.rb
new file mode 100644
index 0000000000..6dd06bde2c
--- /dev/null
+++ b/spec/unit/provider/service/freebsd_service_spec.rb
@@ -0,0 +1,379 @@
+#
+# Author:: Bryan McLellan (btm@loftninjas.org)
+# Copyright:: Copyright (c) 2009 Bryan McLellan
+# 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::Service::Freebsd do
+ before do
+ @node = Chef::Node.new
+ @node.automatic_attrs[:command] = {:ps => "ps -ax"}
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Service.new("apache22")
+ @new_resource.pattern("httpd")
+ @new_resource.supports({:status => false})
+
+ @current_resource = Chef::Resource::Service.new("apache22")
+
+ @provider = Chef::Provider::Service::Freebsd.new(@new_resource,@run_context)
+ @provider.action = :start
+ @init_command = "/usr/local/etc/rc.d/apache22"
+ Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+ end
+
+ describe "load_current_resource" do
+ before(:each) do
+ @stdout = StringIO.new(<<-PS_SAMPLE)
+413 ?? Ss 0:02.51 /usr/sbin/syslogd -s
+539 ?? Is 0:00.14 /usr/sbin/sshd
+545 ?? Ss 0:17.53 sendmail: accepting connections (sendmail)
+PS_SAMPLE
+ @status = mock(:stdout => @stdout, :exitstatus => 0)
+ @provider.stub!(:shell_out!).with(@node[:command][:ps]).and_return(@status)
+
+ ::File.stub!(:exists?).and_return(false)
+ ::File.stub!(:exists?).with("/usr/local/etc/rc.d/#{@new_resource.service_name}").and_return(true)
+ @lines = mock("lines")
+ @lines.stub!(:each).and_yield("sshd_enable=\"YES\"").
+ and_yield("#{@new_resource.name}_enable=\"YES\"")
+ ::File.stub!(:open).and_return(@lines)
+
+ @rc_with_name = StringIO.new(<<-RC_SAMPLE)
+name="apache22"
+rcvar=`set_rcvar`
+RC_SAMPLE
+ ::File.stub!(:open).with("/usr/local/etc/rc.d/#{@new_resource.service_name}").and_return(@rc_with_name)
+ @provider.stub(:service_enable_variable_name).and_return nil
+
+ end
+
+ it "should create a current resource with the name of the new resource" do
+ Chef::Resource::Service.should_receive(:new).and_return(@current_resource)
+ @provider.load_current_resource
+ end
+
+ it "should set the current resources service name to the new resources service name" do
+ @provider.load_current_resource
+ @current_resource.service_name.should == @new_resource.service_name
+ end
+
+ it "should not raise an exception if the rcscript have a name variable" do
+ @provider.load_current_resource
+ lambda { @provider.service_enable_variable_name }.should_not raise_error(Chef::Exceptions::Service)
+ end
+
+ describe "when the service supports status" do
+ before do
+ @new_resource.supports({:status => true})
+ end
+
+ it "should run '/etc/init.d/service_name status'" do
+ @provider.should_receive(:shell_out).with("/usr/local/etc/rc.d/#{@current_resource.service_name} status").and_return(@status)
+ @provider.load_current_resource
+ end
+
+ it "should set running to true if the the status command returns 0" do
+ @provider.should_receive(:shell_out).with("/usr/local/etc/rc.d/#{@current_resource.service_name} status").and_return(@status)
+ @current_resource.should_receive(:running).with(true)
+ @provider.load_current_resource
+ end
+
+ it "should set running to false if the status command returns anything except 0" do
+ @provider.should_receive(:shell_out).with("/usr/local/etc/rc.d/#{@current_resource.service_name} status").and_raise(Mixlib::ShellOut::ShellCommandFailed)
+ @current_resource.should_receive(:running).with(false)
+ @provider.load_current_resource
+ # @provider.current_resource.running.should be_false
+ end
+ end
+
+ describe "when a status command has been specified" do
+ before do
+ @new_resource.status_command("/bin/chefhasmonkeypants status")
+ end
+
+ it "should run the services status command if one has been specified" do
+ @provider.should_receive(:shell_out).with("/bin/chefhasmonkeypants status").and_return(@status)
+ @provider.load_current_resource
+ end
+
+ end
+
+ it "should raise error if the node has a nil ps attribute and no other means to get status" do
+ @node.automatic_attrs[:command] = {:ps => nil}
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+
+ it "should raise error if the node has an empty ps attribute and no other means to get status" do
+ @node.automatic_attrs[:command] = {:ps => ""}
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+
+ describe "when executing assertions" do
+ it "should verify that /etc/rc.conf exists" do
+ ::File.should_receive(:exists?).with("/etc/rc.conf")
+ @provider.stub!(:service_enable_variable_name).and_return("#{@current_resource.service_name}_enable")
+ @provider.load_current_resource
+ end
+
+ context "and the init script is not found" do
+ [ "start", "reload", "restart", "enable" ].each do |action|
+ it "should raise an exception when the action is #{action}" do
+ ::File.stub!(:exists?).and_return(false)
+ @provider.load_current_resource
+ @provider.define_resource_requirements
+ @provider.instance_variable_get("@rcd_script_found").should be_false
+ @provider.action = action
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+ end
+
+ [ "stop", "disable" ].each do |action|
+ it "should not raise an error when the action is #{action}" do
+ @provider.action = action
+ lambda { @provider.process_resource_requirements }.should_not raise_error
+ end
+ end
+ end
+
+ it "update state when current resource enabled state could not be determined" do
+ ::File.should_receive(:exists?).with("/etc/rc.conf").and_return false
+ @provider.load_current_resource
+ @provider.instance_variable_get("@enabled_state_found").should be_false
+ end
+
+ it "update state when current resource enabled state could be determined" do
+ ::File.stub!(:exist?).with("/usr/local/etc/rc.d/#{@new_resource.service_name}").and_return(true)
+ ::File.should_receive(:exists?).with("/etc/rc.conf").and_return true
+ @provider.load_current_resource
+ @provider.instance_variable_get("@enabled_state_found").should be_false
+ @provider.instance_variable_get("@rcd_script_found").should be_true
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service,
+ "Could not find the service name in /usr/local/etc/rc.d/#{@current_resource.service_name} and rcvar")
+ end
+
+ it "should throw an exception if service line is missing from rc.d script" do
+ pending "not implemented" do
+ false.should be_true
+ end
+ end
+
+ end
+
+ describe "when we have a 'ps' attribute" do
+ before do
+ @node.automatic_attrs[:command] = {:ps => "ps -ax"}
+ end
+
+ it "should shell_out! the node's ps command" do
+ @provider.should_receive(:shell_out!).with(@node[:command][:ps]).and_return(@status)
+ @provider.load_current_resource
+ end
+
+ it "should read stdout of the ps command" do
+ @provider.stub!(:shell_out!).and_return(@status)
+ @stdout.should_receive(:each_line).and_return(true)
+ @provider.load_current_resource
+ end
+
+ it "should set running to true if the regex matches the output" do
+ @stdout.stub!(:each_line).and_yield("555 ?? Ss 0:05.16 /usr/sbin/cron -s").
+ and_yield(" 9881 ?? Ss 0:06.67 /usr/local/sbin/httpd -DNOHTTPACCEPT")
+ @provider.load_current_resource
+ @current_resource.running.should be_true
+ end
+
+ it "should set running to false if the regex doesn't match" do
+ @provider.stub!(:shell_out!).and_return(@status)
+ @provider.load_current_resource
+ @current_resource.running.should be_false
+ end
+
+ it "should raise an exception if ps fails" do
+ @provider.stub!(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed)
+ @provider.load_current_resource
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+ end
+
+ it "should return the current resource" do
+ @provider.load_current_resource.should eql(@current_resource)
+ end
+
+ describe "when starting the service" do
+ it "should call the start command if one is specified" do
+ @new_resource.start_command("/etc/rc.d/chef startyousillysally")
+ @provider.should_receive(:shell_out!).with("/etc/rc.d/chef startyousillysally")
+ @provider.load_current_resource
+ @provider.start_service()
+ end
+
+ it "should call '/usr/local/etc/rc.d/service_name faststart' if no start command is specified" do
+ @provider.should_receive(:shell_out!).with("/usr/local/etc/rc.d/#{@new_resource.service_name} faststart")
+ @provider.load_current_resource
+ @provider.start_service()
+ end
+ end
+
+ describe Chef::Provider::Service::Init, "stop_service" do
+ it "should call the stop command if one is specified" do
+ @new_resource.stop_command("/etc/init.d/chef itoldyoutostop")
+ @provider.should_receive(:shell_out!).with("/etc/init.d/chef itoldyoutostop")
+ @provider.load_current_resource
+ @provider.stop_service()
+ end
+
+ it "should call '/usr/local/etc/rc.d/service_name faststop' if no stop command is specified" do
+ @provider.should_receive(:shell_out!).with("/usr/local/etc/rc.d/#{@new_resource.service_name} faststop")
+ @provider.load_current_resource
+ @provider.stop_service()
+ end
+ end
+
+ describe "when restarting a service" do
+ it "should call 'restart' on the service_name if the resource supports it" do
+ @new_resource.supports({:restart => true})
+ @provider.should_receive(:shell_out!).with("/usr/local/etc/rc.d/#{@new_resource.service_name} fastrestart")
+ @provider.load_current_resource
+ @provider.restart_service()
+ end
+
+ it "should call the restart_command if one has been specified" do
+ @new_resource.restart_command("/etc/init.d/chef restartinafire")
+ @provider.should_receive(:shell_out!).with("/etc/init.d/chef restartinafire")
+ @provider.load_current_resource
+ @provider.restart_service()
+ end
+ end
+
+ describe "when the rcscript does not have a name variable" do
+ before do
+ @rc_without_name = StringIO.new(<<-RC_SAMPLE)
+rcvar=`set_rcvar`
+RC_SAMPLE
+ ::File.stub!(:open).with("/usr/local/etc/rc.d/#{@current_resource.service_name}").and_return(@rc_with_noname)
+ @provider.current_resource = @current_resource
+ end
+
+ describe "when rcvar returns foobar_enable" do
+ before do
+ @rcvar_stdout = <<RCVAR_SAMPLE
+# apache22
+#
+# #{@current_resource.service_name}_enable="YES"
+# (default: "")
+RCVAR_SAMPLE
+ @status = mock(:stdout => @rcvar_stdout, :exitstatus => 0)
+ @provider.stub!(:shell_out!).with("/usr/local/etc/rc.d/#{@current_resource.service_name} rcvar").and_return(@status)
+ end
+
+ it "should get the service name from rcvar if the rcscript does not have a name variable" do
+ @provider.load_current_resource
+ @provider.unstub!(:service_enable_variable_name)
+ @provider.service_enable_variable_name.should == "#{@current_resource.service_name}_enable"
+ end
+
+ it "should not raise an exception if the rcscript does not have a name variable" do
+ @provider.load_current_resource
+ lambda { @provider.service_enable_variable_name }.should_not raise_error(Chef::Exceptions::Service)
+ end
+ end
+
+ describe "when rcvar does not return foobar_enable" do
+ before do
+ @rcvar_stdout = <<RCVAR_SAMPLE
+# service_with_noname
+#
+RCVAR_SAMPLE
+ @status = mock(:stdout => @rcvar_stdout, :exitstatus => 0)
+ @provider.stub!(:shell_out!).with("/usr/local/etc/rc.d/#{@current_resource.service_name} rcvar").and_return(@status)
+ end
+
+ [ "start", "reload", "restart", "enable" ].each do |action|
+ it "should raise an exception when the action is #{action}" do
+ @provider.action = action
+ @provider.load_current_resource
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+ end
+
+ [ "stop", "disable" ].each do |action|
+ it "should not raise an error when the action is #{action}" do
+ ::File.stub!(:exist?).with("/usr/local/etc/rc.d/#{@new_resource.service_name}").and_return(true)
+ @provider.action = action
+ @provider.load_current_resource
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should_not raise_error(Chef::Exceptions::Service)
+ end
+ end
+ end
+ end
+ end
+
+ describe Chef::Provider::Service::Freebsd, "enable_service" do
+ before do
+ @provider.current_resource = @current_resource
+ @provider.stub!(:service_enable_variable_name).and_return("#{@current_resource.service_name}_enable")
+ end
+
+ it "should enable the service if it is not enabled" do
+ @current_resource.stub!(:enabled).and_return(false)
+ @provider.should_receive(:read_rc_conf).and_return([ "foo", "#{@current_resource.service_name}_enable=\"NO\"", "bar" ])
+ @provider.should_receive(:write_rc_conf).with(["foo", "bar", "#{@current_resource.service_name}_enable=\"YES\""])
+ @provider.enable_service()
+ end
+
+ it "should enable the service if it is not enabled and not already specified in the rc.conf file" do
+ @current_resource.stub!(:enabled).and_return(false)
+ @provider.should_receive(:read_rc_conf).and_return([ "foo", "bar" ])
+ @provider.should_receive(:write_rc_conf).with(["foo", "bar", "#{@current_resource.service_name}_enable=\"YES\""])
+ @provider.enable_service()
+ end
+
+ it "should not enable the service if it is already enabled" do
+ @current_resource.stub!(:enabled).and_return(true)
+ @provider.should_not_receive(:write_rc_conf)
+ @provider.enable_service
+ end
+ end
+
+ describe Chef::Provider::Service::Freebsd, "disable_service" do
+ before do
+ @provider.current_resource = @current_resource
+ @provider.stub!(:service_enable_variable_name).and_return("#{@current_resource.service_name}_enable")
+ end
+
+ it "should should disable the service if it is not disabled" do
+ @current_resource.stub!(:enabled).and_return(true)
+ @provider.should_receive(:read_rc_conf).and_return([ "foo", "#{@current_resource.service_name}_enable=\"YES\"", "bar" ])
+ @provider.should_receive(:write_rc_conf).with(["foo", "bar", "#{@current_resource.service_name}_enable=\"NO\""])
+ @provider.disable_service()
+ end
+
+ it "should not disable the service if it is already disabled" do
+ @current_resource.stub!(:enabled).and_return(false)
+ @provider.should_not_receive(:write_rc_conf)
+ @provider.disable_service()
+ end
+ end
+end
diff --git a/spec/unit/provider/service/gentoo_service_spec.rb b/spec/unit/provider/service/gentoo_service_spec.rb
new file mode 100644
index 0000000000..8d4ada043b
--- /dev/null
+++ b/spec/unit/provider/service/gentoo_service_spec.rb
@@ -0,0 +1,144 @@
+#
+# Author:: Lee Jensen (<ljensen@engineyard.com>)
+# Author:: AJ Christensen (<aj@opscode.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::Service::Gentoo do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Service.new("chef")
+ @current_resource = Chef::Resource::Service.new("chef")
+
+ @provider = Chef::Provider::Service::Gentoo.new(@new_resource, @run_context)
+ Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+ @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+ @provider.stub!(:shell_out).and_return(@status)
+ File.stub!(:exists?).with("/etc/init.d/chef").and_return(true)
+ File.stub!(:exists?).with("/sbin/rc-update").and_return(true)
+ File.stub!(:exists?).with("/etc/runlevels/default/chef").and_return(false)
+ File.stub!(:readable?).with("/etc/runlevels/default/chef").and_return(false)
+ end
+ # new test: found_enabled state
+ #
+ describe "load_current_resource" do
+ it "should raise Chef::Exceptions::Service if /sbin/rc-update does not exist" do
+ File.should_receive(:exists?).with("/sbin/rc-update").and_return(false)
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+
+ it "should track when service file is not found in /etc/runlevels" do
+ @provider.load_current_resource
+ @provider.instance_variable_get("@found_script").should be_false
+ end
+
+ it "should track when service file is found in /etc/runlevels/**/" do
+ Dir.stub!(:glob).with("/etc/runlevels/**/chef").and_return(["/etc/runlevels/default/chef"])
+ @provider.load_current_resource
+ @provider.instance_variable_get("@found_script").should be_true
+ end
+
+ describe "when detecting the service enable state" do
+ describe "and the glob returns a default service script file" do
+ before do
+ Dir.stub!(:glob).with("/etc/runlevels/**/chef").and_return(["/etc/runlevels/default/chef"])
+ end
+
+ describe "and the file exists and is readable" do
+ before do
+ File.stub!(:exists?).with("/etc/runlevels/default/chef").and_return(true)
+ File.stub!(:readable?).with("/etc/runlevels/default/chef").and_return(true)
+ end
+ it "should set enabled to true" do
+ @provider.load_current_resource
+ @current_resource.enabled.should be_true
+ end
+ end
+
+ describe "and the file exists but is not readable" do
+ before do
+ File.stub!(:exists?).with("/etc/runlevels/default/chef").and_return(true)
+ File.stub!(:readable?).with("/etc/runlevels/default/chef").and_return(false)
+ end
+
+ it "should set enabled to false" do
+ @provider.load_current_resource
+ @current_resource.enabled.should be_false
+ end
+ end
+
+ describe "and the file does not exist" do
+ before do
+ File.stub!(:exists?).with("/etc/runlevels/default/chef").and_return(false)
+ File.stub!(:readable?).with("/etc/runlevels/default/chef").and_return("foobarbaz")
+ end
+
+ it "should set enabled to false" do
+ @provider.load_current_resource
+ @current_resource.enabled.should be_false
+ end
+
+ end
+ end
+
+ end
+
+ it "should return the current_resource" do
+ @provider.load_current_resource.should == @current_resource
+ end
+
+ it "should support the status command automatically" do
+ @provider.load_current_resource
+ @new_resource.supports[:status].should be_true
+ end
+
+ it "should support the restart command automatically" do
+ @provider.load_current_resource
+ @new_resource.supports[:restart].should be_true
+ end
+
+ it "should not support the reload command automatically" do
+ @provider.load_current_resource
+ @new_resource.supports[:reload].should_not be_true
+ end
+
+ end
+
+ describe "action_methods" do
+ before(:each) { @provider.stub!(:load_current_resource).and_return(@current_resource) }
+
+ describe Chef::Provider::Service::Gentoo, "enable_service" do
+ it "should call rc-update add *service* default" do
+ @provider.should_receive(:run_command).with({:command => "/sbin/rc-update add chef default"})
+ @provider.enable_service()
+ end
+ end
+
+ describe Chef::Provider::Service::Gentoo, "disable_service" do
+ it "should call rc-update del *service* default" do
+ @provider.should_receive(:run_command).with({:command => "/sbin/rc-update del chef default"})
+ @provider.disable_service()
+ end
+ end
+ end
+
+end
diff --git a/spec/unit/provider/service/init_service_spec.rb b/spec/unit/provider/service/init_service_spec.rb
new file mode 100644
index 0000000000..77b22c8cf4
--- /dev/null
+++ b/spec/unit/provider/service/init_service_spec.rb
@@ -0,0 +1,212 @@
+#
+# Author:: AJ Christensen (<aj@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::Service::Init, "load_current_resource" do
+ before(:each) do
+ @node = Chef::Node.new
+ @node.automatic_attrs[:command] = {:ps => "ps -ef"}
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Service.new("chef")
+
+ @current_resource = Chef::Resource::Service.new("chef")
+
+ @provider = Chef::Provider::Service::Init.new(@new_resource, @run_context)
+ Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+
+ @stdout = StringIO.new(<<-PS)
+aj 7842 5057 0 21:26 pts/2 00:00:06 vi init.rb
+aj 7903 5016 0 21:26 pts/5 00:00:00 /bin/bash
+aj 8119 6041 0 21:34 pts/3 00:00:03 vi init_service_spec.rb
+PS
+ @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+ @provider.stub!(:shell_out!).and_return(@status)
+ end
+
+ it "should create a current resource with the name of the new resource" do
+ @provider.load_current_resource
+ @provider.current_resource.should equal(@current_resource)
+ end
+
+ it "should set the current resources service name to the new resources service name" do
+ @provider.load_current_resource
+ @current_resource.service_name.should == 'chef'
+ end
+
+ describe "when the service supports status" do
+ before do
+ @new_resource.supports({:status => true})
+ end
+
+ it "should run '/etc/init.d/service_name status'" do
+ @provider.should_receive(:shell_out).with("/etc/init.d/#{@current_resource.service_name} status").and_return(@status)
+ @provider.load_current_resource
+ end
+
+ it "should set running to true if the the status command returns 0" do
+ @provider.stub!(:shell_out).with("/etc/init.d/#{@current_resource.service_name} status").and_return(@status)
+ @provider.load_current_resource
+ @current_resource.running.should be_true
+ end
+
+ it "should set running to false if the status command returns anything except 0" do
+ @status.stub!(:exitstatus).and_return(1)
+ @provider.stub!(:shell_out).with("/etc/init.d/#{@current_resource.service_name} status").and_return(@status)
+ @provider.load_current_resource
+ @current_resource.running.should be_false
+ end
+
+ it "should set running to false if the status command raises" do
+ @provider.stub!(:shell_out).and_raise(Mixlib::ShellOut::ShellCommandFailed)
+ @provider.load_current_resource
+ @current_resource.running.should be_false
+ end
+ end
+
+ describe "when a status command has been specified" do
+ before do
+ @new_resource.stub!(:status_command).and_return("/etc/init.d/chefhasmonkeypants status")
+ end
+
+ it "should run the services status command if one has been specified" do
+ @provider.should_receive(:shell_out).with("/etc/init.d/chefhasmonkeypants status").and_return(@status)
+ @provider.load_current_resource
+ end
+
+ end
+
+ describe "when the node has not specified a ps command" do
+
+ it "should raise an error if the node has a nil ps attribute" do
+ @node.automatic_attrs[:command] = {:ps => nil}
+ @provider.load_current_resource
+ @provider.action = :start
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+
+ it "should raise an error if the node has an empty ps attribute" do
+ @node.automatic_attrs[:command] = {:ps => ""}
+ @provider.load_current_resource
+ @provider.action = :start
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+
+ end
+
+
+ describe "when we have a 'ps' attribute" do
+ it "should shell_out! the node's ps command" do
+ @provider.should_receive(:shell_out!).and_return(@status)
+ @provider.load_current_resource
+ end
+
+ it "should set running to true if the regex matches the output" do
+ @stdout = StringIO.new(<<-RUNNING_PS)
+aj 7842 5057 0 21:26 pts/2 00:00:06 chef
+aj 7842 5057 0 21:26 pts/2 00:00:06 poos
+RUNNING_PS
+ @status.stub!(:stdout).and_return(@stdout)
+ @provider.load_current_resource
+ @current_resource.running.should be_true
+ end
+
+ it "should set running to false if the regex doesn't match" do
+ @provider.stub!(:shell_out!).and_return(@status)
+ @provider.load_current_resource
+ @current_resource.running.should be_false
+ end
+
+ it "should raise an exception if ps fails" do
+ @provider.stub!(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed)
+ @provider.load_current_resource
+ @provider.action = :start
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+ end
+
+ it "should return the current resource" do
+ @provider.load_current_resource.should eql(@current_resource)
+ end
+
+ describe "when starting the service" do
+ it "should call the start command if one is specified" do
+ @new_resource.start_command("/etc/init.d/chef startyousillysally")
+ @provider.should_receive(:shell_out!).with("/etc/init.d/chef startyousillysally")
+ @provider.start_service()
+ end
+
+ it "should call '/etc/init.d/service_name start' if no start command is specified" do
+ @provider.should_receive(:shell_out!).with("/etc/init.d/#{@new_resource.service_name} start")
+ @provider.start_service()
+ end
+ end
+
+ describe Chef::Provider::Service::Init, "stop_service" do
+ it "should call the stop command if one is specified" do
+ @new_resource.stop_command("/etc/init.d/chef itoldyoutostop")
+ @provider.should_receive(:shell_out!).with("/etc/init.d/chef itoldyoutostop")
+ @provider.stop_service()
+ end
+
+ it "should call '/etc/init.d/service_name stop' if no stop command is specified" do
+ @provider.should_receive(:shell_out!).with("/etc/init.d/#{@new_resource.service_name} stop")
+ @provider.stop_service()
+ end
+ end
+
+ describe "when restarting a service" do
+ it "should call 'restart' on the service_name if the resource supports it" do
+ @new_resource.supports({:restart => true})
+ @provider.should_receive(:shell_out!).with("/etc/init.d/#{@new_resource.service_name} restart")
+ @provider.restart_service()
+ end
+
+ it "should call the restart_command if one has been specified" do
+ @new_resource.restart_command("/etc/init.d/chef restartinafire")
+ @provider.should_receive(:shell_out!).with("/etc/init.d/#{@new_resource.service_name} restartinafire")
+ @provider.restart_service()
+ end
+
+ it "should just call stop, then start when the resource doesn't support restart and no restart_command is specified" do
+ @provider.should_receive(:stop_service)
+ @provider.should_receive(:sleep).with(1)
+ @provider.should_receive(:start_service)
+ @provider.restart_service()
+ end
+ end
+
+ describe "when reloading a service" do
+ it "should call 'reload' on the service if it supports it" do
+ @new_resource.supports({:reload => true})
+ @provider.should_receive(:shell_out!).with("/etc/init.d/chef reload")
+ @provider.reload_service()
+ end
+
+ it "should should run the user specified reload command if one is specified and the service doesn't support reload" do
+ @new_resource.reload_command("/etc/init.d/chef lollerpants")
+ @provider.should_receive(:shell_out!).with("/etc/init.d/chef lollerpants")
+ @provider.reload_service()
+ end
+ end
+end
diff --git a/spec/unit/provider/service/insserv_service_spec.rb b/spec/unit/provider/service/insserv_service_spec.rb
new file mode 100644
index 0000000000..c823d511b5
--- /dev/null
+++ b/spec/unit/provider/service/insserv_service_spec.rb
@@ -0,0 +1,76 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 2011 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'
+
+describe Chef::Provider::Service::Insserv do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @node.automatic_attrs[:command] = {:ps => "ps -ax"}
+
+ @new_resource = Chef::Resource::Service.new("initgrediant")
+ @current_resource = Chef::Resource::Service.new("initgrediant")
+
+ @provider = Chef::Provider::Service::Insserv.new(@new_resource, @run_context)
+ @status = mock("Process::Status mock", :exitstatus => 0, :stdout => "")
+ @provider.stub!(:shell_out!).and_return(@status)
+ end
+
+ describe "load_current_resource" do
+ describe "when startup links exist" do
+ before do
+ Dir.stub!(:glob).with("/etc/rc**/S*initgrediant").and_return(["/etc/rc5.d/S18initgrediant", "/etc/rc2.d/S18initgrediant", "/etc/rc4.d/S18initgrediant", "/etc/rc3.d/S18initgrediant"])
+ end
+
+ it "sets the current enabled status to true" do
+ @provider.load_current_resource
+ @provider.current_resource.enabled.should be_true
+ end
+ end
+
+ describe "when startup links do not exist" do
+ before do
+ Dir.stub!(:glob).with("/etc/rc**/S*initgrediant").and_return([])
+ end
+
+ it "sets the current enabled status to false" do
+ @provider.load_current_resource
+ @provider.current_resource.enabled.should be_false
+ end
+ end
+
+ end
+
+ describe "enable_service" do
+ it "should call insserv and create the default links" do
+ @provider.should_receive(:run_command).with({:command=>"/sbin/insserv -r -f #{@new_resource.service_name}"})
+ @provider.should_receive(:run_command).with({:command=>"/sbin/insserv -d -f #{@new_resource.service_name}"})
+ @provider.enable_service
+ end
+ end
+
+ describe "disable_service" do
+ it "should call insserv and remove the links" do
+ @provider.should_receive(:run_command).with({:command=>"/sbin/insserv -r -f #{@new_resource.service_name}"})
+ @provider.disable_service
+ end
+ end
+end
+
diff --git a/spec/unit/provider/service/invokercd_service_spec.rb b/spec/unit/provider/service/invokercd_service_spec.rb
new file mode 100644
index 0000000000..ace2ad24e3
--- /dev/null
+++ b/spec/unit/provider/service/invokercd_service_spec.rb
@@ -0,0 +1,212 @@
+#
+# Author:: AJ Christensen (<aj@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::Service::Invokercd, "load_current_resource" do
+ before(:each) do
+ @node = Chef::Node.new
+ @node.automatic_attrs[:command] = {:ps => "ps -ef"}
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Service.new("chef")
+
+ @current_resource = Chef::Resource::Service.new("chef")
+
+ @provider = Chef::Provider::Service::Invokercd.new(@new_resource, @run_context)
+ Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+
+ @stdout = StringIO.new(<<-PS)
+aj 7842 5057 0 21:26 pts/2 00:00:06 vi init.rb
+aj 7903 5016 0 21:26 pts/5 00:00:00 /bin/bash
+aj 8119 6041 0 21:34 pts/3 00:00:03 vi init_service_spec.rb
+PS
+ @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+ @provider.stub!(:shell_out!).and_return(@status)
+ end
+
+ it "should create a current resource with the name of the new resource" do
+ @provider.load_current_resource
+ @provider.current_resource.should equal(@current_resource)
+ end
+
+ it "should set the current resources service name to the new resources service name" do
+ @provider.load_current_resource
+ @current_resource.service_name.should == 'chef'
+ end
+
+ describe "when the service supports status" do
+ before do
+ @new_resource.supports({:status => true})
+ end
+
+ it "should run '/usr/sbin/invoke-rc.d service_name status'" do
+ @provider.should_receive(:shell_out).with("/usr/sbin/invoke-rc.d #{@current_resource.service_name} status").and_return(@status)
+ @provider.load_current_resource
+ end
+
+ it "should set running to true if the the status command returns 0" do
+ @provider.stub!(:shell_out).with("/usr/sbin/invoke-rc.d #{@current_resource.service_name} status").and_return(@status)
+ @provider.load_current_resource
+ @current_resource.running.should be_true
+ end
+
+ it "should set running to false if the status command returns anything except 0" do
+ @status.stub!(:exitstatus).and_return(1)
+ @provider.stub!(:shell_out).with("/usr/sbin/invoke-rc.d #{@current_resource.service_name} status").and_return(@status)
+ @provider.load_current_resource
+ @current_resource.running.should be_false
+ end
+
+ it "should set running to false if the status command raises" do
+ @provider.stub!(:shell_out).with("/usr/sbin/invoke-rc.d #{@current_resource.service_name} status").and_raise(Mixlib::ShellOut::ShellCommandFailed)
+ @provider.load_current_resource
+ @current_resource.running.should be_false
+ end
+ end
+
+ describe "when a status command has been specified" do
+ before do
+ @new_resource.stub!(:status_command).and_return("/usr/sbin/invoke-rc.d chefhasmonkeypants status")
+ end
+
+ it "should run the services status command if one has been specified" do
+ @provider.should_receive(:shell_out).with("/usr/sbin/invoke-rc.d chefhasmonkeypants status").and_return(@status)
+ @provider.load_current_resource
+ end
+
+ end
+
+ describe "when the node has not specified a ps command" do
+ it "should raise error if the node has a nil ps attribute and no other means to get status" do
+ @node.automatic_attrs[:command] = {:ps => nil}
+ @provider.action = :start
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+
+ it "should raise error if the node has an empty ps attribute and no other means to get status" do
+ @node.automatic_attrs[:command] = {:ps => ""}
+ @provider.action = :start
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+
+ end
+
+
+ describe "when we have a 'ps' attribute" do
+ it "should shell_out! the node's ps command" do
+ @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+ @provider.should_receive(:shell_out!).with(@node[:command][:ps]).and_return(@status)
+ @provider.load_current_resource
+ end
+
+ it "should set running to true if the regex matches the output" do
+ @stdout = StringIO.new(<<-RUNNING_PS)
+aj 7842 5057 0 21:26 pts/2 00:00:06 chef
+aj 7842 5057 0 21:26 pts/2 00:00:06 poos
+RUNNING_PS
+ @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+ @provider.should_receive(:shell_out!).and_return(@status)
+ @provider.load_current_resource
+ @current_resource.running.should be_true
+ end
+
+ it "should set running to false if the regex doesn't match" do
+ @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+ @provider.should_receive(:shell_out!).and_return(@status)
+ @provider.load_current_resource
+ @current_resource.running.should be_false
+ end
+
+ it "should raise an exception if ps fails" do
+ @provider.stub!(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed)
+ @provider.action = :start
+ @provider.load_current_resource
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+ end
+
+ it "should return the current resource" do
+ @provider.load_current_resource.should eql(@current_resource)
+ end
+
+ describe "when starting the service" do
+ it "should call the start command if one is specified" do
+ @new_resource.start_command("/usr/sbin/invoke-rc.d chef startyousillysally")
+ @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d chef startyousillysally")
+ @provider.start_service()
+ end
+
+ it "should call '/usr/sbin/invoke-rc.d service_name start' if no start command is specified" do
+ @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} start")
+ @provider.start_service()
+ end
+ end
+
+ describe Chef::Provider::Service::Invokercd, "stop_service" do
+ it "should call the stop command if one is specified" do
+ @new_resource.stop_command("/usr/sbin/invoke-rc.d chef itoldyoutostop")
+ @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d chef itoldyoutostop")
+ @provider.stop_service()
+ end
+
+ it "should call '/usr/sbin/invoke-rc.d service_name stop' if no stop command is specified" do
+ @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} stop")
+ @provider.stop_service()
+ end
+ end
+
+ describe "when restarting a service" do
+ it "should call 'restart' on the service_name if the resource supports it" do
+ @new_resource.supports({:restart => true})
+ @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} restart")
+ @provider.restart_service()
+ end
+
+ it "should call the restart_command if one has been specified" do
+ @new_resource.restart_command("/usr/sbin/invoke-rc.d chef restartinafire")
+ @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} restartinafire")
+ @provider.restart_service()
+ end
+
+ it "should just call stop, then start when the resource doesn't support restart and no restart_command is specified" do
+ @provider.should_receive(:stop_service)
+ @provider.should_receive(:sleep).with(1)
+ @provider.should_receive(:start_service)
+ @provider.restart_service()
+ end
+ end
+
+ describe "when reloading a service" do
+ it "should call 'reload' on the service if it supports it" do
+ @new_resource.supports({:reload => true})
+ @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d chef reload")
+ @provider.reload_service()
+ end
+
+ it "should should run the user specified reload command if one is specified and the service doesn't support reload" do
+ @new_resource.reload_command("/usr/sbin/invoke-rc.d chef lollerpants")
+ @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d chef lollerpants")
+ @provider.reload_service()
+ end
+ end
+end
diff --git a/spec/unit/provider/service/macosx_spec.rb b/spec/unit/provider/service/macosx_spec.rb
new file mode 100644
index 0000000000..9c3ec340a2
--- /dev/null
+++ b/spec/unit/provider/service/macosx_spec.rb
@@ -0,0 +1,229 @@
+#
+# Author:: Igor Afonov <afonov@gmail.com>
+# Copyright:: Copyright (c) 2011 Igor Afonov
+# 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::Service::Macosx do
+ let(:node) { Chef::Node.new }
+ let(:events) {Chef::EventDispatch::Dispatcher.new}
+ let(:run_context) { Chef::RunContext.new(node, {}, events) }
+ let(:provider) { described_class.new(new_resource, run_context) }
+ let(:stdout) { StringIO.new }
+
+ before do
+ Dir.stub!(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist"], [])
+ provider.stub!(:shell_out!).
+ with("launchctl list", {:group => 1001, :user => 101}).
+ and_return(mock("ouput", :stdout => stdout))
+
+ File.stub!(:stat).and_return(mock("stat", :gid => 1001, :uid => 101))
+ end
+
+ ["redis-server", "io.redis.redis-server"].each do |service_name|
+ context "when service name is given as #{service_name}" do
+ let(:new_resource) { Chef::Resource::Service.new(service_name) }
+ let!(:current_resource) { Chef::Resource::Service.new(service_name) }
+
+ describe "#load_current_resource" do
+ context "when launchctl returns pid in service list" do
+ let(:stdout) { StringIO.new <<-SVC_LIST }
+12761 - 0x100114220.old.machinit.thing
+7777 - io.redis.redis-server
+- - com.lol.stopped-thing
+SVC_LIST
+
+ before do
+ provider.load_current_resource
+ end
+
+ it "sets resource running state to true" do
+ provider.current_resource.running.should be_true
+ end
+
+ it "sets resouce enabled state to true" do
+ provider.current_resource.enabled.should be_true
+ end
+ end
+
+ describe "running unsupported actions" do
+ before do
+ Dir.stub!(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist"], [])
+ end
+ it "should throw an exception when enable action is attempted" do
+ lambda {provider.run_action(:enable)}.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+ it "should throw an exception when reload action is attempted" do
+ lambda {provider.run_action(:reload)}.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+ it "should throw an exception when disable action is attempted" do
+ lambda {provider.run_action(:disable)}.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+ end
+ context "when launchctl returns empty service pid" do
+ let(:stdout) { StringIO.new <<-SVC_LIST }
+12761 - 0x100114220.old.machinit.thing
+- - io.redis.redis-server
+- - com.lol.stopped-thing
+SVC_LIST
+
+ before do
+ provider.load_current_resource
+ end
+
+ it "sets resource running state to false" do
+ provider.current_resource.running.should be_false
+ end
+
+ it "sets resouce enabled state to true" do
+ provider.current_resource.enabled.should be_true
+ end
+ end
+
+ context "when launchctl doesn't return service entry at all" do
+ let(:stdout) { StringIO.new <<-SVC_LIST }
+12761 - 0x100114220.old.machinit.thing
+- - com.lol.stopped-thing
+SVC_LIST
+
+ it "sets service running state to false" do
+ provider.load_current_resource
+ provider.current_resource.running.should be_false
+ end
+
+ context "and plist for service is not available" do
+ before do
+ Dir.stub!(:glob).and_return([])
+ provider.load_current_resource
+ end
+
+ it "sets resouce enabled state to false" do
+ provider.current_resource.enabled.should be_false
+ end
+ end
+
+ context "and plist for service is available" do
+ before do
+ Dir.stub!(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist"], [])
+ provider.load_current_resource
+ end
+
+ it "sets resouce enabled state to true" do
+ provider.current_resource.enabled.should be_true
+ end
+ end
+
+ describe "and several plists match service name" do
+ it "throws exception" do
+ Dir.stub!(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist",
+ "/Users/wtf/something.plist"])
+ provider.load_current_resource
+ provider.define_resource_requirements
+ lambda { provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+ end
+ end
+ end
+ describe "#start_service" do
+ before do
+ Chef::Resource::Service.stub!(:new).and_return(current_resource)
+ provider.load_current_resource
+ current_resource.stub!(:running).and_return(false)
+ end
+
+ it "calls the start command if one is specified and service is not running" do
+ new_resource.stub!(:start_command).and_return("cowsay dirty")
+
+ provider.should_receive(:shell_out!).with("cowsay dirty")
+ provider.start_service
+ end
+
+ it "shows warning message if service is already running" do
+ current_resource.stub!(:running).and_return(true)
+ Chef::Log.should_receive(:debug).with("service[#{service_name}] already running, not starting")
+
+ provider.start_service
+ end
+
+ it "starts service via launchctl if service found" do
+ provider.should_receive(:shell_out!).
+ with("launchctl load -w '/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist'",
+ :group => 1001, :user => 101).
+ and_return(0)
+
+ provider.start_service
+ end
+ end
+
+ describe "#stop_service" do
+ before do
+ Chef::Resource::Service.stub!(:new).and_return(current_resource)
+
+ provider.load_current_resource
+ current_resource.stub!(:running).and_return(true)
+ end
+
+ it "calls the stop command if one is specified and service is running" do
+ new_resource.stub!(:stop_command).and_return("kill -9 123")
+
+ provider.should_receive(:shell_out!).with("kill -9 123")
+ provider.stop_service
+ end
+
+ it "shows warning message if service is not running" do
+ current_resource.stub!(:running).and_return(false)
+ Chef::Log.should_receive(:debug).with("service[#{service_name}] not running, not stopping")
+
+ provider.stop_service
+ end
+
+ it "stops the service via launchctl if service found" do
+ provider.should_receive(:shell_out!).
+ with("launchctl unload '/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist'",
+ :group => 1001, :user => 101).
+ and_return(0)
+
+ provider.stop_service
+ end
+ end
+
+ describe "#restart_service" do
+ before do
+ Chef::Resource::Service.stub!(:new).and_return(current_resource)
+
+ provider.load_current_resource
+ current_resource.stub!(:running).and_return(true)
+ provider.stub!(:sleep)
+ end
+
+ it "issues a command if given" do
+ new_resource.stub!(:restart_command).and_return("reload that thing")
+
+ provider.should_receive(:shell_out!).with("reload that thing")
+ provider.restart_service
+ end
+
+ it "stops and then starts service" do
+ provider.should_receive(:stop_service)
+ provider.should_receive(:start_service);
+
+ provider.restart_service
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/provider/service/redhat_spec.rb b/spec/unit/provider/service/redhat_spec.rb
new file mode 100644
index 0000000000..dd874a4f05
--- /dev/null
+++ b/spec/unit/provider/service/redhat_spec.rb
@@ -0,0 +1,156 @@
+#
+# Author:: AJ Christensen (<aj@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper"))
+require 'ostruct'
+
+shared_examples_for "define_resource_requirements_common" do
+ it "should raise an error if /sbin/chkconfig does not exist" do
+ File.stub!(:exists?).with("/sbin/chkconfig").and_return(false)
+ @provider.stub!(:shell_out).with("/sbin/service chef status").and_raise(Errno::ENOENT)
+ @provider.stub!(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_raise(Errno::ENOENT)
+ @provider.load_current_resource
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+
+ it "should not raise an error if the service exists but is not added to any runlevels" do
+ status = mock("Status", :exitstatus => 0, :stdout => "" , :stderr => "")
+ @provider.should_receive(:shell_out).with("/sbin/service chef status").and_return(status)
+ chkconfig = mock("Chkconfig", :exitstatus => 0, :stdout => "", :stderr => "service chef supports chkconfig, but is not referenced in any runlevel (run 'chkconfig --add chef')")
+ @provider.should_receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+ @provider.load_current_resource
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should_not raise_error
+ end
+end
+
+describe "Chef::Provider::Service::Redhat" do
+
+ before(:each) do
+ @node = Chef::Node.new
+ @node.automatic_attrs[:command] = {:ps => 'foo'}
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Service.new("chef")
+
+ @current_resource = Chef::Resource::Service.new("chef")
+
+ @provider = Chef::Provider::Service::Redhat.new(@new_resource, @run_context)
+ @provider.action = :start
+ Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+ File.stub!(:exists?).with("/sbin/chkconfig").and_return(true)
+ end
+
+ describe "while not in why run mode" do
+ before(:each) do
+ Chef::Config[:why_run] = false
+ end
+
+ describe "load current resource" do
+ it "sets the current enabled status to true if the service is enabled for any run level" do
+ status = mock("Status", :exitstatus => 0, :stdout => "" , :stderr => "")
+ @provider.should_receive(:shell_out).with("/sbin/service chef status").and_return(status)
+ chkconfig = mock("Chkconfig", :exitstatus => 0, :stdout => "chef 0:off 1:off 2:off 3:off 4:off 5:on 6:off", :stderr => "")
+ @provider.should_receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+ @provider.instance_variable_get("@service_missing").should be_false
+ @provider.load_current_resource
+ @current_resource.enabled.should be_true
+ end
+
+ it "sets the current enabled status to false if the regex does not match" do
+ status = mock("Status", :exitstatus => 0, :stdout => "" , :stderr => "")
+ @provider.should_receive(:shell_out).with("/sbin/service chef status").and_return(status)
+ chkconfig = mock("Chkconfig", :exitstatus => 0, :stdout => "chef 0:off 1:off 2:off 3:off 4:off 5:off 6:off", :stderr => "")
+ @provider.should_receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+ @provider.instance_variable_get("@service_missing").should be_false
+ @provider.load_current_resource.should eql(@current_resource)
+ @current_resource.enabled.should be_false
+ end
+ end
+
+ describe "define resource requirements" do
+ it_should_behave_like "define_resource_requirements_common"
+
+ context "when the service does not exist" do
+ before do
+ status = mock("Status", :exitstatus => 1, :stdout => "", :stderr => "chef: unrecognized service")
+ @provider.should_receive(:shell_out).with("/sbin/service chef status").and_return(status)
+ chkconfig = mock("Chkconfig", :existatus=> 1, :stdout => "", :stderr => "error reading information on service chef: No such file or directory")
+ @provider.should_receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+ @provider.load_current_resource
+ @provider.define_resource_requirements
+ end
+
+ [ "start", "reload", "restart", "enable" ].each do |action|
+ it "should raise an error when the action is #{action}" do
+ @provider.action = action
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+ end
+
+ [ "stop", "disable" ].each do |action|
+ it "should not raise an error when the action is #{action}" do
+ @provider.action = action
+ lambda { @provider.process_resource_requirements }.should_not raise_error
+ end
+ end
+ end
+ end
+ end
+
+ describe "while in why run mode" do
+ before(:each) do
+ Chef::Config[:why_run] = true
+ end
+
+ after do
+ Chef::Config[:why_run] = false
+ end
+
+ describe "define resource requirements" do
+ it_should_behave_like "define_resource_requirements_common"
+
+ it "should not raise an error if the service does not exist" do
+ status = mock("Status", :exitstatus => 1, :stdout => "", :stderr => "chef: unrecognized service")
+ @provider.should_receive(:shell_out).with("/sbin/service chef status").and_return(status)
+ chkconfig = mock("Chkconfig", :existatus=> 1, :stdout => "", :stderr => "error reading information on service chef: No such file or directory")
+ @provider.should_receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+ @provider.load_current_resource
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should_not raise_error
+ end
+ end
+ end
+
+ describe "enable_service" do
+ it "should call chkconfig to add 'service_name'" do
+ @provider.should_receive(:shell_out!).with("/sbin/chkconfig #{@new_resource.service_name} on")
+ @provider.enable_service
+ end
+ end
+
+ describe "disable_service" do
+ it "should call chkconfig to del 'service_name'" do
+ @provider.should_receive(:shell_out!).with("/sbin/chkconfig #{@new_resource.service_name} off")
+ @provider.disable_service
+ end
+ end
+
+end
diff --git a/spec/unit/provider/service/simple_service_spec.rb b/spec/unit/provider/service/simple_service_spec.rb
new file mode 100644
index 0000000000..cc0173e246
--- /dev/null
+++ b/spec/unit/provider/service/simple_service_spec.rb
@@ -0,0 +1,171 @@
+#
+# Author:: Mathieu Sauve-Frankel <msf@kisoku.net>
+# Copyright:: Copyright (c) 2009, Mathieu Sauve Frankel
+# 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::Service::Simple, "load_current_resource" do
+ before(:each) do
+ @node = Chef::Node.new
+ @node.automatic_attrs[:command] = {:ps => "ps -ef"}
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Service.new("chef")
+ @current_resource = Chef::Resource::Service.new("chef")
+
+ @provider = Chef::Provider::Service::Simple.new(@new_resource, @run_context)
+ Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+
+ @stdout = StringIO.new(<<-NOMOCKINGSTRINGSPLZ)
+aj 7842 5057 0 21:26 pts/2 00:00:06 vi init.rb
+aj 7903 5016 0 21:26 pts/5 00:00:00 /bin/bash
+aj 8119 6041 0 21:34 pts/3 00:00:03 vi simple_service_spec.rb
+NOMOCKINGSTRINGSPLZ
+ @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+ @provider.stub!(:shell_out!).and_return(@status)
+ end
+
+ it "should create a current resource with the name of the new resource" do
+ Chef::Resource::Service.should_receive(:new).and_return(@current_resource)
+ @provider.load_current_resource
+ end
+
+ it "should set the current resources service name to the new resources service name" do
+ @current_resource.should_receive(:service_name).with(@new_resource.service_name)
+ @provider.load_current_resource
+ end
+
+ it "should raise error if the node has a nil ps attribute and no other means to get status" do
+ @node.automatic_attrs[:command] = {:ps => nil}
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+
+ it "should raise error if the node has an empty ps attribute and no other means to get status" do
+ @node.automatic_attrs[:command] = {:ps => ""}
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+
+ describe "when we have a 'ps' attribute" do
+ it "should shell_out! the node's ps command" do
+ @provider.should_receive(:shell_out!).with(@node[:command][:ps]).and_return(@status)
+ @provider.load_current_resource
+ end
+
+ it "should read stdout of the ps command" do
+ @provider.stub!(:shell_out!).and_return(@status)
+ @stdout.should_receive(:each_line).and_return(true)
+ @provider.load_current_resource
+ end
+
+ it "should set running to true if the regex matches the output" do
+ @stdout = StringIO.new(<<-NOMOCKINGSTRINGSPLZ)
+aj 7842 5057 0 21:26 pts/2 00:00:06 chef
+aj 7842 5057 0 21:26 pts/2 00:00:06 poos
+NOMOCKINGSTRINGSPLZ
+ @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+ @provider.stub!(:shell_out!).and_return(@status)
+ @provider.load_current_resource
+ @current_resource.running.should be_true
+ end
+
+ it "should set running to false if the regex doesn't match" do
+ @provider.stub!(:shell_out!).and_return(@status)
+ @provider.load_current_resource
+ @current_resource.running.should be_false
+ end
+
+ it "should raise an exception if ps fails" do
+ @provider.stub!(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed)
+ @provider.action = :start
+ @provider.load_current_resource
+ @provider.define_resource_requirements
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+ end
+
+ it "should return the current resource" do
+ @provider.load_current_resource.should eql(@current_resource)
+ end
+
+
+
+ describe "when starting the service" do
+ it "should call the start command if one is specified" do
+ @new_resource.stub!(:start_command).and_return("#{@new_resource.start_command}")
+ @provider.should_receive(:shell_out!).with("#{@new_resource.start_command}")
+ @provider.start_service()
+ end
+
+ it "should raise an exception if no start command is specified" do
+ @provider.define_resource_requirements
+ @provider.action = :start
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+ end
+
+ describe "when stopping a service" do
+ it "should call the stop command if one is specified" do
+ @new_resource.stop_command("/etc/init.d/themadness stop")
+ @provider.should_receive(:shell_out!).with("/etc/init.d/themadness stop")
+ @provider.stop_service()
+ end
+
+ it "should raise an exception if no stop command is specified" do
+ @provider.define_resource_requirements
+ @provider.action = :stop
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+ end
+
+ describe Chef::Provider::Service::Simple, "restart_service" do
+ it "should call the restart command if one has been specified" do
+ @new_resource.restart_command("/etc/init.d/foo restart")
+ @provider.should_receive(:shell_out!).with("/etc/init.d/foo restart")
+ @provider.restart_service()
+ end
+
+ it "should raise an exception if the resource doesn't support restart, no restart command is provided, and no stop command is provided" do
+ @provider.define_resource_requirements
+ @provider.action = :restart
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+ end
+
+ it "should just call stop, then start when the resource doesn't support restart and no restart_command is specified" do
+ @provider.should_receive(:stop_service)
+ @provider.should_receive(:sleep).with(1)
+ @provider.should_receive(:start_service)
+ @provider.restart_service()
+ end
+ end
+
+ describe Chef::Provider::Service::Simple, "reload_service" do
+ it "should raise an exception if reload is requested but no command is specified" do
+ @provider.define_resource_requirements
+ @provider.action = :reload
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+
+ it "should should run the user specified reload command if one is specified" do
+ @new_resource.reload_command("kill -9 1")
+ @provider.should_receive(:shell_out!).with("kill -9 1")
+ @provider.reload_service()
+ end
+ end
+end
diff --git a/spec/unit/provider/service/solaris_smf_service_spec.rb b/spec/unit/provider/service/solaris_smf_service_spec.rb
new file mode 100644
index 0000000000..3ea2902755
--- /dev/null
+++ b/spec/unit/provider/service/solaris_smf_service_spec.rb
@@ -0,0 +1,140 @@
+#
+# Author:: Toomas Pelberg (<toomasp@gmx.net>)
+# Copyright:: Copyright (c) 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'
+
+describe Chef::Provider::Service::Solaris do
+ before(:each) do
+ @node =Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Service.new('chef')
+
+ @current_resource = Chef::Resource::Service.new('chef')
+
+ @provider = Chef::Provider::Service::Solaris.new(@new_resource, @run_context)
+ Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+
+ @stdin = StringIO.new
+ @stdout = StringIO.new
+ @stderr = StringIO.new
+ @pid = 2342
+ @stdout_string = "state disabled"
+ @stdout.stub!(:gets).and_return(@stdout_string)
+ end
+
+ it "should raise an error if /bin/svcs does not exist" do
+ File.should_receive(:exists?).with("/bin/svcs").and_return(false)
+ lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Service)
+ end
+
+ describe "on a host with /bin/svcs" do
+
+ before do
+ File.stub!(:exists?).with('/bin/svcs').and_return(true)
+ end
+
+ describe "when discovering the current service state" do
+ it "should create a current resource with the name of the new resource" do
+ @provider.stub!(:popen4).with("/bin/svcs -l chef").and_return(@status)
+ Chef::Resource::Service.should_receive(:new).and_return(@current_resource)
+ @provider.load_current_resource
+ end
+
+
+ it "should return the current resource" do
+ @provider.stub!(:popen4).with("/bin/svcs -l chef").and_return(@status)
+ @provider.load_current_resource.should eql(@current_resource)
+ end
+
+ it "should popen4 '/bin/svcs -l service_name'" do
+ @provider.should_receive(:popen4).with("/bin/svcs -l chef").and_return(@status)
+ @provider.load_current_resource
+ end
+
+ it "should mark service as not running" do
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @current_resource.should_receive(:running).with(false)
+ @provider.load_current_resource
+ end
+
+ it "should mark service as running" do
+ @stdout.stub!(:each).and_yield("state online")
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @current_resource.should_receive(:running).with(true)
+ @provider.load_current_resource
+ end
+ end
+
+ describe "when enabling the service" do
+ before(:each) do
+ #@provider = Chef::Provider::Service::Solaris.new(@node, @new_resource)
+ @provider.current_resource = @current_resource
+ @current_resource.enabled(true)
+ end
+
+ it "should call svcadm enable chef" do
+ @provider.should_receive(:run_command).with({:command => "/usr/sbin/svcadm enable chef"})
+ @provider.should_receive(:service_status).and_return(@current_resource)
+ @provider.enable_service.should be_true
+ end
+
+ it "should call svcadm enable chef for start_service" do
+ @provider.should_receive(:run_command).with({:command => "/usr/sbin/svcadm enable chef"})
+ @provider.should_receive(:service_status).and_return(@current_resource)
+ @provider.start_service.should be_true
+ end
+
+ end
+
+
+ describe "when disabling the service" do
+ before(:each) do
+ @provider.current_resource = @current_resource
+ @current_resource.enabled(false)
+ end
+
+ it "should call svcadm disable chef" do
+ @provider.should_receive(:run_command).with({:command => "/usr/sbin/svcadm disable chef"})
+ @provider.should_receive(:service_status).and_return(@current_resource)
+ @provider.disable_service.should be_false
+ end
+
+ it "should call svcadm disable chef for stop_service" do
+ @provider.should_receive(:run_command).with({:command => "/usr/sbin/svcadm disable chef"})
+ @provider.should_receive(:service_status).and_return(@current_resource)
+ @provider.stop_service.should be_false
+ end
+
+ end
+
+ describe "when reloading the service" do
+ before(:each) do
+ @status = mock("Process::Status", :exitstatus => 0)
+ @provider.current_resource = @current_resource
+ end
+
+ it "should call svcadm refresh chef" do
+ @provider.should_receive(:run_command).with({:command => "/usr/sbin/svcadm refresh chef"}).and_return(@status)
+ @provider.reload_service.should be_true
+ end
+
+ end
+ end
+end
diff --git a/spec/unit/provider/service/systemd_service_spec.rb b/spec/unit/provider/service/systemd_service_spec.rb
new file mode 100644
index 0000000000..dbdedecd40
--- /dev/null
+++ b/spec/unit/provider/service/systemd_service_spec.rb
@@ -0,0 +1,239 @@
+#
+# Author:: Stephen Haynes (<sh@nomitor.com>)
+# Copyright:: Copyright (c) 2011 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'
+
+describe Chef::Provider::Service::Systemd do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Service.new('rsyslog.service')
+ @provider = Chef::Provider::Service::Systemd.new(@new_resource, @run_context)
+ end
+
+ describe "load_current_resource" do
+ before(:each) do
+ @current_resource = Chef::Resource::Service.new('rsyslog.service')
+ Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+
+ @provider.stub!(:is_active?).and_return(false)
+ @provider.stub!(:is_enabled?).and_return(false)
+ end
+
+ it "should create a current resource with the name of the new resource" do
+ Chef::Resource::Service.should_receive(:new).and_return(@current_resource)
+ @provider.load_current_resource
+ end
+
+ it "should set the current resources service name to the new resources service name" do
+ @current_resource.should_receive(:service_name).with(@new_resource.service_name)
+ @provider.load_current_resource
+ end
+
+ it "should check if the service is running" do
+ @provider.should_receive(:is_active?)
+ @provider.load_current_resource
+ end
+
+ it "should set running to true if the service is running" do
+ @provider.stub!(:is_active?).and_return(true)
+ @current_resource.should_receive(:running).with(true)
+ @provider.load_current_resource
+ end
+
+ it "should set running to false if the service is not running" do
+ @provider.stub!(:is_active?).and_return(false)
+ @current_resource.should_receive(:running).with(false)
+ @provider.load_current_resource
+ end
+
+ describe "when a status command has been specified" do
+ before do
+ @new_resource.stub!(:status_command).and_return("/bin/chefhasmonkeypants status")
+ end
+
+ it "should run the services status command if one has been specified" do
+ @provider.stub!(:run_command_with_systems_locale).with({:command => "/bin/chefhasmonkeypants status"}).and_return(0)
+ @current_resource.should_receive(:running).with(true)
+ @provider.load_current_resource
+ end
+
+ it "should run the services status command if one has been specified and properly set status check state" do
+ @provider.stub!(:run_command_with_systems_locale).with({:command => "/bin/chefhasmonkeypants status"}).and_return(0)
+ @provider.load_current_resource
+ @provider.instance_variable_get("@status_check_success").should be_true
+ end
+
+ it "should set running to false if it catches a Chef::Exceptions::Exec when using a status command" do
+ @provider.stub!(:run_command_with_systems_locale).and_raise(Chef::Exceptions::Exec)
+ @current_resource.should_receive(:running).with(false)
+ @provider.load_current_resource
+ end
+
+ it "should update state to indicate status check failed when an exception is thrown using a status command" do
+ @provider.stub!(:run_command_with_systems_locale).and_raise(Chef::Exceptions::Exec)
+ @provider.load_current_resource
+ @provider.instance_variable_get("@status_check_success").should be_false
+ end
+ end
+
+ it "should check if the service is enabled" do
+ @provider.should_receive(:is_enabled?)
+ @provider.load_current_resource
+ end
+
+ it "should set enabled to true if the service is enabled" do
+ @provider.stub!(:is_enabled?).and_return(true)
+ @current_resource.should_receive(:enabled).with(true)
+ @provider.load_current_resource
+ end
+
+ it "should set enabled to false if the service is not enabled" do
+ @provider.stub!(:is_enabled?).and_return(false)
+ @current_resource.should_receive(:enabled).with(false)
+ @provider.load_current_resource
+ end
+
+ it "should return the current resource" do
+ @provider.load_current_resource.should eql(@current_resource)
+ end
+ end
+
+ describe "start and stop service" do
+ before(:each) do
+ @current_resource = Chef::Resource::Service.new('rsyslog.service')
+ Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+ @provider.current_resource = @current_resource
+ end
+
+ it "should call the start command if one is specified" do
+ @new_resource.stub!(:start_command).and_return("/sbin/rsyslog startyousillysally")
+ @provider.should_receive(:shell_out!).with("/sbin/rsyslog startyousillysally")
+ @provider.start_service
+ end
+
+ it "should call '/bin/systemctl start service_name' if no start command is specified" do
+ @provider.should_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl start #{@new_resource.service_name}"}).and_return(0)
+ @provider.start_service
+ end
+
+ it "should not call '/bin/systemctl start service_name' if it is already running" do
+ @current_resource.stub!(:running).and_return(true)
+ @provider.should_not_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl start #{@new_resource.service_name}"}).and_return(0)
+ @provider.start_service
+ end
+
+ it "should call the restart command if one is specified" do
+ @current_resource.stub!(:running).and_return(true)
+ @new_resource.stub!(:restart_command).and_return("/sbin/rsyslog restartyousillysally")
+ @provider.should_receive(:shell_out!).with("/sbin/rsyslog restartyousillysally")
+ @provider.restart_service
+ end
+
+ it "should call '/bin/systemctl restart service_name' if no restart command is specified" do
+ @current_resource.stub!(:running).and_return(true)
+ @provider.should_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl restart #{@new_resource.service_name}"}).and_return(0)
+ @provider.restart_service
+ end
+
+ it "should call the reload command if one is specified" do
+ @current_resource.stub!(:running).and_return(true)
+ @new_resource.stub!(:reload_command).and_return("/sbin/rsyslog reloadyousillysally")
+ @provider.should_receive(:shell_out!).with("/sbin/rsyslog reloadyousillysally")
+ @provider.reload_service
+ end
+
+ it "should call '/bin/systemctl reload service_name' if no reload command is specified" do
+ @current_resource.stub!(:running).and_return(true)
+ @provider.should_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl reload #{@new_resource.service_name}"}).and_return(0)
+ @provider.reload_service
+ end
+
+ it "should call the stop command if one is specified" do
+ @current_resource.stub!(:running).and_return(true)
+ @new_resource.stub!(:stop_command).and_return("/sbin/rsyslog stopyousillysally")
+ @provider.should_receive(:shell_out!).with("/sbin/rsyslog stopyousillysally")
+ @provider.stop_service
+ end
+
+ it "should call '/bin/systemctl stop service_name' if no stop command is specified" do
+ @current_resource.stub!(:running).and_return(true)
+ @provider.should_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl stop #{@new_resource.service_name}"}).and_return(0)
+ @provider.stop_service
+ end
+
+ it "should not call '/bin/systemctl stop service_name' if it is already stopped" do
+ @current_resource.stub!(:running).and_return(false)
+ @provider.should_not_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl stop #{@new_resource.service_name}"}).and_return(0)
+ @provider.stop_service
+ end
+ end
+
+ describe "enable and disable service" do
+ before(:each) do
+ @current_resource = Chef::Resource::Service.new('rsyslog.service')
+ Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+ @provider.current_resource = @current_resource
+ end
+
+ it "should call '/bin/systemctl enable service_name' to enable the service" do
+ @provider.should_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl enable #{@new_resource.service_name}"}).and_return(0)
+ @provider.enable_service
+ end
+
+ it "should call '/bin/systemctl disable service_name' to disable the service" do
+ @provider.should_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl disable #{@new_resource.service_name}"}).and_return(0)
+ @provider.disable_service
+ end
+ end
+
+ describe "is_active?" do
+ before(:each) do
+ @current_resource = Chef::Resource::Service.new('rsyslog.service')
+ Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+ end
+
+ it "should return true if '/bin/systemctl is-active service_name' returns 0" do
+ @provider.should_receive(:run_command_with_systems_locale).with({:command => '/bin/systemctl is-active rsyslog.service', :ignore_failure => true}).and_return(0)
+ @provider.is_active?.should be_true
+ end
+
+ it "should return false if '/bin/systemctl is-active service_name' returns anything except 0" do
+ @provider.should_receive(:run_command_with_systems_locale).with({:command => '/bin/systemctl is-active rsyslog.service', :ignore_failure => true}).and_return(1)
+ @provider.is_active?.should be_false
+ end
+ end
+
+ describe "is_enabled?" do
+ before(:each) do
+ @current_resource = Chef::Resource::Service.new('rsyslog.service')
+ Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+ end
+
+ it "should return true if '/bin/systemctl is-enabled service_name' returns 0" do
+ @provider.should_receive(:run_command_with_systems_locale).with({:command => '/bin/systemctl is-enabled rsyslog.service', :ignore_failure => true}).and_return(0)
+ @provider.is_enabled?.should be_true
+ end
+
+ it "should return false if '/bin/systemctl is-enabled service_name' returns anything except 0" do
+ @provider.should_receive(:run_command_with_systems_locale).with({:command => '/bin/systemctl is-enabled rsyslog.service', :ignore_failure => true}).and_return(1)
+ @provider.is_enabled?.should be_false
+ end
+ end
+end
diff --git a/spec/unit/provider/service/upstart_service_spec.rb b/spec/unit/provider/service/upstart_service_spec.rb
new file mode 100644
index 0000000000..2fc49c7aa2
--- /dev/null
+++ b/spec/unit/provider/service/upstart_service_spec.rb
@@ -0,0 +1,314 @@
+#
+# Author:: Bryan McLellan (btm@loftninjas.org)
+# Copyright:: Copyright (c) 2010 Bryan McLellan
+# 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::Service::Upstart do
+ before(:each) do
+ @node =Chef::Node.new
+ @node.name('upstarter')
+ @node.automatic_attrs[:platform] = 'ubuntu'
+ @node.automatic_attrs[:platform_version] = '9.10'
+
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Service.new("rsyslog")
+ @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context)
+ end
+
+ describe "when first created" do
+ before do
+ @platform = nil
+ end
+
+ it "should return /etc/event.d as the upstart job directory when running on Ubuntu 9.04" do
+ @node.automatic_attrs[:platform_version] = '9.04'
+ #Chef::Platform.stub!(:find_platform_and_version).and_return([ "ubuntu", "9.04" ])
+ @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context)
+ @provider.instance_variable_get(:@upstart_job_dir).should == "/etc/event.d"
+ @provider.instance_variable_get(:@upstart_conf_suffix).should == ""
+ end
+
+ it "should return /etc/init as the upstart job directory when running on Ubuntu 9.10" do
+ @node.automatic_attrs[:platform_version] = '9.10'
+ @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context)
+ @provider.instance_variable_get(:@upstart_job_dir).should == "/etc/init"
+ @provider.instance_variable_get(:@upstart_conf_suffix).should == ".conf"
+ end
+
+ it "should return /etc/init as the upstart job directory by default" do
+ @node.automatic_attrs[:platform_version] = '9000'
+ @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context)
+ @provider.instance_variable_get(:@upstart_job_dir).should == "/etc/init"
+ @provider.instance_variable_get(:@upstart_conf_suffix).should == ".conf"
+ end
+ end
+
+ describe "load_current_resource" do
+ before(:each) do
+ @node.automatic_attrs[:command] = {:ps => "ps -ax"}
+
+ @current_resource = Chef::Resource::Service.new("rsyslog")
+ Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+
+ @status = mock("Status", :exitstatus => 0)
+ @provider.stub!(:popen4).and_return(@status)
+ @stdin = StringIO.new
+ @stdout = StringIO.new
+ @stderr = StringIO.new
+ @pid = mock("PID")
+
+ ::File.stub!(:exists?).and_return(true)
+ ::File.stub!(:open).and_return(true)
+ end
+
+ it "should create a current resource with the name of the new resource" do
+ Chef::Resource::Service.should_receive(:new).and_return(@current_resource)
+ @provider.load_current_resource
+ end
+
+ it "should set the current resources service name to the new resources service name" do
+ @current_resource.should_receive(:service_name).with(@new_resource.service_name)
+ @provider.load_current_resource
+ end
+
+ it "should run '/sbin/status rsyslog'" do
+ @provider.should_receive(:popen4).with("/sbin/status rsyslog").and_return(@status)
+ @provider.load_current_resource
+ end
+
+ describe "when the status command uses the new format" do
+ before do
+ end
+
+ it "should set running to true if the the status command returns 0" do
+ @stdout = StringIO.new("rsyslog start/running")
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.load_current_resource
+ @current_resource.running.should be_true
+ end
+
+ it "should set running to false if the status command returns anything except 0" do
+ @stdout = StringIO.new("rsyslog stop/waiting")
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.load_current_resource
+ @current_resource.running.should be_false
+ end
+ end
+
+ describe "when the status command uses the old format" do
+ it "should set running to true if the the status command returns 0" do
+ @stdout = StringIO.new("rsyslog (start) running, process 32225")
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.load_current_resource
+ @current_resource.running.should be_true
+ end
+
+ it "should set running to false if the status command returns anything except 0" do
+ @stdout = StringIO.new("rsyslog (stop) waiting")
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.load_current_resource
+ @current_resource.running.should be_false
+ end
+ end
+
+ it "should set running to false if it catches a Chef::Exceptions::Exec" do
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_raise(Chef::Exceptions::Exec)
+ @current_resource.should_receive(:running).with(false)
+ @provider.load_current_resource
+ end
+
+ it "should set enabled to true when it finds 'starts on'" do
+ @lines = mock("start on filesystem", :gets => "start on filesystem")
+ ::File.stub!(:open).and_yield(@lines)
+ @current_resource.should_receive(:running).with(false)
+ @provider.load_current_resource
+ end
+
+ it "should set enabled to false when it finds '#starts on'" do
+ @lines = mock("start on filesystem", :gets => "#start on filesystem")
+ ::File.stub!(:open).and_yield(@lines)
+ @current_resource.should_receive(:running).with(false)
+ @provider.load_current_resource
+ end
+
+ it "should assume disable when no job configuration file is found" do
+ ::File.stub!(:exists?).and_return(false)
+ @current_resource.should_receive(:running).with(false)
+ @provider.load_current_resource
+ end
+
+
+ it "should track state when the upstart configuration file fails to load" do
+ File.should_receive(:exists?).and_return false
+ @provider.load_current_resource
+ @provider.instance_variable_get("@config_file_found").should == false
+ end
+
+ describe "when a status command has been specified" do
+ before do
+ @new_resource.stub!(:status_command).and_return("/bin/chefhasmonkeypants status")
+ end
+
+ it "should run the services status command if one has been specified" do
+ @provider.stub!(:run_command_with_systems_locale).with({:command => "/bin/chefhasmonkeypants status"}).and_return(0)
+ @current_resource.should_receive(:running).with(true)
+ @provider.load_current_resource
+ end
+
+ it "should track state when the user-provided status command fails" do
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_raise(Chef::Exceptions::Exec)
+ @provider.load_current_resource
+ @provider.instance_variable_get("@command_success").should == false
+ end
+
+ it "should set running to false if it catches a Chef::Exceptions::Exec when using a status command" do
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_raise(Chef::Exceptions::Exec)
+ @current_resource.should_receive(:running).with(false)
+ @provider.load_current_resource
+ end
+ end
+
+ it "should track state when we fail to obtain service status via upstart_state" do
+ @provider.should_receive(:upstart_state).and_raise Chef::Exceptions::Exec
+ @provider.load_current_resource
+ @provider.instance_variable_get("@command_success").should == false
+ end
+
+ it "should return the current resource" do
+ @provider.load_current_resource.should eql(@current_resource)
+ end
+
+
+ end
+
+ describe "enable and disable service" do
+ before(:each) do
+ @current_resource = Chef::Resource::Service.new('rsyslog')
+ Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+ @provider.current_resource = @current_resource
+ Chef::Util::FileEdit.stub!(:new)
+ end
+
+ it "should enable the service if it is not enabled" do
+ @file = Object.new
+ Chef::Util::FileEdit.stub!(:new).and_return(@file)
+ @current_resource.stub!(:enabled).and_return(false)
+ @file.should_receive(:search_file_replace)
+ @file.should_receive(:write_file)
+ @provider.enable_service()
+ end
+
+ it "should disable the service if it is enabled" do
+ @file = Object.new
+ Chef::Util::FileEdit.stub!(:new).and_return(@file)
+ @current_resource.stub!(:enabled).and_return(true)
+ @file.should_receive(:search_file_replace)
+ @file.should_receive(:write_file)
+ @provider.disable_service()
+ end
+
+ end
+
+ describe "start and stop service" do
+ before(:each) do
+ @current_resource = Chef::Resource::Service.new('rsyslog')
+
+ Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+ @provider.current_resource = @current_resource
+ end
+
+ it "should call the start command if one is specified" do
+ @new_resource.stub!(:start_command).and_return("/sbin/rsyslog startyousillysally")
+ @provider.should_receive(:shell_out!).with("/sbin/rsyslog startyousillysally")
+ @provider.start_service()
+ end
+
+ it "should call '/sbin/start service_name' if no start command is specified" do
+ @provider.should_receive(:run_command_with_systems_locale).with({:command => "/sbin/start #{@new_resource.service_name}"}).and_return(0)
+ @provider.start_service()
+ end
+
+ it "should not call '/sbin/start service_name' if it is already running" do
+ @current_resource.stub!(:running).and_return(true)
+ @provider.should_not_receive(:run_command_with_systems_locale).with({:command => "/sbin/start #{@new_resource.service_name}"}).and_return(0)
+ @provider.start_service()
+ end
+
+ it "should pass parameters to the start command if they are provided" do
+ @new_resource = Chef::Resource::Service.new("rsyslog")
+ @new_resource.parameters({ "OSD_ID" => "2" })
+ @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ @provider.should_receive(:run_command_with_systems_locale).with({:command => "/sbin/start rsyslog OSD_ID=2"}).and_return(0)
+ @provider.start_service()
+ end
+
+ it "should call the restart command if one is specified" do
+ @current_resource.stub!(:running).and_return(true)
+ @new_resource.stub!(:restart_command).and_return("/sbin/rsyslog restartyousillysally")
+ @provider.should_receive(:shell_out!).with("/sbin/rsyslog restartyousillysally")
+ @provider.restart_service()
+ end
+
+ it "should call '/sbin/restart service_name' if no restart command is specified" do
+ @current_resource.stub!(:running).and_return(true)
+ @provider.should_receive(:run_command_with_systems_locale).with({:command => "/sbin/restart #{@new_resource.service_name}"}).and_return(0)
+ @provider.restart_service()
+ end
+
+ it "should call '/sbin/start service_name' if restart_service is called for a stopped service" do
+ @current_resource.stub!(:running).and_return(false)
+ @provider.should_receive(:run_command_with_systems_locale).with({:command => "/sbin/start #{@new_resource.service_name}"}).and_return(0)
+ @provider.restart_service()
+ end
+
+ it "should call the reload command if one is specified" do
+ @current_resource.stub!(:running).and_return(true)
+ @new_resource.stub!(:reload_command).and_return("/sbin/rsyslog reloadyousillysally")
+ @provider.should_receive(:shell_out!).with("/sbin/rsyslog reloadyousillysally")
+ @provider.reload_service()
+ end
+
+ it "should call '/sbin/reload service_name' if no reload command is specified" do
+ @current_resource.stub!(:running).and_return(true)
+ @provider.should_receive(:run_command_with_systems_locale).with({:command => "/sbin/reload #{@new_resource.service_name}"}).and_return(0)
+ @provider.reload_service()
+ end
+
+ it "should call the stop command if one is specified" do
+ @current_resource.stub!(:running).and_return(true)
+ @new_resource.stub!(:stop_command).and_return("/sbin/rsyslog stopyousillysally")
+ @provider.should_receive(:shell_out!).with("/sbin/rsyslog stopyousillysally")
+ @provider.stop_service()
+ end
+
+ it "should call '/sbin/stop service_name' if no stop command is specified" do
+ @current_resource.stub!(:running).and_return(true)
+ @provider.should_receive(:run_command_with_systems_locale).with({:command => "/sbin/stop #{@new_resource.service_name}"}).and_return(0)
+ @provider.stop_service()
+ end
+
+ it "should not call '/sbin/stop service_name' if it is already stopped" do
+ @current_resource.stub!(:running).and_return(false)
+ @provider.should_not_receive(:run_command_with_systems_locale).with({:command => "/sbin/stop #{@new_resource.service_name}"}).and_return(0)
+ @provider.stop_service()
+ end
+ end
+end
diff --git a/spec/unit/provider/service/windows_spec.rb b/spec/unit/provider/service/windows_spec.rb
new file mode 100644
index 0000000000..a68e798d36
--- /dev/null
+++ b/spec/unit/provider/service/windows_spec.rb
@@ -0,0 +1,235 @@
+#
+# Author:: Nuo Yan <nuo@opscode.com>
+# Author:: Seth Chisamore <schisamo@opscode.com>
+# Copyright:: Copyright (c) 2010-2011 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'
+
+describe Chef::Provider::Service::Windows, "load_current_resource" do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Service.new("chef")
+ @provider = Chef::Provider::Service::Windows.new(@new_resource, @run_context)
+ Object.send(:remove_const, 'Win32') if defined?(Win32)
+ Win32 = Module.new
+ Win32::Service = Class.new
+ Win32::Service::AUTO_START = 0x00000002
+ Win32::Service::DISABLED = 0x00000004
+ Win32::Service.stub!(:status).with(@new_resource.service_name).and_return(
+ mock("StatusStruct", :current_state => "running"))
+ Win32::Service.stub!(:config_info).with(@new_resource.service_name).and_return(
+ mock("ConfigStruct", :start_type => "auto start"))
+ Win32::Service.stub!(:exists?).and_return(true)
+ end
+
+ it "should set the current resources service name to the new resources service name" do
+ @provider.load_current_resource
+ @provider.current_resource.service_name.should == 'chef'
+ end
+
+ it "should return the current resource" do
+ @provider.load_current_resource.should equal(@provider.current_resource)
+ end
+
+ it "should set the current resources status" do
+ @provider.load_current_resource
+ @provider.current_resource.running.should be_true
+ end
+
+ it "should set the current resources start type" do
+ @provider.load_current_resource
+ @provider.current_resource.enabled.should be_true
+ end
+
+ describe Chef::Provider::Service::Windows, "start_service" do
+ before(:each) do
+ Win32::Service.stub!(:status).with(@new_resource.service_name).and_return(
+ mock("StatusStruct", :current_state => "stopped"),
+ mock("StatusStruct", :current_state => "running"))
+ end
+
+ it "should call the start command if one is specified" do
+ @new_resource.start_command "sc start chef"
+ @provider.should_receive(:shell_out!).with("#{@new_resource.start_command}").and_return("Starting custom service")
+ @provider.start_service
+ @new_resource.updated_by_last_action?.should be_true
+ end
+
+ it "should use the built-in command if no start command is specified" do
+ Win32::Service.should_receive(:start).with(@new_resource.service_name)
+ @provider.start_service
+ @new_resource.updated_by_last_action?.should be_true
+ end
+
+ it "should do nothing if the service does not exist" do
+ Win32::Service.stub!(:exists?).with(@new_resource.service_name).and_return(false)
+ Win32::Service.should_not_receive(:start).with(@new_resource.service_name)
+ @provider.start_service
+ @new_resource.updated_by_last_action?.should be_false
+ end
+
+ it "should do nothing if the service is running" do
+ Win32::Service.stub!(:status).with(@new_resource.service_name).and_return(
+ mock("StatusStruct", :current_state => "running"))
+ @provider.load_current_resource
+ Win32::Service.should_not_receive(:start).with(@new_resource.service_name)
+ @provider.start_service
+ @new_resource.updated_by_last_action?.should be_false
+ end
+ end
+
+ describe Chef::Provider::Service::Windows, "stop_service" do
+
+ before(:each) do
+ Win32::Service.stub!(:status).with(@new_resource.service_name).and_return(
+ mock("StatusStruct", :current_state => "running"),
+ mock("StatusStruct", :current_state => "stopped"))
+ end
+
+ it "should call the stop command if one is specified" do
+ @new_resource.stop_command "sc stop chef"
+ @provider.should_receive(:shell_out!).with("#{@new_resource.stop_command}").and_return("Stopping custom service")
+ @provider.stop_service
+ @new_resource.updated_by_last_action?.should be_true
+ end
+
+ it "should use the built-in command if no stop command is specified" do
+ Win32::Service.should_receive(:stop).with(@new_resource.service_name)
+ @provider.stop_service
+ @new_resource.updated_by_last_action?.should be_true
+ end
+
+ it "should do nothing if the service does not exist" do
+ Win32::Service.stub!(:exists?).with(@new_resource.service_name).and_return(false)
+ Win32::Service.should_not_receive(:stop).with(@new_resource.service_name)
+ @provider.stop_service
+ @new_resource.updated_by_last_action?.should be_false
+ end
+
+ it "should do nothing if the service is stopped" do
+ Win32::Service.stub!(:status).with(@new_resource.service_name).and_return(
+ mock("StatusStruct", :current_state => "stopped"))
+ @provider.load_current_resource
+ Win32::Service.should_not_receive(:stop).with(@new_resource.service_name)
+ @provider.stop_service
+ @new_resource.updated_by_last_action?.should be_false
+ end
+ end
+
+ describe Chef::Provider::Service::Windows, "restart_service" do
+
+ it "should call the restart command if one is specified" do
+ @new_resource.restart_command "sc restart"
+ @provider.should_receive(:shell_out!).with("#{@new_resource.restart_command}")
+ @provider.restart_service
+ @new_resource.updated_by_last_action?.should be_true
+ end
+
+ it "should stop then start the service if it is running" do
+ Win32::Service.stub!(:status).with(@new_resource.service_name).and_return(
+ mock("StatusStruct", :current_state => "running"),
+ mock("StatusStruct", :current_state => "stopped"),
+ mock("StatusStruct", :current_state => "stopped"),
+ mock("StatusStruct", :current_state => "running"))
+ Win32::Service.should_receive(:stop).with(@new_resource.service_name)
+ Win32::Service.should_receive(:start).with(@new_resource.service_name)
+ @provider.restart_service
+ @new_resource.updated_by_last_action?.should be_true
+ end
+
+ it "should just start the service if it is stopped" do
+ Win32::Service.stub!(:status).with(@new_resource.service_name).and_return(
+ mock("StatusStruct", :current_state => "stopped"),
+ mock("StatusStruct", :current_state => "stopped"),
+ mock("StatusStruct", :current_state => "running"))
+ Win32::Service.should_receive(:start).with(@new_resource.service_name)
+ @provider.restart_service
+ @new_resource.updated_by_last_action?.should be_true
+ end
+
+ it "should do nothing if the service does not exist" do
+ Win32::Service.stub!(:exists?).with(@new_resource.service_name).and_return(false)
+ Win32::Service.should_not_receive(:stop).with(@new_resource.service_name)
+ Win32::Service.should_not_receive(:start).with(@new_resource.service_name)
+ @provider.restart_service
+ @new_resource.updated_by_last_action?.should be_false
+ end
+
+ end
+
+ describe Chef::Provider::Service::Windows, "enable_service" do
+
+ before(:each) do
+ Win32::Service.stub!(:config_info).with(@new_resource.service_name).and_return(
+ mock("ConfigStruct", :start_type => "disabled"))
+ end
+
+ it "should enable service" do
+ Win32::Service.should_receive(:configure).with(:service_name => @new_resource.service_name, :start_type => Win32::Service::AUTO_START)
+ @provider.enable_service
+ @new_resource.updated_by_last_action?.should be_true
+ end
+
+ it "should do nothing if the service does not exist" do
+ Win32::Service.stub!(:exists?).with(@new_resource.service_name).and_return(false)
+ Win32::Service.should_not_receive(:configure)
+ @provider.enable_service
+ @new_resource.updated_by_last_action?.should be_false
+ end
+
+ it "should do nothing if the service is enabled" do
+ Win32::Service.stub!(:config_info).with(@new_resource.service_name).and_return(
+ mock("ConfigStruct", :start_type => "auto start"))
+ Win32::Service.should_not_receive(:configure)
+ @provider.enable_service
+ @new_resource.updated_by_last_action?.should be_false
+ end
+ end
+
+ describe Chef::Provider::Service::Windows, "disable_service" do
+
+ before(:each) do
+ Win32::Service.stub!(:config_info).with(@new_resource.service_name).and_return(
+ mock("ConfigStruct", :start_type => "auto start"))
+ end
+
+ it "should disable service" do
+ Win32::Service.should_receive(:configure).with(:service_name => @new_resource.service_name, :start_type => Win32::Service::DISABLED)
+ @provider.disable_service
+ @new_resource.updated_by_last_action?.should be_true
+ end
+
+ it "should do nothing if the service does not exist" do
+ Win32::Service.stub!(:exists?).with(@new_resource.service_name).and_return(false)
+ Win32::Service.should_not_receive(:configure)
+ @provider.disable_service
+ @new_resource.updated_by_last_action?.should be_false
+ end
+
+ it "should do nothing if the service is disabled" do
+ Win32::Service.stub!(:config_info).with(@new_resource.service_name).and_return(
+ mock("ConfigStruct", :start_type => "disabled"))
+ @provider.load_current_resource
+ Win32::Service.should_not_receive(:configure)
+ @provider.disable_service
+ @new_resource.updated_by_last_action?.should be_false
+ end
+
+ end
+end
diff --git a/spec/unit/provider/service_spec.rb b/spec/unit/provider/service_spec.rb
new file mode 100644
index 0000000000..d25d7cf735
--- /dev/null
+++ b/spec/unit/provider/service_spec.rb
@@ -0,0 +1,169 @@
+#
+# Author:: AJ Christensen (<aj@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::Service do
+ before do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::Service.new("chef")
+ @current_resource = Chef::Resource::Service.new("chef")
+
+ @provider = Chef::Provider::Service.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ @provider.stub!(:load_current_resource)
+ end
+
+ describe "when enabling the service" do
+ it "should enable the service if disabled and set the resource as updated" do
+ @current_resource.enabled(false)
+ @provider.should_receive(:enable_service).and_return(true)
+ @provider.action_enable
+ @provider.converge
+ @provider.new_resource.should be_updated
+ end
+
+ it "should not enable the service if already enabled" do
+ @current_resource.enabled(true)
+ @provider.should_not_receive(:enable_service)
+ @provider.action_enable
+ @provider.converge
+ @provider.new_resource.should_not be_updated
+ end
+ end
+
+
+ describe "when disabling the service" do
+ it "should disable the service if enabled and set the resource as updated" do
+ @current_resource.stub!(:enabled).and_return(true)
+ @provider.should_receive(:disable_service).and_return(true)
+ @provider.run_action(:disable)
+ @provider.new_resource.should be_updated
+ end
+
+ it "should not disable the service if already disabled" do
+ @current_resource.stub!(:enabled).and_return(false)
+ @provider.should_not_receive(:disable_service).and_return(true)
+ @provider.run_action(:disable)
+ @provider.new_resource.should_not be_updated
+ end
+ end
+
+ describe "action_start" do
+ it "should start the service if it isn't running and set the resource as updated" do
+ @current_resource.running(false)
+ @provider.should_receive(:start_service).with.and_return(true)
+ @provider.run_action(:start)
+ @provider.new_resource.should be_updated
+ end
+
+ it "should not start the service if already running" do
+ @current_resource.running(true)
+ @provider.should_not_receive(:start_service)
+ @provider.run_action(:start)
+ @provider.new_resource.should_not be_updated
+ end
+ end
+
+ describe "action_stop" do
+ it "should stop the service if it is running and set the resource as updated" do
+ @current_resource.stub!(:running).and_return(true)
+ @provider.should_receive(:stop_service).and_return(true)
+ @provider.run_action(:stop)
+ @provider.new_resource.should be_updated
+ end
+
+ it "should not stop the service if it's already stopped" do
+ @current_resource.stub!(:running).and_return(false)
+ @provider.should_not_receive(:stop_service).and_return(true)
+ @provider.run_action(:stop)
+ @provider.new_resource.should_not be_updated
+ end
+ end
+
+ describe "action_restart" do
+ before do
+ @current_resource.supports(:restart => true)
+ end
+
+ it "should restart the service if it's supported and set the resource as updated" do
+ @provider.should_receive(:restart_service).and_return(true)
+ @provider.run_action(:restart)
+ @provider.new_resource.should be_updated
+ end
+
+ it "should restart the service even if it isn't running and set the resource as updated" do
+ @current_resource.stub!(:running).and_return(false)
+ @provider.should_receive(:restart_service).and_return(true)
+ @provider.run_action(:restart)
+ @provider.new_resource.should be_updated
+ end
+ end
+
+ describe "action_reload" do
+ before do
+ @new_resource.supports(:reload => true)
+ end
+
+ it "should raise an exception if reload isn't supported" do
+ @new_resource.supports(:reload => false)
+ @new_resource.stub!(:reload_command).and_return(false)
+ lambda { @provider.run_action(:reload) }.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+
+ it "should reload the service if it is running and set the resource as updated" do
+ @current_resource.stub!(:running).and_return(true)
+ @provider.should_receive(:reload_service).and_return(true)
+ @provider.run_action(:reload)
+ @provider.new_resource.should be_updated
+ end
+
+ it "should not reload the service if it's stopped" do
+ @current_resource.stub!(:running).and_return(false)
+ @provider.should_not_receive(:reload_service).and_return(true)
+ @provider.run_action(:stop)
+ @provider.new_resource.should_not be_updated
+ end
+ end
+
+ it "delegates enable_service to subclasses" do
+ lambda { @provider.enable_service }.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+
+ it "delegates disable_service to subclasses" do
+ lambda { @provider.disable_service }.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+
+ it "delegates start_service to subclasses" do
+ lambda { @provider.start_service }.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+
+ it "delegates stop_service to subclasses" do
+ lambda { @provider.stop_service }.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+
+ it "delegates restart_service to subclasses" do
+ lambda { @provider.restart_service }.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+
+ it "delegates reload_service to subclasses" do
+ lambda { @provider.reload_service }.should raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+end
diff --git a/spec/unit/provider/subversion_spec.rb b/spec/unit/provider/subversion_spec.rb
new file mode 100644
index 0000000000..e441428d6c
--- /dev/null
+++ b/spec/unit/provider/subversion_spec.rb
@@ -0,0 +1,281 @@
+#
+# Author:: Daniel DeLeo (<dan@kallistec.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::Subversion do
+
+ before do
+ @resource = Chef::Resource::Subversion.new("my app")
+ @resource.repository "http://svn.example.org/trunk/"
+ @resource.destination "/my/deploy/dir"
+ @resource.revision "12345"
+ @resource.svn_arguments(false)
+ @resource.svn_info_args(false)
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @provider = Chef::Provider::Subversion.new(@resource, @run_context)
+ end
+
+ it "converts resource attributes to options for run_command and popen4" do
+ @provider.run_options.should == {}
+ @resource.user 'deployninja'
+ @provider.run_options.should == {:user => "deployninja"}
+ end
+
+ context "determining the revision of the currently deployed code" do
+
+ before do
+ @stdout = mock("stdout")
+ @stderr = mock("stderr")
+ @exitstatus = mock("exitstatus")
+ end
+
+ it "sets the revision to nil if there isn't any deployed code yet" do
+ ::File.should_receive(:exist?).with("/my/deploy/dir/.svn").and_return(false)
+ @provider.find_current_revision.should be_nil
+ end
+
+ it "determines the current revision if there's a checkout with svn data available" do
+ example_svn_info = "Path: .\n" +
+ "URL: http://svn.example.org/trunk/myapp\n" +
+ "Repository Root: http://svn.example.org\n" +
+ "Repository UUID: d62ff500-7bbc-012c-85f1-0026b0e37c24\n" +
+ "Revision: 11739\nNode Kind: directory\n" +
+ "Schedule: normal\n" +
+ "Last Changed Author: codeninja\n" +
+ "Last Changed Rev: 11410\n" + # Last Changed Rev is preferred to Revision
+ "Last Changed Date: 2009-03-25 06:09:56 -0600 (Wed, 25 Mar 2009)\n\n"
+ ::File.should_receive(:exist?).with("/my/deploy/dir/.svn").and_return(true)
+ ::File.should_receive(:directory?).with("/my/deploy/dir").and_return(true)
+ ::Dir.should_receive(:chdir).with("/my/deploy/dir").and_yield
+ @stdout.stub!(:string).and_return(example_svn_info)
+ @stderr.stub!(:string).and_return("")
+ @exitstatus.stub!(:exitstatus).and_return(0)
+ expected_command = ["svn info", {:cwd=>"/my/deploy/dir"}]
+ @provider.should_receive(:popen4).with(*expected_command).
+ and_yield("no-pid", "no-stdin", @stdout,@stderr).
+ and_return(@exitstatus)
+ @provider.find_current_revision.should eql("11410")
+ end
+
+ it "gives nil as the current revision if the deploy dir isn't a SVN working copy" do
+ example_svn_info = "svn: '/tmp/deploydir' is not a working copy\n"
+ ::File.should_receive(:exist?).with("/my/deploy/dir/.svn").and_return(true)
+ ::File.should_receive(:directory?).with("/my/deploy/dir").and_return(true)
+ ::Dir.should_receive(:chdir).with("/my/deploy/dir").and_yield
+ @stdout.stub!(:string).and_return(example_svn_info)
+ @stderr.stub!(:string).and_return("")
+ @exitstatus.stub!(:exitstatus).and_return(1)
+ @provider.should_receive(:popen4).and_yield("no-pid", "no-stdin", @stdout,@stderr).
+ and_return(@exitstatus)
+ @provider.find_current_revision.should be_nil
+ end
+
+ it "finds the current revision when loading the current resource state" do
+ # note: the test is kinda janky, but it provides regression coverage for CHEF-2092
+ @resource.instance_variable_set(:@action, :sync)
+ @provider.should_receive(:find_current_revision).and_return("12345")
+ @provider.load_current_resource
+ @provider.current_resource.revision.should == "12345"
+ end
+ end
+
+ it "creates the current_resource object and sets its revision to the current deployment's revision as long as we're not exporting" do
+ @provider.stub!(:find_current_revision).and_return("11410")
+ @provider.new_resource.instance_variable_set :@action, [:checkout]
+ @provider.load_current_resource
+ @provider.current_resource.name.should eql(@resource.name)
+ @provider.current_resource.revision.should eql("11410")
+ end
+
+ context "resolving revisions to an integer" do
+
+ before do
+ @stdout = mock("stdout")
+ @stderr = mock("stderr")
+ @resource.svn_info_args "--no-auth-cache"
+ end
+
+ it "returns the revision number as is if it's already an integer" do
+ @provider.revision_int.should eql("12345")
+ end
+
+ it "queries the server and resolves the revision if it's not an integer (i.e. 'HEAD')" do
+ example_svn_info = "Path: .\n" +
+ "URL: http://svn.example.org/trunk/myapp\n" +
+ "Repository Root: http://svn.example.org\n" +
+ "Repository UUID: d62ff500-7bbc-012c-85f1-0026b0e37c24\n" +
+ "Revision: 11739\nNode Kind: directory\n" +
+ "Schedule: normal\n" +
+ "Last Changed Author: codeninja\n" +
+ "Last Changed Rev: 11410\n" + # Last Changed Rev is preferred to Revision
+ "Last Changed Date: 2009-03-25 06:09:56 -0600 (Wed, 25 Mar 2009)\n\n"
+ exitstatus = mock("exitstatus")
+ exitstatus.stub!(:exitstatus).and_return(0)
+ @resource.revision "HEAD"
+ @stdout.stub!(:string).and_return(example_svn_info)
+ @stderr.stub!(:string).and_return("")
+ expected_command = ["svn info http://svn.example.org/trunk/ --no-auth-cache -rHEAD", {:cwd=>Dir.tmpdir}]
+ @provider.should_receive(:popen4).with(*expected_command).
+ and_yield("no-pid","no-stdin",@stdout,@stderr).
+ and_return(exitstatus)
+ @provider.revision_int.should eql("11410")
+ end
+
+ it "returns a helpful message if data from `svn info` can't be parsed" do
+ example_svn_info = "some random text from an error message\n"
+ exitstatus = mock("exitstatus")
+ exitstatus.stub!(:exitstatus).and_return(0)
+ @resource.revision "HEAD"
+ @stdout.stub!(:string).and_return(example_svn_info)
+ @stderr.stub!(:string).and_return("")
+ @provider.should_receive(:popen4).and_yield("no-pid","no-stdin",@stdout,@stderr).
+ and_return(exitstatus)
+ lambda {@provider.revision_int}.should raise_error(RuntimeError, "Could not parse `svn info` data: some random text from an error message")
+
+ end
+
+ it "responds to :revision_slug as an alias for revision_sha" do
+ @provider.should respond_to(:revision_slug)
+ end
+
+ end
+
+ it "generates a checkout command with default options" do
+ @provider.checkout_command.should eql("svn checkout -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir")
+ end
+
+ it "generates a checkout command with authentication" do
+ @resource.svn_username "deployNinja"
+ @resource.svn_password "vanish!"
+ @provider.checkout_command.should eql("svn checkout -q --username deployNinja --password vanish! " +
+ "-r12345 http://svn.example.org/trunk/ /my/deploy/dir")
+ end
+
+ it "generates a checkout command with arbitrary options" do
+ @resource.svn_arguments "--no-auth-cache"
+ @provider.checkout_command.should eql("svn checkout --no-auth-cache -q -r12345 "+
+ "http://svn.example.org/trunk/ /my/deploy/dir")
+ end
+
+ it "generates a sync command with default options" do
+ @provider.sync_command.should eql("svn update -q -r12345 /my/deploy/dir")
+ end
+
+ it "generates an export command with default options" do
+ @provider.export_command.should eql("svn export --force -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir")
+ end
+
+ it "doesn't try to find the current revision when loading the resource if running an export" do
+ @provider.new_resource.instance_variable_set :@action, [:export]
+ @provider.should_not_receive(:find_current_revision)
+ @provider.load_current_resource
+ end
+
+ it "doesn't try to find the current revision when loading the resource if running a force export" do
+ @provider.new_resource.instance_variable_set :@action, [:force_export]
+ @provider.should_not_receive(:find_current_revision)
+ @provider.load_current_resource
+ end
+
+ it "runs an export with the --force option" do
+ ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+ expected_cmd = "svn export --force -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir"
+ @provider.should_receive(:run_command).with(:command => expected_cmd)
+ @provider.run_action(:force_export)
+ @resource.should be_updated
+ end
+
+ it "runs the checkout command for action_checkout" do
+ ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+ expected_cmd = "svn checkout -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir"
+ @provider.should_receive(:run_command).with(:command => expected_cmd)
+ @provider.run_action(:checkout)
+ @resource.should be_updated
+ end
+
+ it "raises an error if the svn checkout command would fail because the enclosing directory doesn't exist" do
+ lambda {@provider.run_action(:sync)}.should raise_error(Chef::Exceptions::MissingParentDirectory)
+ end
+
+ it "should not checkout if the destination exists or is a non empty directory" do
+ ::File.stub!(:exist?).with("/my/deploy/dir/.svn").and_return(false)
+ ::File.stub!(:exist?).with("/my/deploy/dir").and_return(true)
+ ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+ ::Dir.stub!(:entries).with("/my/deploy/dir").and_return(['.','..','foo','bar'])
+ @provider.should_not_receive(:checkout_command)
+ @provider.run_action(:checkout)
+ @resource.should_not be_updated
+ end
+
+ it "runs commands with the user and group specified in the resource" do
+ ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+ @resource.user "whois"
+ @resource.group "thisis"
+ expected_cmd = "svn checkout -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir"
+ @provider.should_receive(:run_command).with(:command => expected_cmd, :user => "whois", :group => "thisis")
+ @provider.run_action(:checkout)
+ @resource.should be_updated
+ end
+
+ it "does a checkout for action_sync if there's no deploy dir" do
+ ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+ ::File.should_receive(:exist?).with("/my/deploy/dir/.svn").twice.and_return(false)
+ @provider.should_receive(:action_checkout)
+ @provider.run_action(:sync)
+ end
+
+ it "does a checkout for action_sync if the deploy dir exists but is empty" do
+ ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+ ::File.should_receive(:exist?).with("/my/deploy/dir/.svn").twice.and_return(false)
+ @provider.should_receive(:action_checkout)
+ @provider.run_action(:sync)
+ end
+
+ it "runs the sync_command on action_sync if the deploy dir exists and isn't empty" do
+ ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+ ::File.should_receive(:exist?).with("/my/deploy/dir/.svn").and_return(true)
+ @provider.stub!(:find_current_revision).and_return("11410")
+ @provider.stub!(:current_revision_matches_target_revision?).and_return(false)
+ expected_cmd = "svn update -q -r12345 /my/deploy/dir"
+ @provider.should_receive(:run_command).with(:command => expected_cmd)
+ @provider.run_action(:sync)
+ @resource.should be_updated
+ end
+
+ it "does not fetch any updates if the remote revision matches the current revision" do
+ ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+ ::File.should_receive(:exist?).with("/my/deploy/dir/.svn").and_return(true)
+ @provider.stub!(:find_current_revision).and_return('12345')
+ @provider.stub!(:current_revision_matches_target_revision?).and_return(true)
+ @provider.run_action(:sync)
+ @resource.should_not be_updated
+ end
+
+ it "runs the export_command on action_export" do
+ ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+ expected_cmd = "svn export --force -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir"
+ @provider.should_receive(:run_command).with(:command => expected_cmd)
+ @provider.run_action(:export)
+ @resource.should be_updated
+ end
+
+end
diff --git a/spec/unit/provider/template_spec.rb b/spec/unit/provider/template_spec.rb
new file mode 100644
index 0000000000..6747876d61
--- /dev/null
+++ b/spec/unit/provider/template_spec.rb
@@ -0,0 +1,198 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 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 'stringio'
+require 'spec_helper'
+require 'etc'
+require 'ostruct'
+
+describe Chef::Provider::Template do
+ before(:each) do
+ @cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks"))
+ Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, @cookbook_repo) }
+
+ @node = Chef::Node.new
+ cl = Chef::CookbookLoader.new(@cookbook_repo)
+ cl.load_cookbooks
+ @cookbook_collection = Chef::CookbookCollection.new(cl)
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+
+ @rendered_file_location = Dir.tmpdir + '/openldap_stuff.conf'
+
+ @resource = Chef::Resource::Template.new(@rendered_file_location)
+ @resource.cookbook_name = 'openldap'
+
+ @provider = Chef::Provider::Template.new(@resource, @run_context)
+ @current_resource = @resource.dup
+ @provider.current_resource = @current_resource
+ @access_controls = mock("access controls")
+ @provider.stub!(:access_controls).and_return(@access_controls)
+ passwd_struct = if windows?
+ Struct::Passwd.new("root", "x", 0, 0, "/root", "/bin/bash")
+ else
+ Struct::Passwd.new("root", "x", 0, 0, "root", "/root", "/bin/bash")
+ end
+ group_struct = OpenStruct.new(:name => "root", :passwd => "x", :gid => 0)
+ Etc.stub!(:getpwuid).and_return(passwd_struct)
+ Etc.stub!(:getgrgid).and_return(group_struct)
+ end
+
+ describe "when creating the template" do
+
+ before do
+
+ end
+ after do
+ FileUtils.rm(@rendered_file_location) if ::File.exist?(@rendered_file_location)
+ end
+
+ it "finds the template file in the coobook cache if it isn't local" do
+ @provider.template_location.should == CHEF_SPEC_DATA + '/cookbooks/openldap/templates/default/openldap_stuff.conf.erb'
+ end
+
+ it "finds the template file locally if it is local" do
+ @resource.local(true)
+ @resource.source('/tmp/its_on_disk.erb')
+ @provider.template_location.should == '/tmp/its_on_disk.erb'
+ end
+
+ it "stops executing when the local template source can't be found" do
+ @access_controls.stub!(:requires_changes?).and_return(false)
+ @resource.source "invalid.erb"
+ @resource.local true
+ lambda { @provider.run_action(:create) } .should raise_error Chef::Mixin::WhyRun::ResourceRequirements::Assertion::AssertionFailure
+ end
+
+ it "should use the cookbook name if defined in the template resource" do
+ @resource.cookbook_name = 'apache2'
+ @resource.cookbook('openldap')
+ @resource.source "test.erb"
+ @provider.template_location.should == CHEF_SPEC_DATA + '/cookbooks/openldap/templates/default/test.erb'
+ end
+
+ describe "when the target file does not exist" do
+ it "creates the template with the rendered content" do
+ @access_controls.stub!(:requires_changes?).and_return(true)
+ @access_controls.should_receive(:set_all!)
+ @node.normal[:slappiness] = "a warm gun"
+ @provider.should_receive(:backup)
+ @provider.run_action(:create)
+ IO.read(@rendered_file_location).should == "slappiness is a warm gun"
+ @resource.should be_updated_by_last_action
+ end
+
+ it "should set the file access control as specified in the resource" do
+ @access_controls.stub!(:requires_changes?).and_return(false)
+ @access_controls.should_receive(:set_all!)
+ @resource.owner("adam")
+ @resource.group("wheel")
+ @resource.mode(00644)
+ @provider.run_action(:create)
+ @resource.should be_updated_by_last_action
+ end
+
+ it "creates the template with the rendered content for the create if missing action" do
+ @access_controls.stub!(:requires_changes?).and_return(true)
+ @access_controls.should_receive(:set_all!)
+ @node.normal[:slappiness] = "happiness"
+ @provider.should_receive(:backup)
+ @provider.run_action(:create_if_missing)
+ IO.read(@rendered_file_location).should == "slappiness is happiness"
+ @resource.should be_updated_by_last_action
+ end
+ end
+
+ describe "when the target file has the wrong content" do
+ before do
+ File.open(@rendered_file_location, "w+") { |f| f.print "blargh" }
+ end
+
+ it "overwrites the file with the updated content when the create action is run" do
+ @node.normal[:slappiness] = "a warm gun"
+ @access_controls.stub!(:requires_changes?).and_return(false)
+ @access_controls.should_receive(:set_all!)
+ @provider.should_receive(:backup)
+ @provider.run_action(:create)
+ IO.read(@rendered_file_location).should == "slappiness is a warm gun"
+ @resource.should be_updated_by_last_action
+ end
+
+ it "should set the file access control as specified in the resource" do
+ @access_controls.stub!(:requires_changes?).and_return(true)
+ @access_controls.should_receive(:set_all!)
+ @resource.owner("adam")
+ @resource.group("wheel")
+ @resource.mode(00644)
+ @provider.should_receive(:backup)
+ @provider.run_action(:create)
+ @resource.should be_updated_by_last_action
+ end
+
+ it "doesn't overwrite the file when the create if missing action is run" do
+ @access_controls.stub!(:requires_changes?).and_return(false)
+ @access_controls.should_not_receive(:set_all!)
+ @node.normal[:slappiness] = "a warm gun"
+ @provider.should_not_receive(:backup)
+ @provider.run_action(:create_if_missing)
+ IO.read(@rendered_file_location).should == "blargh"
+ @resource.should_not be_updated_by_last_action
+ end
+ end
+
+ describe "when the target has the correct content" do
+ before do
+ Chef::ChecksumCache.instance.reset!
+ File.open(@rendered_file_location, "w") { |f| f.print "slappiness is a warm gun" }
+ @current_resource.checksum('4ff94a87794ed9aefe88e734df5a66fc8727a179e9496cbd88e3b5ec762a5ee9')
+ @access_controls = mock("access controls")
+ @provider.stub!(:access_controls).and_return(@access_controls)
+ end
+
+ it "does not backup the original or overwrite it" do
+ @node.normal[:slappiness] = "a warm gun"
+ @access_controls.stub!(:requires_changes?).and_return(false)
+ @provider.should_not_receive(:backup)
+ FileUtils.should_not_receive(:mv)
+ @provider.run_action(:create)
+ @resource.should_not be_updated_by_last_action
+ end
+
+ it "does not backup the original or overwrite it on create if missing" do
+ @node.normal[:slappiness] = "a warm gun"
+ @access_controls.stub!(:requires_changes?).and_return(false)
+ @provider.should_not_receive(:backup)
+ FileUtils.should_not_receive(:mv)
+ @provider.run_action(:create)
+ @resource.should_not be_updated_by_last_action
+ end
+
+ it "sets the file access controls if they have diverged" do
+ @provider.stub!(:backup).and_return(true)
+ @access_controls.stub!(:requires_changes?).and_return(true)
+ @access_controls.should_receive(:set_all!)
+ @resource.owner("adam")
+ @resource.group("wheel")
+ @resource.mode(00644)
+ @provider.should_receive(:backup)
+ @provider.run_action(:create)
+ @resource.should be_updated_by_last_action
+ end
+ end
+
+ end
+end
diff --git a/spec/unit/provider/user/dscl_spec.rb b/spec/unit/provider/user/dscl_spec.rb
new file mode 100644
index 0000000000..3894cd61b4
--- /dev/null
+++ b/spec/unit/provider/user/dscl_spec.rb
@@ -0,0 +1,480 @@
+#
+# Author:: Dreamcat4 (<dreamcat4@gmail.com>)
+# Copyright:: Copyright (c) 2009 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.
+#
+
+ShellCmdResult = Struct.new(:stdout, :stderr, :exitstatus)
+
+require 'spec_helper'
+require 'ostruct'
+
+describe Chef::Provider::User::Dscl do
+ before do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @new_resource = Chef::Resource::User.new("toor")
+ @provider = Chef::Provider::User::Dscl.new(@new_resource, @run_context)
+ end
+
+ describe "when shelling out to dscl" do
+ it "should run dscl with the supplied cmd /Path args" do
+ shell_return = ShellCmdResult.new('stdout', 'err', 0)
+ @provider.should_receive(:shell_out).with("dscl . -cmd /Path args").and_return(shell_return)
+ @provider.safe_dscl("cmd /Path args").should == 'stdout'
+ end
+
+ it "returns an empty string from delete commands" do
+ shell_return = ShellCmdResult.new('out', 'err', 23)
+ @provider.should_receive(:shell_out).with("dscl . -delete /Path args").and_return(shell_return)
+ @provider.safe_dscl("delete /Path args").should == ""
+ end
+
+ it "should raise an exception for any other command" do
+ shell_return = ShellCmdResult.new('out', 'err', 23)
+ @provider.should_receive(:shell_out).with('dscl . -cmd /Path arguments').and_return(shell_return)
+ lambda { @provider.safe_dscl("cmd /Path arguments") }.should raise_error(Chef::Exceptions::DsclCommandFailed)
+ end
+
+ it "raises an exception when dscl reports 'no such key'" do
+ shell_return = ShellCmdResult.new("No such key: ", 'err', 23)
+ @provider.should_receive(:shell_out).with('dscl . -cmd /Path args').and_return(shell_return)
+ lambda { @provider.safe_dscl("cmd /Path args") }.should raise_error(Chef::Exceptions::DsclCommandFailed)
+ end
+
+ it "raises an exception when dscl reports 'eDSRecordNotFound'" do
+ shell_return = ShellCmdResult.new("<dscl_cmd> DS Error: -14136 (eDSRecordNotFound)", 'err', -14136)
+ @provider.should_receive(:shell_out).with('dscl . -cmd /Path args').and_return(shell_return)
+ lambda { @provider.safe_dscl("cmd /Path args") }.should raise_error(Chef::Exceptions::DsclCommandFailed)
+ end
+ end
+
+ describe "get_free_uid" do
+ before do
+ @provider.stub!(:safe_dscl).and_return("\nwheel 200\nstaff 201\n")
+ end
+
+ it "should run safe_dscl with list /Users uid" do
+ @provider.should_receive(:safe_dscl).with("list /Users uid")
+ @provider.get_free_uid
+ end
+
+ it "should return the first unused uid number on or above 200" do
+ @provider.get_free_uid.should == 202
+ end
+
+ it "should raise an exception when the search limit is exhausted" do
+ search_limit = 1
+ lambda { @provider.get_free_uid(search_limit) }.should raise_error(RuntimeError)
+ end
+ end
+
+ describe "uid_used?" do
+ before do
+ @provider.stub!(:safe_dscl).and_return("\naj 500\n")
+ end
+
+ it "should run safe_dscl with list /Users uid" do
+ @provider.should_receive(:safe_dscl).with("list /Users uid")
+ @provider.uid_used?(500)
+ end
+
+ it "should return true for a used uid number" do
+ @provider.uid_used?(500).should be_true
+ end
+
+ it "should return false for an unused uid number" do
+ @provider.uid_used?(501).should be_false
+ end
+
+ it "should return false if not given any valid uid number" do
+ @provider.uid_used?(nil).should be_false
+ end
+ end
+
+ describe "when determining the uid to set" do
+ it "raises RequestedUIDUnavailable if the requested uid is already in use" do
+ @provider.stub!(:uid_used?).and_return(true)
+ @provider.should_receive(:get_free_uid).and_return(501)
+ lambda { @provider.set_uid }.should raise_error(Chef::Exceptions::RequestedUIDUnavailable)
+ end
+
+ it "finds a valid, unused uid when none is specified" do
+ @provider.should_receive(:safe_dscl).with("list /Users uid").and_return('')
+ @provider.should_receive(:safe_dscl).with("create /Users/toor UniqueID 501")
+ @provider.should_receive(:get_free_uid).and_return(501)
+ @provider.set_uid
+ @new_resource.uid.should == 501
+ end
+
+ it "sets the uid specified in the resource" do
+ @new_resource.uid(1000)
+ @provider.should_receive(:safe_dscl).with("create /Users/toor UniqueID 1000").and_return(true)
+ @provider.should_receive(:safe_dscl).with("list /Users uid").and_return('')
+ @provider.set_uid
+ end
+ end
+
+ describe "when modifying the home directory" do
+ before do
+ @new_resource.supports({ :manage_home => true })
+ @new_resource.home('/Users/toor')
+
+ @current_resource = @new_resource.dup
+ @provider.current_resource = @current_resource
+ end
+
+ it "deletes the home directory when resource#home is nil" do
+ @new_resource.instance_variable_set(:@home, nil)
+ @provider.should_receive(:safe_dscl).with("delete /Users/toor NFSHomeDirectory").and_return(true)
+ @provider.modify_home
+ end
+
+
+ it "raises InvalidHomeDirectory when the resource's home directory doesn't look right" do
+ @new_resource.home('epic-fail')
+ lambda { @provider.modify_home }.should raise_error(Chef::Exceptions::InvalidHomeDirectory)
+ end
+
+ it "moves the users home to the new location if it exists and the target location is different" do
+ @new_resource.supports(:manage_home => true)
+
+ current_home = CHEF_SPEC_DATA + '/old_home_dir'
+ current_home_files = [current_home + '/my-dot-emacs', current_home + '/my-dot-vim']
+ @current_resource.home(current_home)
+ @new_resource.gid(23)
+ ::File.stub!(:exists?).with('/old/home/toor').and_return(true)
+ ::File.stub!(:exists?).with('/Users/toor').and_return(true)
+
+ FileUtils.should_receive(:mkdir_p).with('/Users/toor').and_return(true)
+ FileUtils.should_receive(:rmdir).with(current_home)
+ ::Dir.should_receive(:glob).with("#{CHEF_SPEC_DATA}/old_home_dir/*",::File::FNM_DOTMATCH).and_return(current_home_files)
+ FileUtils.should_receive(:mv).with(current_home_files, "/Users/toor", :force => true)
+ FileUtils.should_receive(:chown_R).with('toor','23','/Users/toor')
+
+ @provider.should_receive(:safe_dscl).with("create /Users/toor NFSHomeDirectory '/Users/toor'")
+ @provider.modify_home
+ end
+
+ it "should raise an exception when the systems user template dir (skel) cannot be found" do
+ ::File.stub!(:exists?).and_return(false,false,false)
+ lambda { @provider.modify_home }.should raise_error(Chef::Exceptions::User)
+ end
+
+ it "should run ditto to copy any missing files from skel to the new home dir" do
+ ::File.should_receive(:exists?).with("/System/Library/User\ Template/English.lproj").and_return(true)
+ FileUtils.should_receive(:chown_R).with('toor', '', '/Users/toor')
+ @provider.should_receive(:shell_out!).with("ditto '/System/Library/User Template/English.lproj' '/Users/toor'")
+ @provider.ditto_home
+ end
+
+ it "creates the user's NFSHomeDirectory and home directory" do
+ @provider.should_receive(:safe_dscl).with("create /Users/toor NFSHomeDirectory '/Users/toor'").and_return(true)
+ @provider.should_receive(:ditto_home)
+ @provider.modify_home
+ end
+ end
+
+ describe "osx_shadow_hash?" do
+ it "should return true when the string is a shadow hash" do
+ @provider.osx_shadow_hash?("0"*8*155).should eql(true)
+ end
+
+ it "should return false otherwise" do
+ @provider.osx_shadow_hash?("any other string").should eql(false)
+ end
+ end
+
+ describe "when detecting the format of a password" do
+ it "detects a OS X salted sha1" do
+ @provider.osx_salted_sha1?("0"*48).should eql(true)
+ @provider.osx_salted_sha1?("any other string").should eql(false)
+ end
+ end
+
+ describe "guid" do
+ it "should run safe_dscl with read /Users/user GeneratedUID to get the users GUID" do
+ expected_uuid = "b398449e-cee0-45e0-80f8-b0b5b1bfdeaa"
+ @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(expected_uuid + "\n")
+ @provider.guid.should == expected_uuid
+ end
+ end
+
+ describe "shadow_hash_set?" do
+
+ it "should run safe_dscl with read /Users/user to see if the AuthenticationAuthority key exists" do
+ @provider.should_receive(:safe_dscl).with("read /Users/toor")
+ @provider.shadow_hash_set?
+ end
+
+ describe "when the user account has an AuthenticationAuthority key" do
+ it "uses the shadow hash when there is a ShadowHash field in the AuthenticationAuthority key" do
+ @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
+ @provider.shadow_hash_set?.should be_true
+ end
+
+ it "does not use the shadow hash when there is no ShadowHash field in the AuthenticationAuthority key" do
+ @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: \n")
+ @provider.shadow_hash_set?.should be_false
+ end
+
+ end
+
+ describe "with no AuthenticationAuthority key in the user account" do
+ it "does not use the shadow hash" do
+ @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("")
+ @provider.shadow_hash_set?.should eql(false)
+ end
+ end
+ end
+
+ describe "when setting or modifying the user password" do
+ before do
+ @new_resource.password("password")
+ @output = StringIO.new
+ end
+
+ describe "when using a salted sha1 for the password" do
+ before do
+ @new_resource.password("F"*48)
+ end
+
+ it "should write a shadow hash file with the expected salted sha1" do
+ uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
+ File.should_receive(:open).with('/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA', "w", 384).and_yield(@output)
+ @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
+ @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
+ expected_salted_sha1 = @new_resource.password
+ expected_shadow_hash = "00000000"*155
+ expected_shadow_hash[168] = expected_salted_sha1
+ @provider.modify_password
+ @output.string.strip.should == expected_shadow_hash
+ end
+ end
+
+ describe "when given a shadow hash file for the password" do
+ it "should write the shadow hash file directly to /var/db/shadow/hash/GUID" do
+ shadow_hash = '0123456789ABCDE0123456789ABCDEF' * 40
+ raise 'oops' unless shadow_hash.size == 1240
+ @new_resource.password shadow_hash
+ uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
+ File.should_receive(:open).with('/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA', "w", 384).and_yield(@output)
+ @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
+ @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
+ @provider.modify_password
+ @output.string.strip.should == shadow_hash
+ end
+ end
+
+ describe "when given a string for the password" do
+ it "should output a salted sha1 and shadow hash file from the specified password" do
+ uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
+ File.should_receive(:open).with('/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA', "w", 384).and_yield(@output)
+ @new_resource.password("password")
+ OpenSSL::Random.stub!(:random_bytes).and_return("\377\377\377\377\377\377\377\377")
+ expected_salted_sha1 = "F"*8+"SHA1-"*8
+ expected_shadow_hash = "00000000"*155
+ expected_shadow_hash[168] = expected_salted_sha1
+ @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
+ @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
+ @provider.modify_password
+ @output.string.strip.should match(/^0{168}(FFFFFFFF1C1AA7935D4E1190AFEC92343F31F7671FBF126D)0{1071}$/)
+ end
+ end
+
+ it "should write the output directly to the shadow hash file at /var/db/shadow/hash/GUID" do
+ shadow_file = StringIO.new
+ uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
+ File.should_receive(:open).with("/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA",'w',0600).and_yield(shadow_file)
+ @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
+ @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
+ @provider.modify_password
+ shadow_file.string.should match(/^0{168}[0-9A-F]{48}0{1071}$/)
+ end
+
+ it "should run safe_dscl append /Users/user AuthenticationAuthority ;ShadowHash; when no shadow hash set" do
+ shadow_file = StringIO.new
+ uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
+ File.should_receive(:open).with("/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA",'w',0600).and_yield(shadow_file)
+ @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
+ @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority:\n")
+ @provider.should_receive(:safe_dscl).with("append /Users/toor AuthenticationAuthority ';ShadowHash;'")
+ @provider.modify_password
+ shadow_file.string.should match(/^0{168}[0-9A-F]{48}0{1071}$/)
+ end
+ end
+
+ describe "load_current_resource" do
+ it "should raise an error if the required binary /usr/bin/dscl doesn't exist" do
+ ::File.should_receive(:exists?).with("/usr/bin/dscl").and_return(false)
+ lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::User)
+ end
+
+ it "shouldn't raise an error if /usr/bin/dscl exists" do
+ ::File.stub!(:exists?).and_return(true)
+ lambda { @provider.load_current_resource }.should_not raise_error(Chef::Exceptions::User)
+ end
+ end
+
+ describe "when the user does not yet exist and chef is creating it" do
+ context "with a numeric gid" do
+ before do
+ @new_resource.comment "#mockssuck"
+ @new_resource.gid 1001
+ end
+
+ it "creates the user, comment field, sets uid, gid, configures the home directory, sets the shell, and sets the password" do
+ @provider.should_receive :dscl_create_user
+ @provider.should_receive :dscl_create_comment
+ @provider.should_receive :set_uid
+ @provider.should_receive :dscl_set_gid
+ @provider.should_receive :modify_home
+ @provider.should_receive :dscl_set_shell
+ @provider.should_receive :modify_password
+ @provider.create_user
+ end
+
+ it "creates the user and sets the comment field" do
+ @provider.should_receive(:safe_dscl).with("create /Users/toor").and_return(true)
+ @provider.dscl_create_user
+ end
+
+ it "sets the comment field" do
+ @provider.should_receive(:safe_dscl).with("create /Users/toor RealName '#mockssuck'").and_return(true)
+ @provider.dscl_create_comment
+ end
+
+ it "should run safe_dscl with create /Users/user PrimaryGroupID to set the users primary group" do
+ @provider.should_receive(:safe_dscl).with("create /Users/toor PrimaryGroupID '1001'").and_return(true)
+ @provider.dscl_set_gid
+ end
+
+ it "should run safe_dscl with create /Users/user UserShell to set the users login shell" do
+ @provider.should_receive(:safe_dscl).with("create /Users/toor UserShell '/usr/bin/false'").and_return(true)
+ @provider.dscl_set_shell
+ end
+ end
+
+ context "with a non-numeric gid" do
+ before do
+ @new_resource.comment "#mockssuck"
+ @new_resource.gid "newgroup"
+ end
+
+ it "should map the group name to a numeric ID when the group exists" do
+ @provider.should_receive(:safe_dscl).with("read /Groups/newgroup PrimaryGroupID").ordered.and_return("PrimaryGroupID: 1001\n")
+ @provider.should_receive(:safe_dscl).with("create /Users/toor PrimaryGroupID '1001'").ordered.and_return(true)
+ @provider.dscl_set_gid
+ end
+
+ it "should raise an exception when the group does not exist" do
+ shell_return = ShellCmdResult.new("<dscl_cmd> DS Error: -14136 (eDSRecordNotFound)", 'err', -14136)
+ @provider.should_receive(:shell_out).with('dscl . -read /Groups/newgroup PrimaryGroupID').and_return(shell_return)
+ lambda { @provider.dscl_set_gid }.should raise_error(Chef::Exceptions::GroupIDNotFound)
+ end
+ end
+ end
+
+ describe "when the user exists and chef is managing it" do
+ before do
+ @current_resource = @new_resource.dup
+ @provider.current_resource = @current_resource
+
+ # These are all different from @current_resource
+ @new_resource.username "mud"
+ @new_resource.uid 2342
+ @new_resource.gid 2342
+ @new_resource.home '/Users/death'
+ @new_resource.password 'goaway'
+ end
+
+ it "sets the user, comment field, uid, gid, moves the home directory, sets the shell, and sets the password" do
+ @provider.should_receive :dscl_create_user
+ @provider.should_receive :dscl_create_comment
+ @provider.should_receive :set_uid
+ @provider.should_receive :dscl_set_gid
+ @provider.should_receive :modify_home
+ @provider.should_receive :dscl_set_shell
+ @provider.should_receive :modify_password
+ @provider.create_user
+ end
+ end
+
+ describe "when changing the gid" do
+ before do
+ @current_resource = @new_resource.dup
+ @provider.current_resource = @current_resource
+
+ # This is different from @current_resource
+ @new_resource.gid 2342
+ end
+
+ it "sets the gid" do
+ @provider.should_receive :dscl_set_gid
+ @provider.manage_user
+ end
+ end
+
+ describe "when the user exists and chef is removing it" do
+ it "removes the user's home directory when the resource is configured to manage home" do
+ @new_resource.supports({ :manage_home => true })
+ @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("NFSHomeDirectory: /Users/fuuuuuuuuuuuuu")
+ @provider.should_receive(:safe_dscl).with("delete /Users/toor")
+ FileUtils.should_receive(:rm_rf).with("/Users/fuuuuuuuuuuuuu")
+ @provider.remove_user
+ end
+
+ it "removes the user from any group memberships" do
+ Etc.stub(:group).and_yield(OpenStruct.new(:name => 'ragefisters', :mem => 'toor'))
+ @provider.should_receive(:safe_dscl).with("delete /Users/toor")
+ @provider.should_receive(:safe_dscl).with("delete /Groups/ragefisters GroupMembership 'toor'")
+ @provider.remove_user
+ end
+ end
+
+ describe "when discovering if a user is locked" do
+
+ it "determines the user is not locked when dscl shows an AuthenticationAuthority without a DisabledUser field" do
+ @provider.should_receive(:safe_dscl).with("read /Users/toor")
+ @provider.should_not be_locked
+ end
+
+ it "determines the user is locked when dscl shows an AuthenticationAuthority with a DisabledUser field" do
+ @provider.should_receive(:safe_dscl).with('read /Users/toor').and_return("\nAuthenticationAuthority: ;DisabledUser;\n")
+ @provider.should be_locked
+ end
+
+ it "determines the user is not locked when dscl shows no AuthenticationAuthority" do
+ @provider.should_receive(:safe_dscl).with('read /Users/toor').and_return("\n")
+ @provider.should_not be_locked
+ end
+ end
+
+ describe "when locking the user" do
+ it "should run safe_dscl with append /Users/user AuthenticationAuthority ;DisabledUser; to lock the user account" do
+ @provider.should_receive(:safe_dscl).with("append /Users/toor AuthenticationAuthority ';DisabledUser;'")
+ @provider.lock_user
+ end
+ end
+
+ describe "when unlocking the user" do
+ it "removes DisabledUser from the authentication string" do
+ @provider.should_receive(:safe_dscl).with("read /Users/toor AuthenticationAuthority").and_return("\nAuthenticationAuthority: ;ShadowHash; ;DisabledUser;\n")
+ @provider.should_receive(:safe_dscl).with("create /Users/toor AuthenticationAuthority ';ShadowHash;'")
+ @provider.unlock_user
+ end
+ end
+end
diff --git a/spec/unit/provider/user/pw_spec.rb b/spec/unit/provider/user/pw_spec.rb
new file mode 100644
index 0000000000..b7503ea15f
--- /dev/null
+++ b/spec/unit/provider/user/pw_spec.rb
@@ -0,0 +1,235 @@
+#
+# Author:: Stephen Haynes (<sh@nomitor.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+describe Chef::Provider::User::Pw do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::User.new("adam")
+ @new_resource.comment "Adam Jacob"
+ @new_resource.uid 1000
+ @new_resource.gid 1000
+ @new_resource.home "/home/adam"
+ @new_resource.shell "/usr/bin/zsh"
+ @new_resource.password "abracadabra"
+
+ @new_resource.supports :manage_home => true
+
+ @current_resource = Chef::Resource::User.new("adam")
+ @current_resource.comment "Adam Jacob"
+ @current_resource.uid 1000
+ @current_resource.gid 1000
+ @current_resource.home "/home/adam"
+ @current_resource.shell "/usr/bin/zsh"
+ @current_resource.password "abracadabra"
+
+ @provider = Chef::Provider::User::Pw.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ end
+
+ describe "setting options to the pw command" do
+ field_list = {
+ 'comment' => "-c",
+ 'home' => "-d",
+ 'gid' => "-g",
+ 'uid' => "-u",
+ 'shell' => "-s"
+ }
+ field_list.each do |attribute, option|
+ it "should check for differences in #{attribute} between the new and current resources" do
+ @current_resource.should_receive(attribute)
+ @new_resource.should_receive(attribute)
+ @provider.set_options
+ end
+
+ it "should set the option for #{attribute} if the new resources #{attribute} is not null" do
+ @new_resource.stub!(attribute).and_return("hola")
+ @provider.set_options.should eql(" #{@new_resource.username} #{option} '#{@new_resource.send(attribute)}' -m")
+ end
+
+ it "should set the option for #{attribute} if the new resources #{attribute} is not null, without homedir management" do
+ @new_resource.stub!(:supports).and_return({:manage_home => false})
+ @new_resource.stub!(attribute).and_return("hola")
+ @provider.set_options.should eql(" #{@new_resource.username} #{option} '#{@new_resource.send(attribute)}'")
+ end
+ end
+
+ it "should combine all the possible options" do
+ match_string = " adam"
+ field_list.sort{ |a,b| a[0] <=> b[0] }.each do |attribute, option|
+ @new_resource.stub!(attribute).and_return("hola")
+ match_string << " #{option} 'hola'"
+ end
+ match_string << " -m"
+ @provider.set_options.should eql(match_string)
+ end
+ end
+
+ describe "create_user" do
+ before(:each) do
+ @provider.stub!(:run_command).and_return(true)
+ @provider.stub!(:modify_password).and_return(true)
+ end
+
+ it "should run pw useradd with the return of set_options" do
+ @provider.should_receive(:run_command).with({ :command => "pw useradd adam -m" }).and_return(true)
+ @provider.create_user
+ end
+
+ it "should modify the password" do
+ @provider.should_receive(:modify_password).and_return(true)
+ @provider.create_user
+ end
+ end
+
+ describe "manage_user" do
+ before(:each) do
+ @provider.stub!(:run_command).and_return(true)
+ @provider.stub!(:modify_password).and_return(true)
+ end
+
+ it "should run pw usermod with the return of set_options" do
+ @provider.should_receive(:run_command).with({ :command => "pw usermod adam -m" }).and_return(true)
+ @provider.manage_user
+ end
+
+ it "should modify the password" do
+ @provider.should_receive(:modify_password).and_return(true)
+ @provider.create_user
+ end
+ end
+
+ describe "remove_user" do
+ it "should run pw userdel with the new resources user name" do
+ @new_resource.supports :manage_home => false
+ @provider.should_receive(:run_command).with({ :command => "pw userdel #{@new_resource.username}" }).and_return(true)
+ @provider.remove_user
+ end
+
+ it "should run pw userdel with the new resources user name and -r if manage_home is true" do
+ @provider.should_receive(:run_command).with({ :command => "pw userdel #{@new_resource.username} -r"}).and_return(true)
+ @provider.remove_user
+ end
+ end
+
+ describe "determining if the user is locked" do
+ it "should return true if user is locked" do
+ @current_resource.stub!(:password).and_return("*LOCKED*abracadabra")
+ @provider.check_lock.should eql(true)
+ end
+
+ it "should return false if user is not locked" do
+ @current_resource.stub!(:password).and_return("abracadabra")
+ @provider.check_lock.should eql(false)
+ end
+ end
+
+ describe "when locking the user" do
+ it "should run pw lock with the new resources username" do
+ @provider.should_receive(:run_command).with({ :command => "pw lock #{@new_resource.username}"})
+ @provider.lock_user
+ end
+ end
+
+ describe "when unlocking the user" do
+ it "should run pw unlock with the new resources username" do
+ @provider.should_receive(:run_command).with({ :command => "pw unlock #{@new_resource.username}"})
+ @provider.unlock_user
+ end
+ end
+
+ describe "when modifying the password" do
+ before(:each) do
+ @status = mock("Status", :exitstatus => 0)
+ @provider.stub!(:popen4).and_return(@status)
+ @pid, @stdin, @stdout, @stderr = nil, nil, nil, nil
+ end
+
+ it "should check for differences in password between the new and current resources" do
+ @current_resource.should_receive(:password)
+ @new_resource.should_receive(:password)
+ @provider.modify_password
+ end
+
+ describe "and the passwords are identical" do
+ before(:each) do
+ @new_resource.stub!(:password).and_return("abracadabra")
+ @current_resource.stub!(:password).and_return("abracadabra")
+ end
+
+ it "logs an appropriate message" do
+ Chef::Log.should_receive(:debug).with("user[adam] no change needed to password")
+ @provider.modify_password
+ end
+ end
+
+ describe "and the passwords are different" do
+ before(:each) do
+ @new_resource.stub!(:password).and_return("abracadabra")
+ @current_resource.stub!(:password).and_return("sesame")
+ end
+
+ it "should log an appropriate message" do
+ Chef::Log.should_receive(:debug).with("user[adam] updating password")
+ @provider.modify_password
+ end
+
+ it "should run pw usermod with the username and the option -H 0" do
+ @provider.should_receive(:popen4).with("pw usermod adam -H 0", :waitlast => true).and_return(@status)
+ @provider.modify_password
+ end
+
+ it "should send the new password to the stdin of pw usermod" do
+ @stdin = StringIO.new
+ @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.modify_password
+ @stdin.string.should == "abracadabra\n"
+ end
+
+ it "should raise an exception if pw usermod fails" do
+ @status.should_receive(:exitstatus).and_return(1)
+ lambda { @provider.modify_password }.should raise_error(Chef::Exceptions::User)
+ end
+
+ it "should not raise an exception if pw usermod succeeds" do
+ @status.should_receive(:exitstatus).and_return(0)
+ lambda { @provider.modify_password }.should_not raise_error(Chef::Exceptions::User)
+ end
+ end
+ end
+
+ describe "when loading the current state" do
+ before do
+ @provider.new_resource = Chef::Resource::User.new("adam")
+ end
+
+ it "should raise an error if the required binary /usr/sbin/pw doesn't exist" do
+ File.should_receive(:exists?).with("/usr/sbin/pw").and_return(false)
+ lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::User)
+ end
+
+ it "shouldn't raise an error if /usr/sbin/pw exists" do
+ File.stub!(:exists?).and_return(true)
+ lambda { @provider.load_current_resource }.should_not raise_error(Chef::Exceptions::User)
+ end
+ end
+end
diff --git a/spec/unit/provider/user/useradd_spec.rb b/spec/unit/provider/user/useradd_spec.rb
new file mode 100644
index 0000000000..ea6caf6e0a
--- /dev/null
+++ b/spec/unit/provider/user/useradd_spec.rb
@@ -0,0 +1,386 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Daniel DeLeo (<dan@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'
+
+describe Chef::Provider::User::Useradd do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::User.new("adam", @run_context)
+ @new_resource.comment "Adam Jacob"
+ @new_resource.uid 1000
+ @new_resource.gid 1000
+ @new_resource.home "/home/adam"
+ @new_resource.shell "/usr/bin/zsh"
+ @new_resource.password "abracadabra"
+ @new_resource.system false
+ @new_resource.manage_home false
+ @new_resource.non_unique false
+ @current_resource = Chef::Resource::User.new("adam", @run_context)
+ @current_resource.comment "Adam Jacob"
+ @current_resource.uid 1000
+ @current_resource.gid 1000
+ @current_resource.home "/home/adam"
+ @current_resource.shell "/usr/bin/zsh"
+ @current_resource.password "abracadabra"
+ @current_resource.system false
+ @current_resource.manage_home false
+ @current_resource.non_unique false
+ @current_resource.supports({:manage_home => false, :non_unique => false})
+ @provider = Chef::Provider::User::Useradd.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ end
+
+ describe "when setting option" do
+ field_list = {
+ 'comment' => "-c",
+ 'gid' => "-g",
+ 'uid' => "-u",
+ 'shell' => "-s",
+ 'password' => "-p"
+ }
+
+ field_list.each do |attribute, option|
+ it "should check for differences in #{attribute} between the new and current resources" do
+ @current_resource.should_receive(attribute)
+ @new_resource.should_receive(attribute)
+ @provider.universal_options
+ end
+
+ it "should set the option for #{attribute} if the new resources #{attribute} is not nil" do
+ @new_resource.stub!(attribute).and_return("hola")
+ @provider.universal_options.should eql(" #{option} 'hola'")
+ end
+
+ it "should set the option for #{attribute} if the new resources #{attribute} is not nil, without homedir management" do
+ @new_resource.stub!(:supports).and_return({:manage_home => false,
+ :non_unique => false})
+ @new_resource.stub!(attribute).and_return("hola")
+ @provider.universal_options.should eql(" #{option} 'hola'")
+ end
+
+ it "should set the option for #{attribute} if the new resources #{attribute} is not nil, without homedir management (using real attributes)" do
+ @new_resource.stub!(:manage_home).and_return(false)
+ @new_resource.stub!(:non_unique).and_return(false)
+ @new_resource.stub!(attribute).and_return("hola")
+ @provider.universal_options.should eql(" #{option} 'hola'")
+ end
+ end
+
+ it "should combine all the possible options" do
+ match_string = ""
+ field_list.sort{ |a,b| a[0] <=> b[0] }.each do |attribute, option|
+ @new_resource.stub!(attribute).and_return("hola")
+ match_string << " #{option} 'hola'"
+ end
+ @provider.universal_options.should eql(match_string)
+ end
+
+ describe "when we want to create a system user" do
+ before do
+ @new_resource.manage_home(true)
+ @new_resource.non_unique(false)
+ end
+
+ it "should set useradd -r" do
+ @new_resource.system(true)
+ @provider.useradd_options.should == " -r"
+ end
+ end
+
+ describe "when the resource has a different home directory and supports home directory management" do
+ before do
+ @new_resource.stub!(:home).and_return("/wowaweea")
+ @new_resource.stub!(:supports).and_return({:manage_home => true,
+ :non_unique => false})
+ end
+
+ it "should set -m -d /homedir" do
+ @provider.universal_options.should == " -m -d '/wowaweea'"
+ @provider.useradd_options.should == ""
+ end
+ end
+
+ describe "when the resource has a different home directory and supports home directory management (using real attributes)" do
+ before do
+ @new_resource.stub!(:home).and_return("/wowaweea")
+ @new_resource.stub!(:manage_home).and_return(true)
+ @new_resource.stub!(:non_unique).and_return(false)
+ end
+
+ it "should set -m -d /homedir" do
+ @provider.universal_options.should eql(" -m -d '/wowaweea'")
+ @provider.useradd_options.should == ""
+ end
+ end
+
+ describe "when the resource supports non_unique ids" do
+ before do
+ @new_resource.stub!(:supports).and_return({:manage_home => false,
+ :non_unique => true})
+ end
+
+ it "should set -m -o" do
+ @provider.universal_options.should eql(" -o")
+ end
+ end
+
+ describe "when the resource supports non_unique ids (using real attributes)" do
+ before do
+ @new_resource.stub!(:manage_home).and_return(false)
+ @new_resource.stub!(:non_unique).and_return(true)
+ end
+
+ it "should set -m -o" do
+ @provider.universal_options.should eql(" -o")
+ end
+ end
+ end
+
+ describe "when creating a user" do
+ before(:each) do
+ @current_resource = Chef::Resource::User.new(@new_resource.name, @run_context)
+ @current_resource.username(@new_resource.username)
+ @provider.current_resource = @current_resource
+ @provider.new_resource.manage_home true
+ @provider.new_resource.home "/Users/mud"
+ @provider.new_resource.gid '23'
+ end
+
+ it "runs useradd with the computed command options" do
+ command = "useradd -c 'Adam Jacob' -g '23' -p 'abracadabra' -s '/usr/bin/zsh' -u '1000' -m -d '/Users/mud' adam"
+ @provider.should_receive(:run_command).with({ :command => command }).and_return(true)
+ @provider.create_user
+ end
+
+ describe "and home is not specified for new system user resource" do
+
+ before do
+ @provider.new_resource.system true
+ # there is no public API to set attribute's value to nil
+ @provider.new_resource.instance_variable_set("@home", nil)
+ end
+
+ it "should not include -m or -d in the command options" do
+ command = "useradd -c 'Adam Jacob' -g '23' -p 'abracadabra' -s '/usr/bin/zsh' -u '1000' -r adam"
+ @provider.should_receive(:run_command).with({ :command => command }).and_return(true)
+ @provider.create_user
+ end
+
+ end
+
+ end
+
+ describe "when managing a user" do
+ before(:each) do
+ @provider.new_resource.manage_home true
+ @provider.new_resource.home "/Users/mud"
+ @provider.new_resource.gid '23'
+ end
+
+ # CHEF-3423, -m must come before the username
+ it "runs usermod with the computed command options" do
+ @provider.should_receive(:run_command).with({ :command => "usermod -g '23' -m -d '/Users/mud' adam" }).and_return(true)
+ @provider.manage_user
+ end
+
+ it "does not set the -r option to usermod" do
+ @new_resource.system(true)
+ @provider.should_receive(:run_command).with({ :command => "usermod -g '23' -m -d '/Users/mud' adam" }).and_return(true)
+ @provider.manage_user
+ end
+
+ it "CHEF-3429: does not set -m if we aren't changing the home directory" do
+ @provider.should_receive(:updating_home?).and_return(false)
+ @provider.should_receive(:run_command).with({ :command => "usermod -g '23' adam" }).and_return(true)
+ @provider.manage_user
+ end
+ end
+
+ describe "when removing a user" do
+
+ it "should run userdel with the new resources user name" do
+ @provider.should_receive(:run_command).with({ :command => "userdel #{@new_resource.username}" }).and_return(true)
+ @provider.remove_user
+ end
+
+ it "should run userdel with the new resources user name and -r if manage_home is true" do
+ @new_resource.stub!(:supports).and_return({ :manage_home => true,
+ :non_unique => false})
+ @provider.should_receive(:run_command).with({ :command => "userdel -r #{@new_resource.username}"}).and_return(true)
+ @provider.remove_user
+ end
+
+ it "should run userdel with the new resources user name if non_unique is true" do
+ @new_resource.stub!(:supports).and_return({ :manage_home => false,
+ :non_unique => true})
+ @provider.should_receive(:run_command).with({ :command => "userdel #{@new_resource.username}"}).and_return(true)
+ @provider.remove_user
+ end
+ end
+
+ describe "when checking the lock" do
+ before(:each) do
+ # @node = Chef::Node.new
+ # @new_resource = mock("Chef::Resource::User",
+ # :nil_object => true,
+ # :username => "adam"
+ # )
+ @status = mock("Status", :exitstatus => 0)
+ #@provider = Chef::Provider::User::Useradd.new(@node, @new_resource)
+ @provider.stub!(:popen4).and_return(@status)
+ @stdin = mock("STDIN", :nil_object => true)
+ @stdout = mock("STDOUT", :nil_object => true)
+ @stdout.stub!(:gets).and_return("root P 09/02/2008 0 99999 7 -1")
+ @stderr = mock("STDERR", :nil_object => true)
+ @pid = mock("PID", :nil_object => true)
+ end
+
+ it "should call passwd -S to check the lock status" do
+ @provider.should_receive(:popen4).with("passwd -S #{@new_resource.username}").and_return(@status)
+ @provider.check_lock
+ end
+
+ it "should get the first line of passwd -S STDOUT" do
+ @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @stdout.should_receive(:gets).and_return("root P 09/02/2008 0 99999 7 -1")
+ @provider.check_lock
+ end
+
+ it "should return false if status begins with P" do
+ @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.check_lock.should eql(false)
+ end
+
+ it "should return false if status begins with N" do
+ @stdout.stub!(:gets).and_return("root N")
+ @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.check_lock.should eql(false)
+ end
+
+ it "should return true if status begins with L" do
+ @stdout.stub!(:gets).and_return("root L")
+ @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ @provider.check_lock.should eql(true)
+ end
+
+ it "should raise a Chef::Exceptions::User if passwd -S fails on anything other than redhat/centos" do
+ @node.automatic_attrs[:platform] = 'ubuntu'
+ @status.should_receive(:exitstatus).and_return(1)
+ lambda { @provider.check_lock }.should raise_error(Chef::Exceptions::User)
+ end
+
+ ['redhat', 'centos'].each do |os|
+ it "should not raise a Chef::Exceptions::User if passwd -S exits with 1 on #{os} and the passwd package is version 0.73-1" do
+ @node.automatic_attrs[:platform] = os
+ @stdout.stub!(:gets).and_return("passwd-0.73-1\n")
+ @status.should_receive(:exitstatus).twice.and_return(1)
+ @provider.should_receive(:popen4).with("passwd -S #{@new_resource.username}")
+ @provider.should_receive(:popen4).with("rpm -q passwd").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ lambda { @provider.check_lock }.should_not raise_error(Chef::Exceptions::User)
+ end
+
+ it "should raise a Chef::Exceptions::User if passwd -S exits with 1 on #{os} and the passwd package is not version 0.73-1" do
+ @node.automatic_attrs[:platform] = os
+ @stdout.stub!(:gets).and_return("passwd-0.73-2\n")
+ @status.should_receive(:exitstatus).twice.and_return(1)
+ @provider.should_receive(:popen4).with("passwd -S #{@new_resource.username}")
+ @provider.should_receive(:popen4).with("rpm -q passwd").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+ lambda { @provider.check_lock }.should raise_error(Chef::Exceptions::User)
+ end
+
+ it "should raise a Chef::Exceptions::User if passwd -S exits with something other than 0 or 1 on #{os}" do
+ @node.automatic_attrs[:platform] = os
+ @status.should_receive(:exitstatus).twice.and_return(2)
+ lambda { @provider.check_lock }.should raise_error(Chef::Exceptions::User)
+ end
+ end
+ end
+
+ describe "when locking the user" do
+ it "should run usermod -L with the new resources username" do
+ @provider.should_receive(:run_command).with({ :command => "usermod -L #{@new_resource.username}"})
+ @provider.lock_user
+ end
+ end
+
+ describe "when unlocking the user" do
+ it "should run usermod -L with the new resources username" do
+ @provider.should_receive(:run_command).with({ :command => "usermod -U #{@new_resource.username}"})
+ @provider.unlock_user
+ end
+ end
+
+ describe "when checking if home needs updating" do
+ [
+ {
+ "action" => "should return false if home matches",
+ "current_resource_home" => [ "/home/laurent" ],
+ "new_resource_home" => [ "/home/laurent" ],
+ "expected_result" => false
+ },
+ {
+ "action" => "should return true if home doesn't match",
+ "current_resource_home" => [ "/home/laurent" ],
+ "new_resource_home" => [ "/something/else" ],
+ "expected_result" => true
+ },
+ {
+ "action" => "should return false if home only differs by trailing slash",
+ "current_resource_home" => [ "/home/laurent" ],
+ "new_resource_home" => [ "/home/laurent/", "/home/laurent" ],
+ "expected_result" => false
+ },
+ {
+ "action" => "should return false if home is an equivalent path",
+ "current_resource_home" => [ "/home/laurent" ],
+ "new_resource_home" => [ "/home/./laurent", "/home/laurent" ],
+ "expected_result" => false
+ },
+ ].each do |home_check|
+ it home_check["action"] do
+ @provider.current_resource.home home_check["current_resource_home"].first
+ @current_home_mock = mock("Pathname")
+ @provider.new_resource.home home_check["new_resource_home"].first
+ @new_home_mock = mock("Pathname")
+
+ Pathname.should_receive(:new).with(@current_resource.home).and_return(@current_home_mock)
+ @current_home_mock.should_receive(:cleanpath).and_return(home_check["current_resource_home"].last)
+ Pathname.should_receive(:new).with(@new_resource.home).and_return(@new_home_mock)
+ @new_home_mock.should_receive(:cleanpath).and_return(home_check["new_resource_home"].last)
+
+ @provider.updating_home?.should == home_check["expected_result"]
+ end
+ end
+ it "should return true if the current home does not exist but a home is specified by the new resource" do
+ @new_resource = Chef::Resource::User.new("adam", @run_context)
+ @current_resource = Chef::Resource::User.new("adam", @run_context)
+ @provider = Chef::Provider::User::Useradd.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ @current_resource.home nil
+ @new_resource.home "/home/kitten"
+
+ @provider.updating_home?.should == true
+ end
+ end
+end
diff --git a/spec/unit/provider/user/windows_spec.rb b/spec/unit/provider/user/windows_spec.rb
new file mode 100644
index 0000000000..6ede11b28c
--- /dev/null
+++ b/spec/unit/provider/user/windows_spec.rb
@@ -0,0 +1,178 @@
+#
+# Author:: Doug MacEachern (<dougm@vmware.com>)
+# Copyright:: Copyright (c) 2010 VMware, 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'
+
+class Chef
+ class Util
+ class Windows
+ class NetUser
+ end
+ end
+ end
+end
+
+describe Chef::Provider::User::Windows do
+ before(:each) do
+ @node = Chef::Node.new
+ @new_resource = Chef::Resource::User.new("monkey")
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @current_resource = Chef::Resource::User.new("monkey")
+
+ @net_user = mock("Chef::Util::Windows::NetUser")
+ Chef::Util::Windows::NetUser.stub!(:new).and_return(@net_user)
+
+ @provider = Chef::Provider::User::Windows.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ end
+
+ describe "when comparing the user's current attributes to the desired attributes" do
+ before do
+ @new_resource.comment "Adam Jacob"
+ @new_resource.uid 1000
+ @new_resource.gid 1000
+ @new_resource.home "/home/adam"
+ @new_resource.shell "/usr/bin/zsh"
+ @new_resource.password "abracadabra"
+
+ @provider.current_resource = @new_resource.clone
+ end
+ describe "and the attributes match" do
+ it "doesn't set the comment field to be updated" do
+ @provider.set_options.should_not have_key(:full_name)
+ end
+
+ it "doesn't set the home directory to be updated" do
+ @provider.set_options.should_not have_key(:home_dir)
+ end
+
+ it "doesn't set the group id to be updated" do
+ @provider.set_options.should_not have_key(:primary_group_id)
+ end
+
+ it "doesn't set the user id to be updated" do
+ @provider.set_options.should_not have_key(:user_id)
+ end
+
+ it "doesn't set the shell to be updated" do
+ @provider.set_options.should_not have_key(:script_path)
+ end
+
+ it "doesn't set the password to be updated" do
+ @provider.set_options.should_not have_key(:password)
+ end
+
+ end
+
+ describe "and the attributes do not match" do
+ before do
+ @current_resource = Chef::Resource::User.new("adam")
+ @current_resource.comment "Adam Jacob-foo"
+ @current_resource.uid 1111
+ @current_resource.gid 1111
+ @current_resource.home "/home/adam-foo"
+ @current_resource.shell "/usr/bin/tcsh"
+ @current_resource.password "foobarbaz"
+ @provider.current_resource = @current_resource
+ end
+
+ it "marks the full_name field to be updated" do
+ @provider.set_options[:full_name].should == "Adam Jacob"
+ end
+
+ it "marks the home_dir attribute to be updated" do
+ @provider.set_options[:home_dir].should == '/home/adam'
+ end
+
+ it "marks the primary_group_id attribute to be updated" do
+ @provider.set_options[:primary_group_id].should == 1000
+ end
+
+ it "marks the user_id attribute to be updated" do
+ @provider.set_options[:user_id].should == 1000
+ end
+
+ it "marks the script_path attribute to be updated" do
+ @provider.set_options[:script_path].should == '/usr/bin/zsh'
+ end
+
+ it "marks the password attribute to be updated" do
+ @provider.set_options[:password].should == 'abracadabra'
+ end
+ end
+ end
+
+ describe "when creating the user" do
+ it "should call @net_user.add with the return of set_options" do
+ @provider.stub!(:set_options).and_return(:name=> "monkey")
+ @net_user.should_receive(:add).with(:name=> "monkey")
+ @provider.create_user
+ end
+ end
+
+ describe "manage_user" do
+ before(:each) do
+ @provider.stub!(:set_options).and_return(:name=> "monkey")
+ end
+
+ it "should call @net_user.update with the return of set_options" do
+ @net_user.should_receive(:update).with(:name=> "monkey")
+ @provider.manage_user
+ end
+ end
+
+ describe "when removing the user" do
+ it "should call @net_user.delete" do
+ @net_user.should_receive(:delete)
+ @provider.remove_user
+ end
+ end
+
+ describe "when checking if the user is locked" do
+ before(:each) do
+ @current_resource.password "abracadabra"
+ end
+
+ it "should return true if user is locked" do
+ @net_user.stub!(:check_enabled).and_return(true)
+ @provider.check_lock.should eql(true)
+ end
+
+ it "should return false if user is not locked" do
+ @net_user.stub!(:check_enabled).and_return(false)
+ @provider.check_lock.should eql(false)
+ end
+ end
+
+ describe "locking the user" do
+ it "should call @net_user.disable_account" do
+ @net_user.stub!(:check_enabled).and_return(true)
+ @net_user.should_receive(:disable_account)
+ @provider.lock_user
+ end
+ end
+
+ describe "unlocking the user" do
+ it "should call @net_user.enable_account" do
+ @net_user.stub!(:check_enabled).and_return(false)
+ @net_user.should_receive(:enable_account)
+ @provider.unlock_user
+ end
+ end
+end
diff --git a/spec/unit/provider/user_spec.rb b/spec/unit/provider/user_spec.rb
new file mode 100644
index 0000000000..4066ffd7fe
--- /dev/null
+++ b/spec/unit/provider/user_spec.rb
@@ -0,0 +1,477 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 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'
+
+EtcPwnamIsh = Struct.new(:name, :passwd, :uid, :gid, :gecos, :dir, :shell, :change, :uclass, :expire)
+EtcGrnamIsh = Struct.new(:name, :passwd, :gid, :mem)
+
+describe Chef::Provider::User do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::User.new("adam")
+ @new_resource.comment "Adam Jacob"
+ @new_resource.uid 1000
+ @new_resource.gid 1000
+ @new_resource.home "/home/adam"
+ @new_resource.shell "/usr/bin/zsh"
+
+ @current_resource = Chef::Resource::User.new("adam")
+ @current_resource.comment "Adam Jacob"
+ @current_resource.uid 1000
+ @current_resource.gid 1000
+ @current_resource.home "/home/adam"
+ @current_resource.shell "/usr/bin/zsh"
+
+ @provider = Chef::Provider::User.new(@new_resource, @run_context)
+ @provider.current_resource = @current_resource
+ end
+
+ describe "when first created" do
+ it "assume the user exists by default" do
+ @provider.user_exists.should eql(true)
+ end
+
+ it "does not know the locked state" do
+ @provider.locked.should eql(nil)
+ end
+ end
+
+ describe "executing load_current_resource" do
+ before(:each) do
+ @node = Chef::Node.new
+ #@new_resource = mock("Chef::Resource::User",
+ # :null_object => true,
+ # :username => "adam",
+ # :comment => "Adam Jacob",
+ # :uid => 1000,
+ # :gid => 1000,
+ # :home => "/home/adam",
+ # :shell => "/usr/bin/zsh",
+ # :password => nil,
+ # :updated => nil
+ #)
+ Chef::Resource::User.stub!(:new).and_return(@current_resource)
+ @pw_user = EtcPwnamIsh.new
+ @pw_user.name = "adam"
+ @pw_user.gid = 1000
+ @pw_user.uid = 1000
+ @pw_user.gecos = "Adam Jacob"
+ @pw_user.dir = "/home/adam"
+ @pw_user.shell = "/usr/bin/zsh"
+ @pw_user.passwd = "*"
+ Etc.stub!(:getpwnam).and_return(@pw_user)
+ end
+
+ it "should create a current resource with the same name as the new resource" do
+ @provider.load_current_resource
+ @provider.current_resource.name.should == 'adam'
+ end
+
+ it "should set the username of the current resource to the username of the new resource" do
+ @provider.load_current_resource
+ @current_resource.username.should == @new_resource.username
+ end
+
+ it "should look up the user in /etc/passwd with getpwnam" do
+ Etc.should_receive(:getpwnam).with(@new_resource.username).and_return(@pw_user)
+ @provider.load_current_resource
+ end
+
+ it "should set user_exists to false if the user is not found with getpwnam" do
+ Etc.should_receive(:getpwnam).and_raise(ArgumentError)
+ @provider.load_current_resource
+ @provider.user_exists.should eql(false)
+ end
+
+ # The mapping between the Chef::Resource::User and Getpwnam struct
+ user_attrib_map = {
+ :uid => :uid,
+ :gid => :gid,
+ :comment => :gecos,
+ :home => :dir,
+ :shell => :shell
+ }
+ user_attrib_map.each do |user_attrib, getpwnam_attrib|
+ it "should set the current resources #{user_attrib} based on getpwnam #{getpwnam_attrib}" do
+ @current_resource.should_receive(user_attrib).with(@pw_user.send(getpwnam_attrib))
+ @provider.load_current_resource
+ end
+ end
+
+ it "should attempt to convert the group gid if one has been supplied" do
+ @provider.should_receive(:convert_group_name)
+ @provider.load_current_resource
+ end
+
+ it "shouldn't try and convert the group gid if none has been supplied" do
+ @new_resource.stub!(:gid).and_return(nil)
+ @provider.should_not_receive(:convert_group_name)
+ @provider.load_current_resource
+ end
+
+ it "should return the current resource" do
+ @provider.load_current_resource.should eql(@current_resource)
+ end
+
+ describe "and running assertions" do
+ def self.shadow_lib_unavail?
+ begin
+ require 'rubygems'
+ require 'shadow'
+ rescue LoadError => e
+ pending "ruby-shadow gem not installed for dynamic load test"
+ true
+ else
+ false
+ end
+ end
+
+ before (:each) do
+ user = @pw_user.dup
+ user.name = "root"
+ user.passwd = "x"
+ @new_resource.password "some new password"
+ Etc.stub!(:getpwnam).and_return(user)
+ end
+
+ unless shadow_lib_unavail?
+ context "and we have the ruby-shadow gem" do
+ pending "and we are not root (rerun this again as root)", :requires_unprivileged_user => true
+
+ context "and we are root", :requires_root => true do
+ it "should pass assertions when ruby-shadow can be loaded" do
+ @provider.action = 'create'
+ original_method = @provider.method(:require)
+ @provider.should_receive(:require) { |*args| original_method.call(*args) }
+ passwd_info = Struct::PasswdEntry.new(:sp_namp => "adm ", :sp_pwdp => "$1$T0N0Q.lc$nyG6pFI3Dpqa5cxUz/57j0", :sp_lstchg => 14861, :sp_min => 0, :sp_max => 99999,
+ :sp_warn => 7, :sp_inact => -1, :sp_expire => -1, :sp_flag => -1)
+ Shadow::Passwd.should_receive(:getspnam).with("adam").and_return(passwd_info)
+ @provider.load_current_resource
+ @provider.define_resource_requirements
+ @provider.process_resource_requirements
+ end
+ end
+ end
+ end
+
+ it "should fail assertions when ruby-shadow cannot be loaded" do
+ @provider.should_receive(:require).with("shadow") { raise LoadError }
+ @provider.load_current_resource
+ @provider.define_resource_requirements
+ lambda {@provider.process_resource_requirements}.should raise_error Chef::Exceptions::MissingLibrary
+ end
+
+ end
+ end
+
+ describe "compare_user" do
+ before(:each) do
+ # @node = Chef::Node.new
+ # @new_resource = mock("Chef::Resource::User",
+ # :null_object => true,
+ # :username => "adam",
+ # :comment => "Adam Jacob",
+ # :uid => 1000,
+ # :gid => 1000,
+ # :home => "/home/adam",
+ # :shell => "/usr/bin/zsh",
+ # :password => nil,
+ # :updated => nil
+ # )
+ # @current_resource = mock("Chef::Resource::User",
+ # :null_object => true,
+ # :username => "adam",
+ # :comment => "Adam Jacob",
+ # :uid => 1000,
+ # :gid => 1000,
+ # :home => "/home/adam",
+ # :shell => "/usr/bin/zsh",
+ # :password => nil,
+ # :updated => nil
+ # )
+ # @provider = Chef::Provider::User.new(@node, @new_resource)
+ # @provider.current_resource = @current_resource
+ end
+
+ %w{uid gid comment home shell password}.each do |attribute|
+ it "should return true if #{attribute} doesn't match" do
+ @new_resource.should_receive(attribute).exactly(2).times.and_return(true)
+ @current_resource.should_receive(attribute).once.and_return(false)
+ @provider.compare_user.should eql(true)
+ end
+ end
+
+ it "should return false if the objects are identical" do
+ @provider.compare_user.should eql(false)
+ end
+ end
+
+ describe "action_create" do
+ before(:each) do
+ @provider.stub!(:load_current_resource)
+ # @current_resource = mock("Chef::Resource::User",
+ # :null_object => true,
+ # :username => "adam",
+ # :comment => "Adam Jacob",
+ # :uid => 1000,
+ # :gid => 1000,
+ # :home => "/home/adam",
+ # :shell => "/usr/bin/zsh",
+ # :password => nil,
+ # :updated => nil
+ # )
+ # @provider = Chef::Provider::User.new(@node, @new_resource)
+ # @provider.current_resource = @current_resource
+ # @provider.user_exists = false
+ # @provider.stub!(:create_user).and_return(true)
+ # @provider.stub!(:manage_user).and_return(true)
+ end
+
+ it "should call create_user if the user does not exist" do
+ @provider.user_exists = false
+ @provider.should_receive(:create_user).and_return(true)
+ @provider.action_create
+ @provider.converge
+ @new_resource.should be_updated
+ end
+
+ it "should call manage_user if the user exists and has mismatched attributes" do
+ @provider.user_exists = true
+ @provider.stub!(:compare_user).and_return(true)
+ @provider.should_receive(:manage_user).and_return(true)
+ @provider.action_create
+ @provider.converge
+ end
+
+ it "should set the the new_resources updated flag when it creates the user if we call manage_user" do
+ @provider.user_exists = true
+ @provider.stub!(:compare_user).and_return(true)
+ @provider.stub!(:manage_user).and_return(true)
+ @provider.action_create
+ @provider.converge
+ @new_resource.should be_updated
+ end
+ end
+
+ describe "action_remove" do
+ before(:each) do
+ @provider.stub!(:load_current_resource)
+ end
+
+ it "should not call remove_user if the user does not exist" do
+ @provider.user_exists = false
+ @provider.should_not_receive(:remove_user)
+ @provider.action_remove
+ @provider.converge
+ end
+
+ it "should call remove_user if the user exists" do
+ @provider.user_exists = true
+ @provider.should_receive(:remove_user)
+ @provider.action_remove
+ @provider.converge
+ end
+
+ it "should set the new_resources updated flag to true if the user is removed" do
+ @provider.user_exists = true
+ @provider.should_receive(:remove_user)
+ @provider.action_remove
+ @provider.converge
+ @new_resource.should be_updated
+ end
+ end
+
+ describe "action_manage" do
+ before(:each) do
+ @provider.stub!(:load_current_resource)
+ # @node = Chef::Node.new
+ # @new_resource = mock("Chef::Resource::User",
+ # :null_object => true
+ # )
+ # @current_resource = mock("Chef::Resource::User",
+ # :null_object => true
+ # )
+ # @provider = Chef::Provider::User.new(@node, @new_resource)
+ # @provider.current_resource = @current_resource
+ # @provider.user_exists = true
+ # @provider.stub!(:manage_user).and_return(true)
+ end
+
+ it "should run manage_user if the user exists and has mismatched attributes" do
+ @provider.should_receive(:compare_user).and_return(true)
+ @provider.should_receive(:manage_user).and_return(true)
+ @provider.action_manage
+ @provider.converge
+ end
+
+ it "should set the new resources updated flag to true if manage_user is called" do
+ @provider.stub!(:compare_user).and_return(true)
+ @provider.stub!(:manage_user).and_return(true)
+ @provider.action_manage
+ @provider.converge
+ @new_resource.should be_updated
+ end
+
+ it "should not run manage_user if the user does not exist" do
+ @provider.user_exists = false
+ @provider.should_not_receive(:manage_user)
+ @provider.action_manage
+ @provider.converge
+ end
+
+ it "should not run manage_user if the user exists but has no differing attributes" do
+ @provider.should_receive(:compare_user).and_return(false)
+ @provider.should_not_receive(:manage_user)
+ @provider.action_manage
+ @provider.converge
+ end
+ end
+
+ describe "action_modify" do
+ before(:each) do
+ @provider.stub!(:load_current_resource)
+ # @node = Chef::Node.new
+ # @new_resource = mock("Chef::Resource::User",
+ # :null_object => true
+ # )
+ # @current_resource = mock("Chef::Resource::User",
+ # :null_object => true
+ # )
+ # @provider = Chef::Provider::User.new(@node, @new_resource)
+ # @provider.current_resource = @current_resource
+ # @provider.user_exists = true
+ # @provider.stub!(:manage_user).and_return(true)
+ end
+
+ it "should run manage_user if the user exists and has mismatched attributes" do
+ @provider.should_receive(:compare_user).and_return(true)
+ @provider.should_receive(:manage_user).and_return(true)
+ @provider.action_modify
+ @provider.converge
+ end
+
+ it "should set the new resources updated flag to true if manage_user is called" do
+ @provider.stub!(:compare_user).and_return(true)
+ @provider.stub!(:manage_user).and_return(true)
+ @provider.action_modify
+ @provider.converge
+ @new_resource.should be_updated
+ end
+
+ it "should not run manage_user if the user exists but has no differing attributes" do
+ @provider.should_receive(:compare_user).and_return(false)
+ @provider.should_not_receive(:manage_user)
+ @provider.action_modify
+ @provider.converge
+ end
+
+ it "should raise a Chef::Exceptions::User if the user doesn't exist" do
+ @provider.user_exists = false
+ lambda { @provider.action = :modify; @provider.run_action }.should raise_error(Chef::Exceptions::User)
+ end
+ end
+
+
+ describe "action_lock" do
+ before(:each) do
+ @provider.stub!(:load_current_resource)
+ end
+ it "should lock the user if it exists and is unlocked" do
+ @provider.stub!(:check_lock).and_return(false)
+ @provider.should_receive(:lock_user).and_return(true)
+ @provider.action_lock
+ @provider.converge
+ end
+
+ it "should set the new resources updated flag to true if lock_user is called" do
+ @provider.stub!(:check_lock).and_return(false)
+ @provider.should_receive(:lock_user)
+ @provider.action_lock
+ @provider.converge
+ @new_resource.should be_updated
+ end
+
+ it "should raise a Chef::Exceptions::User if we try and lock a user that does not exist" do
+ @provider.user_exists = false
+ @provider.action = :lock
+
+ lambda { @provider.run_action }.should raise_error(Chef::Exceptions::User)
+ end
+ end
+
+ describe "action_unlock" do
+ before(:each) do
+ @provider.stub!(:load_current_resource)
+ # @node = Chef::Node.new
+ # @new_resource = mock("Chef::Resource::User",
+ # :null_object => true
+ # )
+ # @current_resource = mock("Chef::Resource::User",
+ # :null_object => true
+ # )
+ # @provider = Chef::Provider::User.new(@node, @new_resource)
+ # @provider.current_resource = @current_resource
+ # @provider.user_exists = true
+ # @provider.stub!(:check_lock).and_return(true)
+ # @provider.stub!(:unlock_user).and_return(true)
+ end
+
+ it "should unlock the user if it exists and is locked" do
+ @provider.stub!(:check_lock).and_return(true)
+ @provider.should_receive(:unlock_user).and_return(true)
+ @provider.action_unlock
+ @provider.converge
+ @new_resource.should be_updated
+ end
+
+ it "should raise a Chef::Exceptions::User if we try and unlock a user that does not exist" do
+ @provider.user_exists = false
+ @provider.action = :unlock
+ lambda { @provider.run_action }.should raise_error(Chef::Exceptions::User)
+ end
+ end
+
+ describe "convert_group_name" do
+ before do
+ @new_resource.gid('999')
+ @group = EtcGrnamIsh.new('wheel', '*', 999, [])
+ end
+
+ it "should lookup the group name locally" do
+ Etc.should_receive(:getgrnam).with("999").and_return(@group)
+ @provider.convert_group_name.should == 999
+ end
+
+ it "should raise an error if we can't translate the group name during resource assertions" do
+ Etc.should_receive(:getgrnam).and_raise(ArgumentError)
+ @provider.define_resource_requirements
+ @provider.convert_group_name
+ lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::User)
+ end
+
+ it "should set the new resources gid to the integerized version if available" do
+ Etc.should_receive(:getgrnam).with("999").and_return(@group)
+ @provider.convert_group_name
+ @new_resource.gid.should == 999
+ end
+ end
+end