summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorLamont Granquist <lamont@opscode.com>2013-03-15 15:17:07 -0700
committerLamont Granquist <lamont@opscode.com>2013-03-15 16:39:35 -0700
commitb480aca6eb0167928cabfb47291a91ddcb50e434 (patch)
treeb2375ab1e65ed8535ee4dd9a5fb552a5f5285a72 /spec
parent6b653284a81195d13c6561371fdcf70bb0fa0ee7 (diff)
downloadchef-b480aca6eb0167928cabfb47291a91ddcb50e434.tar.gz
refactoring of file providers
Diffstat (limited to 'spec')
-rw-r--r--spec/functional/resource/cookbook_file_spec.rb2
-rw-r--r--spec/functional/resource/directory_spec.rb2
-rw-r--r--spec/functional/resource/file_spec.rb16
-rw-r--r--spec/functional/resource/remote_directory_spec.rb2
-rw-r--r--spec/functional/resource/remote_file_spec.rb16
-rw-r--r--spec/functional/resource/template_spec.rb17
-rw-r--r--spec/unit/provider/file_spec.rb867
-rw-r--r--spec/unit/util/backup_spec.rb151
-rw-r--r--spec/unit/util/diff_spec.rb254
9 files changed, 898 insertions, 429 deletions
diff --git a/spec/functional/resource/cookbook_file_spec.rb b/spec/functional/resource/cookbook_file_spec.rb
index 9977cd6c99..d61668853c 100644
--- a/spec/functional/resource/cookbook_file_spec.rb
+++ b/spec/functional/resource/cookbook_file_spec.rb
@@ -32,7 +32,7 @@ describe Chef::Resource::CookbookFile do
content
end
- let(:default_mode) { "600" }
+ let(:default_mode) { ((0100666 - File.umask) & 07777).to_s(8) }
it_behaves_like "a securable resource with reporting"
diff --git a/spec/functional/resource/directory_spec.rb b/spec/functional/resource/directory_spec.rb
index 9ae0503336..0401f506c3 100644
--- a/spec/functional/resource/directory_spec.rb
+++ b/spec/functional/resource/directory_spec.rb
@@ -23,7 +23,7 @@ describe Chef::Resource::Directory do
let(:directory_base) { "directory_spec" }
- let(:default_mode) { "755" }
+ let(:default_mode) { ((0100777 - File.umask) & 07777).to_s(8) }
def create_resource
events = Chef::EventDispatch::Dispatcher.new
diff --git a/spec/functional/resource/file_spec.rb b/spec/functional/resource/file_spec.rb
index 2c18d07520..7da15ff2e5 100644
--- a/spec/functional/resource/file_spec.rb
+++ b/spec/functional/resource/file_spec.rb
@@ -58,6 +58,22 @@ describe Chef::Resource::File do
it_behaves_like "a securable resource with reporting"
+ describe "when running action :create without content" do
+ before do
+ resource_without_content.run_action(:create)
+ end
+
+ context "and the target file does not exist" do
+ it "creates the file" do
+ File.should exist(path)
+ end
+
+ it "is marked updated by last action" do
+ resource_without_content.should be_updated_by_last_action
+ end
+ end
+ end
+
describe "when running action :touch" do
context "and the target file does not exist" do
before do
diff --git a/spec/functional/resource/remote_directory_spec.rb b/spec/functional/resource/remote_directory_spec.rb
index b4e26a59b2..10d4d973e9 100644
--- a/spec/functional/resource/remote_directory_spec.rb
+++ b/spec/functional/resource/remote_directory_spec.rb
@@ -22,7 +22,7 @@ describe Chef::Resource::RemoteDirectory do
include_context Chef::Resource::Directory
let(:directory_base) { "directory_spec" }
- let(:default_mode) { "755" }
+ let(:default_mode) { ((0100777 - File.umask) & 07777).to_s(8) }
def create_resource
cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks"))
diff --git a/spec/functional/resource/remote_file_spec.rb b/spec/functional/resource/remote_file_spec.rb
index fbb921d48c..0354cc1aa3 100644
--- a/spec/functional/resource/remote_file_spec.rb
+++ b/spec/functional/resource/remote_file_spec.rb
@@ -37,21 +37,7 @@ describe Chef::Resource::RemoteFile do
create_resource
end
- let(:default_mode) do
- # TODO: Lots of ugly here :(
- # RemoteFile uses FileUtils.cp. FileUtils does a copy by opening the
- # destination file and writing to it. Before 1.9.3, it does not preserve
- # the mode of the copied file. In 1.9.3 and after, it does. So we have to
- # figure out what the default mode ought to be via heuristic.
-
- t = Tempfile.new("get-the-mode")
- path = t.path
- path_2 = t.path + "fileutils-mode-test"
- FileUtils.cp(path, path_2)
- t.close
- m = File.stat(path_2).mode
- (07777 & m).to_s(8)
- end
+ let(:default_mode) { ((0100666 - File.umask) & 07777).to_s(8) }
before(:all) do
@server = TinyServer::Manager.new
diff --git a/spec/functional/resource/template_spec.rb b/spec/functional/resource/template_spec.rb
index 0987aabf05..ae568a496a 100644
--- a/spec/functional/resource/template_spec.rb
+++ b/spec/functional/resource/template_spec.rb
@@ -49,22 +49,7 @@ describe Chef::Resource::Template do
create_resource
end
- let(:default_mode) do
- # TODO: Lots of ugly here :(
- # RemoteFile uses FileUtils.cp. FileUtils does a copy by opening the
- # destination file and writing to it. Before 1.9.3, it does not preserve
- # the mode of the copied file. In 1.9.3 and after, it does. So we have to
- # figure out what the default mode ought to be via heuristic.
-
- t = Tempfile.new("get-the-mode")
- path = t.path
- path_2 = t.path + "fileutils-mode-test"
- FileUtils.cp(path, path_2)
- t.close
- m = File.stat(path_2).mode
- (07777 & m).to_s(8)
- end
-
+ let(:default_mode) { ((0100666 - File.umask) & 07777).to_s(8) }
it_behaves_like "a file resource"
diff --git a/spec/unit/provider/file_spec.rb b/spec/unit/provider/file_spec.rb
index 5b1cbbbdb8..7e99e6755b 100644
--- a/spec/unit/provider/file_spec.rb
+++ b/spec/unit/provider/file_spec.rb
@@ -1,6 +1,7 @@
#
# Author:: Adam Jacob (<adam@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Author:: Lamont Granquist (<lamont@opscode.com>)
+# Copyright:: Copyright (c) 2008-2013 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,499 +17,575 @@
# 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")))
+ # Mocksplosion
+
+ let(:node) { double('Chef::Node') }
+ let(:events) { double('Chef::Events').as_null_object } # mock all the methods
+ let(:run_context) { double('Chef::RunContext', :node => node, :events => events) }
+ let(:enclosing_directory) { File.expand_path(File.join(CHEF_SPEC_DATA, "templates")) }
+ let(:resource_path) { File.expand_path(File.join(enclosing_directory, "seattle.txt")) }
- @provider = Chef::Provider::File.new(@resource, @run_context)
+ let(:resource) do
+ # need to check for/against mutating state within the new_resource, so don't mock
+ resource = Chef::Resource::File.new("seattle")
+ resource.path(resource_path)
+ resource
end
- it "should return a Chef::Provider::File" do
- @provider.should be_a_kind_of(Chef::Provider::File)
+ # Subject
+
+ let(:provider) do
+ Chef::Provider::File.new(resource, run_context)
end
- it "should store the resource passed to new as new_resource" do
- @provider.new_resource.should eql(@resource)
+ # Filesystem stubs
+
+ def setup_normal_file
+ File.stub!(:exists?).with(resource_path).and_return(true)
+ File.stub!(:directory?).with(resource_path).and_return(false)
+ File.stub!(:directory?).with(enclosing_directory).and_return(true)
+ File.stub!(:writable?).with(resource_path).and_return(true)
+ File.stub!(:symlink?).with(resource_path).and_return(false)
end
- it "should store the node passed to new as node" do
- @provider.node.should eql(@node)
+ def setup_missing_file
+ File.stub!(:exists?).with(resource_path).and_return(false)
+ File.stub!(:directory?).with(resource_path).and_return(false)
+ File.stub!(:directory?).with(enclosing_directory).and_return(true)
+ File.stub!(:writable?).with(resource_path).and_return(false)
+ File.stub!(:symlink?).with(resource_path).and_return(false)
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)
+ def setup_symlink
+ File.stub!(:exists?).with(resource_path).and_return(true)
+ File.stub!(:directory?).with(resource_path).and_return(false)
+ File.stub!(:directory?).with(enclosing_directory).and_return(true)
+ File.stub!(:writable?).with(resource_path).and_return(true)
+ File.stub!(:symlink?).with(resource_path).and_return(true)
end
- describe "examining file security metadata on Unix" do
- before do
- Chef::Platform.stub!(:windows?).and_return(false)
- 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(1).times.with(@resource.path).and_return(stat_struct)
+ def setup_unwritable_file
+ File.stub!(:exists?).with(resource_path).and_return(true)
+ File.stub!(:directory?).with(resource_path).and_return(false)
+ File.stub!(:directory?).with(enclosing_directory).and_return(true)
+ File.stub!(:writable?).with(resource_path).and_return(false)
+ File.stub!(:symlink?).with(resource_path).and_return(false)
+ end
- # test execution
+ def setup_missing_enclosing_directory
+ File.stub!(:exists?).with(resource_path).and_return(false)
+ File.stub!(:directory?).with(resource_path).and_return(false)
+ File.stub!(:directory?).with(enclosing_directory).and_return(false)
+ File.stub!(:writable?).with(resource_path).and_return(false)
+ File.stub!(:symlink?).with(resource_path).and_return(false)
+ end
- Etc.should_receive(:getgrgid).with(0).and_return(mock("Group Ent", :name => "wheel"))
- Etc.should_receive(:getpwuid).with(0).and_return(mock("User Ent", :name => "root"))
+ # Tests
- # test execution
- @provider.load_current_resource
+ it "should return a Chef::Provider::File" do
+ provider.should be_a_kind_of(Chef::Provider::File)
+ end
- # post-condition checks
- @provider.current_resource.mode.should == "0600"
- @provider.current_resource.owner.should == "root"
- @provider.current_resource.group.should == "wheel"
- end
+ it "should store the resource passed to new as new_resource" do
+ provider.new_resource.should eql(resource)
+ 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(1).times.with(@resource.path).and_return(stat_struct)
+ it "should store the node passed to new as node" do
+ provider.node.should eql(node)
+ end
- @provider.new_resource.group(1)
- @provider.new_resource.owner(1)
- @provider.new_resource.mode(0644)
+ context "when loading the current resource" do
- # test execution
- @provider.load_current_resource
+ context "when running load_current_resource and the file exists" do
+ before do
+ File.should_receive(:exist?).with(resource_path).at_least(:once).and_return(true)
+ provider.load_current_resource
+ end
- # post-condition checks
- @provider.new_resource.group.should == 1
- @provider.new_resource.owner.should == 1
- @provider.new_resource.mode.should == 0644
- end
+ it "should load a current resource based on the one specified at construction" do
+ provider.current_resource.should be_a_kind_of(Chef::Resource::File)
+ end
- context "when the new_resource does not specify the desired access control" do
- it "records access control information in the new resource after modifying the file" 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).once.with(@provider.new_resource.path).and_return(stat_struct)
- ::File.should_receive(:directory?).once.with(@provider.new_resource.path).and_return(false)
+ it "the loaded current_resource name should be the same as the resource name" do
+ provider.current_resource.name.should eql(resource.name)
+ end
- Etc.should_receive(:getpwuid).with(0).and_return(mock("User Ent", :name => "root"))
- Etc.should_receive(:getgrgid).with(0).and_return(mock("Group Ent", :name => "wheel"))
+ it "the loaded current_resource path should be the same as the resoure path" do
+ provider.current_resource.path.should eql(resource.path)
+ end
- @provider.new_resource.group(nil)
- @provider.new_resource.owner(nil)
- @provider.new_resource.mode(nil)
+ it "the loaded current_resource content should be nil" do
+ provider.current_resource.content.should eql(nil)
+ end
+ end
- # test exectution
- @provider.update_new_file_state
+ context "when running load_current_resource and the file does not exist" do
+ before do
+ File.should_receive(:exist?).with(resource_path).at_least(:once).and_return(false)
+ provider.load_current_resource
+ end
- # post-condition checks
- @provider.new_resource.group.should == "wheel"
- @provider.new_resource.owner.should == "root"
- @provider.new_resource.mode.should == "0600"
+ it "the current_resource should be a Chef::Resource::File" do
+ provider.current_resource.should be_a_kind_of(Chef::Resource::File)
end
- end
- end
- describe "when reporting security metadata on windows" do
+ it "the current_resource name should be the same as the resource name" do
+ provider.current_resource.name.should eql(resource.name)
+ end
- it "records the file owner" do
- pending
- end
+ it "the current_resource path should be the same as the resource path" do
+ provider.current_resource.path.should eql(resource.path)
+ end
- it "records rights for each user in the ACL" do
- pending
+ it "the loaded current_resource content should be nil" do
+ provider.current_resource.content.should eql(nil)
+ end
end
- it "records deny_rights for each user in the ACL" do
- pending
- end
- end
+ context "examining file security metadata on Unix with a file that exists" do
+ before do
+ # fake that we're on unix even if we're on windows
+ Chef::Platform.stub!(:windows?).and_return(false)
+ # mock up the filesystem to behave like unix
+ stat_struct = mock("::File.stat", :mode => 0600, :uid => 0, :gid => 0, :mtime => 10000)
+ File.should_receive(:exist?).with(resource_path).at_least(:once).and_return(true)
+ File.should_receive(:stat).with(resource.path).at_least(:once).and_return(stat_struct)
+ Etc.stub!(:getgrgid).with(0).and_return(mock("Group Ent", :name => "wheel"))
+ Etc.stub!(:getpwuid).with(0).and_return(mock("User Ent", :name => "root"))
+ 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
+ context "when the new_resource does not specify any state" do
+ before do
+ provider.load_current_resource
+ 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 load the permissions into the current_resource" do
+ provider.current_resource.mode.should == "0600"
+ provider.current_resource.owner.should == "root"
+ provider.current_resource.group.should == "wheel"
+ end
- it "should compare the current content with the requested content" do
- @provider.load_current_resource
+ it "should not set the new_resource permissions" do
+ provider.new_resource.group.should be_nil
+ provider.new_resource.owner.should be_nil
+ provider.new_resource.mode.should be_nil
+ end
+ end
- @provider.new_resource.content "foobar"
- @provider.compare_content.should eql(false)
+ context "when the new_resource explicitly specifies resource state as numbers" do
+ before do
+ resource.owner(1)
+ resource.group(1)
+ resource.mode(0644)
+ provider.load_current_resource
+ end
- @provider.new_resource.content IO.read(@resource.path)
- @provider.compare_content.should eql(true)
- end
+ it "should load the permissions into the current_resource as numbers (BUT DOESN'T, BUG?)" do
+ # FIXME: inconsistency, hmmmm....
+ provider.current_resource.mode.should == "0600"
+ provider.current_resource.owner.should == 0
+ provider.current_resource.group.should == 0
+ 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)
- # checksum check
- File.should_receive(:open).with(@provider.new_resource.path, "rb").and_yield(io)
- File.should_receive(:open).with(@provider.new_resource.path, "w").and_yield(io)
- @provider.set_content
- io.string.should == "foobar"
- end
+ it "should not set the new_resource permissions" do
+ provider.new_resource.group.should == 1
+ provider.new_resource.owner.should == 1
+ provider.new_resource.mode.should == 0644
+ end
+ 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)
- # Checksum check:
- File.should_receive(:open).with(@resource.path, "rb").and_yield(StringIO.new(@resource.content))
- 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
+ context "when the new_resource explicitly specifies resource state as symbols" do
+ before do
+ resource.owner("macklemore")
+ resource.group("seattlehiphop")
+ resource.mode("0321")
+ provider.load_current_resource
+ 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 load the permissions into the current_resource as symbols" do
+ provider.current_resource.mode.should == "0600"
+ provider.current_resource.owner.should == "root"
+ provider.current_resource.group.should == "wheel"
+ 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 not set the new_resource permissions" do
+ provider.new_resource.group.should == "seattlehiphop"
+ provider.new_resource.owner.should == "macklemore"
+ provider.new_resource.mode.should == "0321"
+ end
+ 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
+ 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
+ context "examining file security metadata on Unix with a file that does not exist" do
+ before do
+ # fake that we're on unix even if we're on windows
+ Chef::Platform.stub!(:windows?).and_return(false)
+ File.should_receive(:exist?).with(resource_path).at_least(:once).and_return(false)
+ 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
+ context "when the new_resource does not specify any state" do
+ before do
+ provider.load_current_resource
+ 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 "the current_resource permissions should be nil" do
+ provider.current_resource.mode.should be_nil
+ provider.current_resource.owner.should be_nil
+ provider.current_resource.group.should be_nil
+ 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 set the new_resource permissions" do
+ provider.new_resource.group.should be_nil
+ provider.new_resource.owner.should be_nil
+ provider.new_resource.mode.should be_nil
+ end
+ 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
+ context "when the new_resource explicitly specifies resource state" do
+ before do
+ resource.owner(63945)
+ resource.group(51948)
+ resource.mode(0123)
+ provider.load_current_resource
+ 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 "the current_resource permissions should be nil" do
+ provider.current_resource.mode.should be_nil
+ provider.current_resource.owner.should be_nil
+ provider.current_resource.group.should be_nil
+ 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
+ it "should not set the new_resource permissions" do
+ provider.new_resource.group.should == 51948
+ provider.new_resource.owner.should == 63945
+ provider.new_resource.mode.should == 0123
+ end
+ end
+ end
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
+ context "when loading the new_resource after the run" do
- describe "when the enclosing directory does not exist" do
before do
- @resource.path("/tmp/no-such-path/file.txt")
+ # fake that we're on unix even if we're on windows
+ Chef::Platform.stub!(:windows?).and_return(false)
+ # mock up the filesystem to behave like unix
+ stat_struct = mock("::File.stat", :mode => 0600, :uid => 0, :gid => 0, :mtime => 10000)
+ File.stub!(:stat).with(resource.path).and_return(stat_struct)
+ Etc.stub!(:getgrgid).with(0).and_return(mock("Group Ent", :name => "wheel"))
+ Etc.stub!(:getpwuid).with(0).and_return(mock("User Ent", :name => "root"))
+ provider.send(:load_resource_attributes_from_file, resource)
end
- it "raises a specific error describing the problem" do
- lambda {@provider.run_action(:create)}.should raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+ it "new_resource should record the new permission information" do
+ provider.new_resource.group.should == "wheel"
+ provider.new_resource.owner.should == "root"
+ provider.new_resource.mode.should == "0600"
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
+ context "when reporting security metadata on windows (FIXME: moar tests)" do
+
+ it "records the file owner" do
+ pending
+ end
+
+ it "records rights for each user in the ACL" do
+ pending
end
- it "should call action create if the does not file exist" do
- @resource.path("/tmp/example-dir/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)
- ::File.stub!(:directory?).with("/tmp/example-dir/non_existant_file").and_return(false)
- ::File.stub!(:directory?).with("/tmp/example-dir").and_return(true)
- @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
+ it "records deny_rights for each user in the ACL" do
+ pending
end
end
- describe "when a diff is requested", :uses_diff => true do
-
- before(:each) do
- @original_config = Chef::Config.hash_dup
+ context "define_resource_requirements" do
+ context "when the enclosing directory does not exist" do
+ before { setup_missing_enclosing_directory }
+
+ [:create, :create_if_missing, :touch].each do |action|
+ describe "action #{action}" do
+ it "raises EnclosingDirectoryDoesNotExist" do
+ lambda {provider.run_action(action)}.should raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+ end
+
+ it "does not raise an exception in why-run mode" do
+ Chef::Config[:why_run] = true
+ lambda {provider.run_action(action)}.should_not raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+ Chef::Config[:why_run] = false
+ end
+ end
+ end
end
- after(:each) do
- Chef::Config.configuration = @original_config if @original_config
+ context "when the file exists but is not deletable" do
+ before { setup_unwritable_file }
+
+ it "action delete raises InsufficientPermissions" do
+ lambda {provider.run_action(:delete)}.should raise_error(Chef::Exceptions::InsufficientPermissions)
+ end
+
+ it "action delete also raises InsufficientPermissions in why-run mode" do
+ Chef::Config[:why_run] = true
+ lambda {provider.run_action(:delete)}.should raise_error(Chef::Exceptions::InsufficientPermissions)
+ Chef::Config[:why_run] = false
+ end
end
+ end
- describe "when identifying files as binary or text" do
+ context "action create" do
+ it "should create the file, update its contents and then set the acls on the file" do
+ setup_missing_file
+ provider.should_receive(:do_create_file)
+ provider.should_receive(:do_contents_changes)
+ provider.should_receive(:do_acl_changes)
+ provider.should_receive(:load_resource_attributes_from_file).twice # current_resource + new_resource
+ provider.run_action(:create)
+ end
- 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
+ context "do_create_file" do
+ context "when the file exists" do
+ before { setup_normal_file }
+ it "should not create the file" do
+ provider.deployment_strategy.should_not_receive(:create).with(resource_path)
+ provider.send(:do_create_file)
+ provider.send(:file_created?).should == 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
+ context "when the file does not exist" do
+ before { setup_missing_file }
+ it "should create the file" do
+ provider.deployment_strategy.should_receive(:create).with(resource_path)
+ provider.send(:do_create_file)
+ provider.send(:file_created?).should == true
end
end
+ end
+
+ context "do_contents_changes" do
+ context "when there is content to deploy" do
+ before do
+ tempfile = double('Tempfile', :path => "/tmp/foo-bar-baz")
+ provider.send(:content).should_receive(:tempfile).at_least(:once).and_return(tempfile)
+ File.should_receive(:exists?).with("/tmp/foo-bar-baz").and_return(true)
+ tempfile.should_receive(:unlink).once
+ 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
+ context "when the contents have changed" do
+ let (:tempfile_path) { "/tmp/foo-bar-baz" }
+ let (:tempfile_md5) { "71f3811d0472fbef15d90a779615b254" }
+ let (:diff_for_reporting) { "+++\n---\n+foo\n-bar\n" }
+ before do
+ provider.stub!(:contents_changed?).and_return(true)
+ diff = double('Diff', :for_output => ['+++','---','+foo','-bar'],
+ :for_reporting => diff_for_reporting )
+ diff.stub!(:diff).with(resource_path, tempfile_path).and_return(true)
+ provider.should_receive(:diff).at_least(:once).and_return(diff)
+ provider.should_receive(:checksum).with(tempfile_path).and_return(tempfile_md5)
+ provider.deployment_strategy.should_receive(:deploy).with(tempfile_path, resource_path)
+ end
+ context "when the file was created" do
+ before { provider.should_receive(:file_created?).at_least(:once).and_return(true) }
+ it "does not backup the file and does not produce a diff for reporting" do
+ provider.should_not_receive(:backup)
+ provider.send(:do_contents_changes)
+ resource.diff.should be_nil
+ end
+ end
+ context "when the file was not created" do
+ before { provider.should_receive(:file_created?).at_least(:once).and_return(false) }
+ it "backs up the file and produces a diff for reporting" do
+ provider.should_receive(:backup)
+ provider.send(:do_contents_changes)
+ resource.diff.should == diff_for_reporting
+ end
+ end
end
+
+ it "does nothing when the contents have not changed" do
+ provider.stub!(:contents_changed?).and_return(false)
+ provider.should_not_receive(:diff)
+ provider.send(:do_contents_changes)
+ end
+ end
+
+ it "does nothing when there is no content to deploy (tempfile returned from contents is nil)" do
+ provider.send(:content).should_receive(:tempfile).at_least(:once).and_return(nil)
+ provider.should_not_receive(:diff)
+ lambda{ provider.send(:do_contents_changes) }.should_not raise_error
end
+ it "raises an exception when the content object returns a tempfile with a nil path" do
+ tempfile = double('Tempfile', :path => nil)
+ provider.send(:content).should_receive(:tempfile).at_least(:once).and_return(tempfile)
+ lambda{ provider.send(:do_contents_changes) }.should raise_error
+ end
+
+ it "raises an exception when the content object returns a tempfile that does not exist" do
+ tempfile = double('Tempfile', :path => "/tmp/foo-bar-baz")
+ provider.send(:content).should_receive(:tempfile).at_least(:once).and_return(tempfile)
+ File.should_receive(:exists?).with("/tmp/foo-bar-baz").and_return(false)
+ lambda{ provider.send(:do_contents_changes) }.should raise_error
+ 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
+ context "do_acl_changes" do
+ it "needs tests" do
+ pending
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
+ # 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)
+ # # checksum check
+ # File.should_receive(:open).with(@provider.new_resource.path, "rb").and_yield(io)
+ # File.should_receive(:open).with(@provider.new_resource.path, "w").and_yield(io)
+ # @provider.set_content
+ # 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)
+ # # Checksum check:
+ # File.should_receive(:open).with(@resource.path, "rb").and_yield(StringIO.new(@resource.content))
+ # 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
+ end
+
+ context "action delete" do
+ context "when the file exists" do
+ context "when the file is writable" do
+ context "when the file is not a symlink" do
+ before { setup_normal_file }
+ it "should backup and delete the file and be updated by the last action" do
+ provider.should_receive(:backup).at_least(:once).and_return(true)
+ File.should_receive(:delete).with(resource_path).and_return(true)
+ provider.run_action(:delete)
+ resource.should be_updated_by_last_action
+ end
+ end
+ context "when the file is a symlink" do
+ before { setup_symlink }
+ it "should not backup the symlink" do
+ provider.should_not_receive(:backup)
+ File.should_receive(:delete).with(resource_path).and_return(true)
+ provider.run_action(:delete)
+ resource.should be_updated_by_last_action
+ end
+ end
+ end
+ context "when the file is not writable" do
+ before { setup_unwritable_file }
+ it "should not try to backup or delete the file, and should not be updated by last action" do
+ provider.should_not_receive(:backup)
+ File.should_not_receive(:delete)
+ lambda { provider.run_action(:delete) }.should raise_error()
+ resource.should_not be_updated_by_last_action
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
+ context "when the file does not exist" do
+ before { setup_missing_file }
+
+ it "should not try to backup or delete the file, and should not be updated by last action" do
+ provider.should_not_receive(:backup)
+ File.should_not_receive(:delete)
+ lambda { provider.run_action(:delete) }.should_not raise_error()
+ resource.should_not be_updated_by_last_action
end
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
+ context "action touch" do
+ context "when the file does not exist" do
+ before { setup_missing_file }
+ it "should update the atime/mtime on action_touch" do
+ File.should_receive(:utime).once
+ provider.should_receive(:action_create)
+ provider.run_action(:touch)
+ resource.should be_updated_by_last_action
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
+ context "when the file exists" do
+ before { setup_normal_file }
+ it "should update the atime/mtime on action_touch" do
+ File.should_receive(:utime).once
+ provider.should_receive(:action_create)
+ provider.run_action(:touch)
+ resource.should be_updated_by_last_action
end
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
+ context "action create_if_missing" do
+ context "when the file does not exist" do
+ before { setup_missing_file }
+ it "should call action_create" do
+ provider.should_receive(:action_create)
+ provider.run_action(:create_if_missing)
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
+ context "when the file exists" do
+ before { setup_normal_file }
+ it "should not call action_create" do
+ provider.should_not_receive(:action_create)
+ provider.run_action(:create_if_missing)
+ end
end
+
end
+
end
+
diff --git a/spec/unit/util/backup_spec.rb b/spec/unit/util/backup_spec.rb
new file mode 100644
index 0000000000..e08d5c846f
--- /dev/null
+++ b/spec/unit/util/backup_spec.rb
@@ -0,0 +1,151 @@
+#
+# Author:: Lamont Granquist (<lamont@opscode.com>)
+# Copyright:: Copyright (c) 2013 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::Util::Backup do
+ before(:all) do
+ @original_config = Chef::Config.configuration
+ end
+
+ after(:all) do
+ Chef::Config.configuration.replace(@original_config)
+ end
+
+ let (:tempfile) do
+ Tempfile.new("chef-util-backup-spec-test")
+ end
+
+ before(:each) do
+ @new_resource = mock("new_resource")
+ @new_resource.should_receive(:path).at_least(:once).and_return(tempfile.path)
+ @backup = Chef::Util::Backup.new(@new_resource)
+ end
+
+ it "should store the resource passed to new as new_resource" do
+ @backup.new_resource.should eql(@new_resource)
+ end
+
+ describe "for cases when we don't want to back anything up" do
+
+ before(:each) do
+ @backup.should_not_receive(:do_backup)
+ end
+
+ it "should not attempt to backup a file if :backup is false" do
+ @new_resource.should_receive(:backup).at_least(:once).and_return(false)
+ @backup.backup!
+ end
+
+ it "should not attempt to backup a file if :backup == 0" do
+ @new_resource.should_receive(:backup).at_least(:once).and_return(0)
+ @backup.backup!
+ end
+
+ it "should not attempt to backup a file if it does not exist" do
+ @new_resource.should_receive(:backup).at_least(:once).and_return(1)
+ File.should_receive(:exist?).with(tempfile.path).at_least(:once).and_return(false)
+ @backup.backup!
+ end
+
+ end
+
+ describe "for cases when we want to back things up" do
+ before(:each) do
+ @backup.should_receive(:do_backup)
+ end
+
+ describe "when the number of backups is specified as 1" do
+ before(:each) do
+ @new_resource.should_receive(:backup).at_least(:once).and_return(1)
+ end
+
+ it "should not delete anything if this is the only backup" do
+ @backup.should_receive(:sorted_backup_files).and_return(['a'])
+ @backup.should_not_receive(:delete_backup)
+ @backup.backup!
+ end
+
+ it "should keep only 1 backup copy" do
+ @backup.should_receive(:sorted_backup_files).and_return(['a', 'b', 'c'])
+ @backup.should_receive(:delete_backup).with('b')
+ @backup.should_receive(:delete_backup).with('c')
+ @backup.backup!
+ end
+ end
+
+ describe "when the number of backups is specified as 2" do
+ before(:each) do
+ @new_resource.should_receive(:backup).at_least(:once).and_return(2)
+ end
+
+ it "should not delete anything if we only have one other backup" do
+ @backup.should_receive(:sorted_backup_files).and_return(['a', 'b'])
+ @backup.should_not_receive(:delete_backup)
+ @backup.backup!
+ end
+
+ it "should keep only 2 backup copies" do
+ @backup.should_receive(:sorted_backup_files).and_return(['a', 'b', 'c', 'd'])
+ @backup.should_receive(:delete_backup).with('c')
+ @backup.should_receive(:delete_backup).with('d')
+ @backup.backup!
+ end
+ end
+ end
+
+ describe "backup_filename" do
+ it "should return a timestamped path" do
+ @backup.should_receive(:path).and_return('/a/b/c.txt')
+ @backup.send(:backup_filename).should =~ %r|^/a/b/c.txt.chef-\d{14}$|
+ end
+ it "should strip the drive letter off for windows" do
+ @backup.should_receive(:path).and_return('c:\a\b\c.txt')
+ @backup.send(:backup_filename).should =~ %r|^\\a\\b\\c.txt.chef-\d{14}$|
+ end
+ it "should strip the drive letter off for windows (with forwardslashes)" do
+ @backup.should_receive(:path).and_return('c:/a/b/c.txt')
+ @backup.send(:backup_filename).should =~ %r|^/a/b/c.txt.chef-\d{14}$|
+ end
+ end
+
+ describe "backup_path" do
+ it "uses the file's directory when Chef::Config[:file_backup_path] is nil" do
+ @backup.should_receive(:path).and_return('/a/b/c.txt')
+ Chef::Config[:file_backup_path] = nil
+ @backup.send(:backup_path).should =~ %r|^/a/b/c.txt.chef-\d{14}$|
+ end
+
+ it "uses the configured Chef::Config[:file_backup_path]" do
+ @backup.should_receive(:path).and_return('/a/b/c.txt')
+ Chef::Config[:file_backup_path] = '/backupdir'
+ @backup.send(:backup_path).should =~ %r|^/backupdir[\\/]+a/b/c.txt.chef-\d{14}$|
+ end
+
+ it "uses the configured Chef::Config[:file_backup_path] and strips the drive on windows" do
+ @backup.should_receive(:path).and_return('c:\\a\\b\\c.txt')
+ Chef::Config[:file_backup_path] = 'c:\backupdir'
+ @backup.send(:backup_path).should =~ %r|^c:\\backupdir[\\/]+a\\b\\c.txt.chef-\d{14}$|
+ end
+ end
+
+ # it "should keep the same ownership on backed up files" do (FIXME: functional test)
+
+end
diff --git a/spec/unit/util/diff_spec.rb b/spec/unit/util/diff_spec.rb
new file mode 100644
index 0000000000..ffb0cd6455
--- /dev/null
+++ b/spec/unit/util/diff_spec.rb
@@ -0,0 +1,254 @@
+#
+# Author:: Lamont Granquist (<lamont@opscode.com>)
+# Copyright:: Copyright (c) 2013 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::Util::Diff, :uses_diff => true do
+ before(:all) do
+ @original_config = Chef::Config.hash_dup
+ end
+
+ after(:all) do
+ Chef::Config.configuration = @original_config if @original_config
+ end
+
+ let!(:old_tempfile) { Tempfile.new("chef-util-diff-spec") }
+ let!(:new_tempfile) { Tempfile.new("chef-util-diff-spec") }
+ let!(:old_file) { old_tempfile.path }
+ let!(:new_file) { new_tempfile.path }
+
+ let(:differ) do # subject
+ differ = Chef::Util::Diff.new
+ differ.diff(old_file, new_file)
+ differ
+ end
+
+ it "should return a Chef::Util::Diff" do
+ expect(differ).to be_a_kind_of(Chef::Util::Diff)
+ end
+
+ it "should raise an exception if the old_file does not exist" do
+ old_tempfile.unlink
+ expect { differ.diff(old_file, new_file) }.to raise_error
+ end
+
+ it "should raise an exception if the new_file does not exist" do
+ new_tempfile.unlink
+ expect { differ.diff(old_file, new_file) }.to raise_error
+ end
+
+ describe "when the two files exist with no content" do
+ it "calling for_output should return the error message" do
+ expect(differ.for_output).to eql(["(no diff)"])
+ end
+
+ it "calling for_reporting should be nil" do
+ expect(differ.for_reporting).to be_nil
+ end
+ end
+
+ describe "when diffs are disabled" do
+ before do
+ Chef::Config[:diff_disabled] = true
+ end
+
+ after do
+ Chef::Config[:diff_disabled] = false
+ end
+
+ it "calling for_output should return the error message" do
+ expect(differ.for_output).to eql( [ "(diff output suppressed by config)" ] )
+ end
+
+ it "calling for_reporting should be nil" do
+ expect(differ.for_reporting).to be_nil
+ end
+ end
+
+ describe "when the old_file has binary content" do
+ before do
+ old_tempfile.write("\x01\xff")
+ old_tempfile.close
+ end
+
+ it "calling for_output should return the error message" do
+ expect(differ.for_output).to eql( [ "(current file is binary, diff output suppressed)" ] )
+ end
+
+ it "calling for_reporting should be nil" do
+ expect(differ.for_reporting).to be_nil
+ end
+ end
+
+ describe "when the new_file has binary content" do
+ before do
+ new_tempfile.write("\x01\xff")
+ new_tempfile.close
+ end
+
+ it "calling for_output should return the error message" do
+ expect(differ.for_output).to eql( [ "(new content is binary, diff output suppressed)" ])
+ end
+
+ it "calling for_reporting should be nil" do
+ expect(differ.for_reporting).to be_nil
+ end
+ end
+
+ describe "when testing the diff_filesize_threshold" do
+ before do
+ @diff_filesize_threshold_saved = Chef::Config[:diff_filesize_threshold]
+ Chef::Config[:diff_filesize_threshold] = 10
+ end
+
+ after do
+ Chef::Config[:diff_filesize_threshold] = @diff_filesize_threshold_saved
+ end
+
+ describe "when the old_file goes over the threshold" do
+ before do
+ old_tempfile.write("But thats what you get when Wu-Tang raised you")
+ old_tempfile.close
+ end
+
+ it "calling for_output should return the error message" do
+ expect(differ.for_output).to eql( [ "(file sizes exceed 10 bytes, diff output suppressed)" ])
+ end
+
+ it "calling for_reporting should be nil" do
+ expect(differ.for_reporting).to be_nil
+ end
+ end
+
+ describe "when the new_file goes over the threshold" do
+ before do
+ new_tempfile.write("But thats what you get when Wu-Tang raised you")
+ new_tempfile.close
+ end
+
+ it "calling for_output should return the error message" do
+ expect(differ.for_output).to eql( [ "(file sizes exceed 10 bytes, diff output suppressed)" ])
+ end
+
+ it "calling for_reporting should be nil" do
+ expect(differ.for_reporting).to be_nil
+ end
+ end
+ end
+
+ describe "when generating a valid diff" do
+ before do
+ old_tempfile.write("foo")
+ old_tempfile.close
+ new_tempfile.write("bar")
+ new_tempfile.close
+ end
+
+ it "calling for_output should return a unified diff" do
+ differ.for_output.size.should eql(5)
+ differ.for_output.join("\\n").should match(/^--- .*\\n\+\+\+ .*\\n@@ .* @@\\n-foo\\n\+bar$/)
+ end
+
+ it "calling for_reporting should return a unified diff" do
+ differ.for_reporting.should match(/^--- .*\\n\+\+\+ .*\\n@@ .* @@\\n-foo\\n\+bar$/)
+ end
+
+ describe "when the diff output is too long" do
+
+ before do
+ @diff_output_threshold_saved = Chef::Config[:diff_output_threshold]
+ Chef::Config[:diff_output_threshold] = 10
+ end
+
+ after do
+ Chef::Config[:diff_output_threshold] = @diff_output_threshold_saved
+ end
+
+ it "calling for_output should return the error message" do
+ expect(differ.for_output).to eql(["(long diff of over 10 characters, diff output suppressed)"])
+ end
+
+ it "calling for_reporting should be nil" do
+ expect(differ.for_reporting).to be_nil
+ end
+ end
+ end
+
+ describe "when errors are thrown from shell_out" do
+ before do
+ differ.stub!(:shell_out).and_raise('boom')
+ differ.diff(old_file, new_file)
+ end
+
+ it "calling for_output should return the error message" do
+ expect(differ.for_output).to eql(["Could not determine diff. Error: boom"])
+ end
+
+ it "calling for_reporting should be nil" do
+ expect(differ.for_reporting).to be_nil
+ end
+ end
+
+ describe "when shell_out returns stderr output" do
+ before do
+ @result = mock('result', :stdout => "", :stderr => "boom")
+ differ.stub!(:shell_out).and_return(@result)
+ differ.diff(old_file, new_file)
+ end
+
+ it "calling for_output should return the error message" do
+ expect(differ.for_output).to eql(["Could not determine diff. Error: boom"])
+ end
+
+ it "calling for_reporting should be nil" do
+ expect(differ.for_reporting).to be_nil
+ end
+ end
+
+ describe "when checking if files are binary or text" do
+
+ it "should identify zero-length files as text" do
+ Tempfile.new("chef-util-diff-spec") do |file|
+ differ.is_binary?(file.path).should be_false
+ end
+ end
+
+ it "should identify text files as text" do
+ Tempfile.new("chef-util-diff-spec") do |file|
+ file.write("This is a text file.")
+ file.write("With more than one line.")
+ file.write("And lets make sure that other printable chars work too: ~!@\#$%^&*()`:\"<>?{}|_+,./;'[]\\-=")
+ file.close
+ differ.is_binary?(file.path).should be_false
+ end
+ end
+
+ it "should identify a null-terminated string files as binary" do
+ Tempfile.new("chef-util-diff-spec") do |file|
+ file.write("This is a binary file.\0")
+ file.close
+ differ.is_binary?(file.path).should be_false
+ end
+ end
+
+ end
+
+end
+