summaryrefslogtreecommitdiff
path: root/spec/functional
diff options
context:
space:
mode:
Diffstat (limited to 'spec/functional')
-rw-r--r--spec/functional/knife/cookbook_delete_spec.rb162
-rw-r--r--spec/functional/knife/exec_spec.rb62
-rw-r--r--spec/functional/knife/ssh_spec.rb211
-rw-r--r--spec/functional/resource/directory_spec.rb39
-rw-r--r--spec/functional/resource/file_spec.rb69
-rw-r--r--spec/functional/resource/link_spec.rb572
-rw-r--r--spec/functional/resource/remote_directory_spec.rb116
-rw-r--r--spec/functional/resource/remote_file_spec.rb58
-rw-r--r--spec/functional/resource/template_spec.rb70
-rw-r--r--spec/functional/run_lock_spec.rb90
-rw-r--r--spec/functional/tiny_server_spec.rb77
11 files changed, 1526 insertions, 0 deletions
diff --git a/spec/functional/knife/cookbook_delete_spec.rb b/spec/functional/knife/cookbook_delete_spec.rb
new file mode 100644
index 0000000000..54081263f0
--- /dev/null
+++ b/spec/functional/knife/cookbook_delete_spec.rb
@@ -0,0 +1,162 @@
+#
+# 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 'tiny_server'
+
+describe Chef::Knife::CookbookDelete do
+ before(:all) do
+ @original_config = Chef::Config.hash_dup
+
+ Thin::Logging.silent = true
+
+ @server = TinyServer::Manager.new
+ @server.start
+ end
+
+ before(:each) do
+ @knife = Chef::Knife::CookbookDelete.new
+ @api = TinyServer::API.instance
+ @api.clear
+
+ Chef::Config[:node_name] = nil
+ Chef::Config[:client_key] = nil
+ Chef::Config[:chef_server_url] = 'http://localhost:9000'
+ end
+
+ after(:all) do
+ Chef::Config.configuration = @original_config
+ @server.stop
+ end
+
+ context "when the the cookbook doesn't exist" do
+ before do
+ @log_output = StringIO.new
+
+ Chef::Log.logger = Logger.new(@log_output)
+ Chef::Log.level = :debug
+
+ @knife.name_args = %w{no-such-cookbook}
+ @api.get("/cookbooks/no-such-cookbook", 404, {'error'=>'dear Tim, no. -Sent from my iPad'}.to_json)
+ end
+
+ it "logs an error and exits" do
+ @knife.ui.stub!(:stderr).and_return(@log_output)
+ lambda {@knife.run}.should raise_error(SystemExit)
+ @log_output.string.should match(/Cannot find a cookbook named no-such-cookbook to delete/)
+ end
+
+ end
+
+ context "when there is only one version of a cookbook" do
+ before do
+ @knife.name_args = %w{obsolete-cookbook}
+ @cookbook_list = {'obsolete-cookbook' => { 'versions' => ['version' => '1.0.0']} }
+ @api.get("/cookbooks/obsolete-cookbook", 200, @cookbook_list.to_json)
+ end
+
+ it "asks for confirmation, then deletes the cookbook" do
+ stdin, stdout = StringIO.new("y\n"), StringIO.new
+ @knife.ui.stub!(:stdin).and_return(stdin)
+ @knife.ui.stub!(:stdout).and_return(stdout)
+
+ cb100_deleted = false
+ @api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" }
+
+ @knife.run
+
+ stdout.string.should match(/#{Regexp.escape('Do you really want to delete obsolete-cookbook version 1.0.0? (Y/N)')}/)
+ cb100_deleted.should be_true
+ end
+
+ it "asks for confirmation before purging" do
+ @knife.config[:purge] = true
+
+ stdin, stdout = StringIO.new("y\ny\n"), StringIO.new
+ @knife.ui.stub!(:stdin).and_return(stdin)
+ @knife.ui.stub!(:stdout).and_return(stdout)
+
+ cb100_deleted = false
+ @api.delete("/cookbooks/obsolete-cookbook/1.0.0?purge=true", 200) { cb100_deleted = true; "[\"true\"]" }
+
+ @knife.run
+
+ stdout.string.should match(/#{Regexp.escape('Are you sure you want to purge files')}/)
+ stdout.string.should match(/#{Regexp.escape('Do you really want to delete obsolete-cookbook version 1.0.0? (Y/N)')}/)
+ cb100_deleted.should be_true
+
+ end
+
+ end
+
+ context "when there are several versions of a cookbook" do
+ before do
+ @knife.name_args = %w{obsolete-cookbook}
+ versions = ['1.0.0', '1.1.0', '1.2.0']
+ with_version = lambda { |version| { 'version' => version } }
+ @cookbook_list = {'obsolete-cookbook' => { 'versions' => versions.map(&with_version) } }
+ @api.get("/cookbooks/obsolete-cookbook", 200, @cookbook_list.to_json)
+ end
+
+ it "deletes all versions of a cookbook when given the '-a' flag" do
+ @knife.config[:all] = true
+ @knife.config[:yes] = true
+ cb100_deleted = cb110_deleted = cb120_deleted = nil
+ @api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" }
+ @api.delete("/cookbooks/obsolete-cookbook/1.1.0", 200) { cb110_deleted = true; "[\"true\"]" }
+ @api.delete("/cookbooks/obsolete-cookbook/1.2.0", 200) { cb120_deleted = true; "[\"true\"]" }
+ @knife.run
+
+ cb100_deleted.should be_true
+ cb110_deleted.should be_true
+ cb120_deleted.should be_true
+ end
+
+ it "asks which version to delete and deletes that when not given the -a flag" do
+ cb100_deleted = cb110_deleted = cb120_deleted = nil
+ @api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" }
+ stdin, stdout = StringIO.new, StringIO.new
+ @knife.ui.stub!(:stdin).and_return(stdin)
+ @knife.ui.stub!(:stdout).and_return(stdout)
+ stdin << "1\n"
+ stdin.rewind
+ @knife.run
+ cb100_deleted.should be_true
+ stdout.string.should match(/Which version\(s\) do you want to delete\?/)
+ end
+
+ it "deletes all versions of the cookbook when not given the -a flag and the user chooses to delete all" do
+ cb100_deleted = cb110_deleted = cb120_deleted = nil
+ @api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" }
+ @api.delete("/cookbooks/obsolete-cookbook/1.1.0", 200) { cb110_deleted = true; "[\"true\"]" }
+ @api.delete("/cookbooks/obsolete-cookbook/1.2.0", 200) { cb120_deleted = true; "[\"true\"]" }
+
+ stdin, stdout = StringIO.new("4\n"), StringIO.new
+ @knife.ui.stub!(:stdin).and_return(stdin)
+ @knife.ui.stub!(:stdout).and_return(stdout)
+
+ @knife.run
+
+ cb100_deleted.should be_true
+ cb110_deleted.should be_true
+ cb120_deleted.should be_true
+ end
+
+ end
+
+end
diff --git a/spec/functional/knife/exec_spec.rb b/spec/functional/knife/exec_spec.rb
new file mode 100644
index 0000000000..fa4a448fbb
--- /dev/null
+++ b/spec/functional/knife/exec_spec.rb
@@ -0,0 +1,62 @@
+#
+# 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 'tiny_server'
+
+describe Chef::Knife::Exec do
+ before(:all) do
+ @original_config = Chef::Config.hash_dup
+
+ Thin::Logging.silent = false
+
+ @server = TinyServer::Manager.new#(:debug => true)
+ @server.start
+ end
+
+ before(:each) do
+ @knife = Chef::Knife::Exec.new
+ @api = TinyServer::API.instance
+ @api.clear
+
+ Chef::Config[:node_name] = nil
+ Chef::Config[:client_key] = nil
+ Chef::Config[:chef_server_url] = 'http://localhost:9000'
+
+ $output = StringIO.new
+ end
+
+ after(:all) do
+ Chef::Config.configuration = @original_config
+ @server.stop
+ end
+
+ pending "executes a script in the context of the chef-shell main context", :ruby_18_only
+
+ it "executes a script in the context of the chef-shell main context", :ruby_19_only do
+ @node = Chef::Node.new
+ @node.name("ohai-world")
+ response = {"rows" => [@node],"start" => 0,"total" => 1}
+ @api.get(%r{^/search/node}, 200, response.to_json)
+ code = "$output.puts nodes.all.inspect"
+ @knife.config[:exec] = code
+ @knife.run
+ $output.string.should match(%r{node\[ohai-world\]})
+ end
+
+end
diff --git a/spec/functional/knife/ssh_spec.rb b/spec/functional/knife/ssh_spec.rb
new file mode 100644
index 0000000000..8f87e53bf7
--- /dev/null
+++ b/spec/functional/knife/ssh_spec.rb
@@ -0,0 +1,211 @@
+#
+# 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 'tiny_server'
+
+describe Chef::Knife::Ssh do
+
+ before(:all) do
+ @original_config = Chef::Config.hash_dup
+ Chef::Knife::Ssh.load_deps
+ Thin::Logging.silent = true
+ @server = TinyServer::Manager.new
+ @server.start
+ end
+
+ after(:all) do
+ Chef::Config.configuration = @original_config
+ @server.stop
+ end
+
+ describe "identity file" do
+ context "when knife[:ssh_identity_file] is set" do
+ before do
+ setup_knife(['*:*', 'uptime'])
+ Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/aws.rsa"
+ end
+
+ it "uses the ssh_identity_file" do
+ @knife.run
+ @knife.config[:identity_file].should == "~/.ssh/aws.rsa"
+ end
+ end
+
+ context "when knife[:ssh_identity_file] is set and frozen" do
+ before do
+ setup_knife(['*:*', 'uptime'])
+ Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/aws.rsa".freeze
+ end
+
+ it "uses the ssh_identity_file" do
+ @knife.run
+ @knife.config[:identity_file].should == "~/.ssh/aws.rsa"
+ end
+ end
+
+ context "when -i is provided" do
+ before do
+ setup_knife(['-i ~/.ssh/aws.rsa', '*:*', 'uptime'])
+ Chef::Config[:knife][:ssh_identity_file] = nil
+ end
+
+ it "should use the value on the command line" do
+ @knife.run
+ @knife.config[:identity_file].should == "~/.ssh/aws.rsa"
+ end
+
+ it "should override what is set in knife.rb" do
+ Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/other.rsa"
+ @knife.run
+ @knife.config[:identity_file].should == "~/.ssh/aws.rsa"
+ end
+ end
+
+ context "when knife[:ssh_identity_file] is not provided]" do
+ before do
+ setup_knife(['*:*', 'uptime'])
+ Chef::Config[:knife][:ssh_identity_file] = nil
+ end
+
+ it "uses the default" do
+ @knife.run
+ @knife.config[:identity_file].should == nil
+ end
+ end
+ end
+
+ describe "user" do
+ context "when knife[:ssh_user] is set" do
+ before do
+ setup_knife(['*:*', 'uptime'])
+ Chef::Config[:knife][:ssh_user] = "ubuntu"
+ end
+
+ it "uses the ssh_user" do
+ @knife.run
+ @knife.config[:ssh_user].should == "ubuntu"
+ end
+ end
+
+ context "when knife[:ssh_user] is set and frozen" do
+ before do
+ setup_knife(['*:*', 'uptime'])
+ Chef::Config[:knife][:ssh_user] = "ubuntu".freeze
+ end
+
+ it "uses the ssh_user" do
+ @knife.run
+ @knife.config[:ssh_user].should == "ubuntu"
+ end
+ end
+
+ context "when -x is provided" do
+ before do
+ setup_knife(['-x ubuntu', '*:*', 'uptime'])
+ Chef::Config[:knife][:ssh_user] = nil
+ end
+
+ it "should use the value on the command line" do
+ @knife.run
+ @knife.config[:ssh_user].should == "ubuntu"
+ end
+
+ it "should override what is set in knife.rb" do
+ Chef::Config[:knife][:ssh_user] = "root"
+ @knife.run
+ @knife.config[:ssh_user].should == "ubuntu"
+ end
+ end
+
+ context "when knife[:ssh_user] is not provided]" do
+ before do
+ setup_knife(['*:*', 'uptime'])
+ Chef::Config[:knife][:ssh_user] = nil
+ end
+
+ it "uses the default" do
+ @knife.run
+ @knife.config[:ssh_user].should == nil
+ end
+ end
+ end
+
+ describe "attribute" do
+ context "when knife[:ssh_attribute] is set" do
+ before do
+ setup_knife(['*:*', 'uptime'])
+ Chef::Config[:knife][:ssh_attribute] = "ec2.public_hostname"
+ end
+
+ it "uses the ssh_attribute" do
+ @knife.run
+ @knife.config[:attribute].should == "ec2.public_hostname"
+ end
+ end
+
+ context "when knife[:ssh_attribute] is not provided]" do
+ before do
+ setup_knife(['*:*', 'uptime'])
+ Chef::Config[:knife][:ssh_attribute] = nil
+ end
+
+ it "uses the default" do
+ @knife.run
+ @knife.config[:attribute].should == "fqdn"
+ end
+ end
+
+ context "when -a ec2.public_ipv4 is provided" do
+ before do
+ setup_knife(['-a ec2.public_hostname', '*:*', 'uptime'])
+ Chef::Config[:knife][:ssh_attribute] = nil
+ end
+
+ it "should use the value on the command line" do
+ @knife.run
+ @knife.config[:attribute].should == "ec2.public_hostname"
+ end
+
+ it "should override what is set in knife.rb" do
+ # This is the setting imported from knife.rb
+ Chef::Config[:knife][:ssh_attribute] = "fqdn"
+ # Then we run knife with the -a flag, which sets the above variable
+ setup_knife(['-a ec2.public_hostname', '*:*', 'uptime'])
+ @knife.run
+ @knife.config[:attribute].should == "ec2.public_hostname"
+ end
+ end
+ end
+
+ def setup_knife(params=[])
+ @knife = Chef::Knife::Ssh.new(params)
+ @knife.stub!(:ssh_command).and_return { [] }
+ @api = TinyServer::API.instance
+ @api.clear
+
+ Chef::Config[:node_name] = nil
+ Chef::Config[:client_key] = nil
+ Chef::Config[:chef_server_url] = 'http://localhost:9000'
+
+ @api.get("/search/node?q=*:*&sort=X_CHEF_id_CHEF_X%20asc&start=0&rows=1000", 200) {
+ %({"total":1, "start":0, "rows":[{"name":"i-xxxxxxxx", "json_class":"Chef::Node", "automatic":{"fqdn":"the.fqdn", "ec2":{"public_hostname":"the_public_hostname"}},"recipes":[]}]})
+ }
+ end
+
+end
diff --git a/spec/functional/resource/directory_spec.rb b/spec/functional/resource/directory_spec.rb
new file mode 100644
index 0000000000..5777a114fc
--- /dev/null
+++ b/spec/functional/resource/directory_spec.rb
@@ -0,0 +1,39 @@
+#
+# Author:: Seth Chisamore (<schisamo@opscode.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::Resource::Directory do
+ include_context Chef::Resource::Directory
+
+ let(:directory_base) { "directory_spec" }
+
+ def create_resource
+ events = Chef::EventDispatch::Dispatcher.new
+ node = Chef::Node.new
+ run_context = Chef::RunContext.new(node, {}, events)
+ Chef::Resource::Directory.new(path, run_context)
+ end
+
+ let!(:resource) do
+ create_resource
+ end
+
+ it_behaves_like "a directory resource"
+
+end
diff --git a/spec/functional/resource/file_spec.rb b/spec/functional/resource/file_spec.rb
new file mode 100644
index 0000000000..d587e5f93b
--- /dev/null
+++ b/spec/functional/resource/file_spec.rb
@@ -0,0 +1,69 @@
+#
+# Author:: Seth Chisamore (<schisamo@opscode.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::Resource::File do
+ include_context Chef::Resource::File
+
+ let(:file_base) { "file_spec" }
+ let(:expected_content) { "Don't fear the ruby." }
+
+ def create_resource
+ events = Chef::EventDispatch::Dispatcher.new
+ node = Chef::Node.new
+ run_context = Chef::RunContext.new(node, {}, events)
+ resource = Chef::Resource::File.new(path, run_context)
+ resource.content(expected_content)
+ resource
+ end
+
+ let!(:resource) do
+ create_resource
+ end
+
+ it_behaves_like "a file resource"
+
+ context "when the target file does not exist" do
+ it "it creates the file when the :touch action is run" do
+ resource.run_action(:touch)
+ File.should exist(path)
+ end
+ end
+
+ context "when the target file has the correct content" do
+ before(:each) do
+ File.open(path, "w") { |f| f.print expected_content }
+ end
+
+ it "updates the mtime/atime of the file when the :touch action is run" do
+ expected_mtime = File.stat(path).mtime
+ expected_atime = File.stat(path).atime
+ sleep 1
+ resource.run_action(:touch)
+ File.stat(path).mtime.should > expected_mtime
+ File.stat(path).atime.should > expected_atime
+ end
+
+ it "does not change the content when :touch action is run" do
+ expected_checksum = sha256_checksum(path)
+ resource.run_action(:touch)
+ sha256_checksum(path).should == expected_checksum
+ end
+ end
+end
diff --git a/spec/functional/resource/link_spec.rb b/spec/functional/resource/link_spec.rb
new file mode 100644
index 0000000000..b80dc72d49
--- /dev/null
+++ b/spec/functional/resource/link_spec.rb
@@ -0,0 +1,572 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.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'
+
+if windows?
+ require 'chef/win32/file' #probably need this in spec_helper
+end
+
+describe Chef::Resource::Link do
+ let(:file_base) { "file_spec" }
+
+ let(:base_dir) do
+ if windows?
+ Chef::ReservedNames::Win32::File.get_long_path_name(Dir.tmpdir.gsub('/', '\\'))
+ else
+ Dir.tmpdir
+ end
+ end
+
+ let(:to) do
+ File.join(base_dir, make_tmpname("to_spec", nil))
+ end
+ let(:target_file) do
+ File.join(base_dir, make_tmpname("from_spec", nil))
+ end
+
+ after(:each) do
+ # TODO Windows fails to clean up some symlinks.
+ begin
+ FileUtils.rm_r(to) if File.exists?(to)
+ FileUtils.rm_r(target_file) if File.exists?(target_file)
+ FileUtils.rm_r(CHEF_SPEC_BACKUP_PATH) if File.exists?(CHEF_SPEC_BACKUP_PATH)
+ rescue
+ puts "Could not remove a file: #{$!}"
+ end
+ end
+
+ def canonicalize(path)
+ windows? ? path.gsub('/', '\\') : path
+ end
+
+ def symlink(a, b)
+ if windows?
+ Chef::ReservedNames::Win32::File.symlink(a, b)
+ else
+ File.symlink(a, b)
+ end
+ end
+ def symlink?(file)
+ if windows?
+ Chef::ReservedNames::Win32::File.symlink?(file)
+ else
+ File.symlink?(file)
+ end
+ end
+ def readlink(file)
+ if windows?
+ Chef::ReservedNames::Win32::File.readlink(file)
+ else
+ File.readlink(file)
+ end
+ end
+ def link(a, b)
+ if windows?
+ Chef::ReservedNames::Win32::File.link(a, b)
+ else
+ File.link(a, b)
+ end
+ end
+
+ def create_resource
+ node = Chef::Node.new
+ events = Chef::EventDispatch::Dispatcher.new
+ cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks"))
+ cookbook_collection = Chef::CookbookCollection.new(Chef::CookbookLoader.new(cookbook_repo))
+ run_context = Chef::RunContext.new(node, cookbook_collection, events)
+ resource = Chef::Resource::Link.new(target_file, run_context)
+ resource.to(to)
+ resource
+ end
+
+ let!(:resource) do
+ create_resource
+ end
+
+ shared_examples_for 'delete errors out' do
+ it 'delete errors out' do
+ lambda { resource.run_action(:delete) }.should raise_error(Chef::Exceptions::Link)
+ (File.exist?(target_file) || symlink?(target_file)).should be_true
+ end
+ end
+
+ shared_context 'delete is noop' do
+ describe 'the :delete action' do
+ before(:each) do
+ @info = []
+ Chef::Log.stub!(:info) { |msg| @info << msg }
+ resource.run_action(:delete)
+ end
+
+ it 'leaves the file deleted' do
+ File.exist?(target_file).should be_false
+ symlink?(target_file).should be_false
+ end
+ it 'does not mark the resource updated' do
+ resource.should_not be_updated
+ end
+ it 'does not log that it deleted' do
+ @info.include?("link[#{target_file}] deleted").should be_false
+ end
+ end
+ end
+
+ shared_context 'delete succeeds' do
+ describe 'the :delete action' do
+ before(:each) do
+ @info = []
+ Chef::Log.stub!(:info) { |msg| @info << msg }
+ resource.run_action(:delete)
+ end
+
+ it 'deletes the file' do
+ File.exist?(target_file).should be_false
+ symlink?(target_file).should be_false
+ end
+ it 'marks the resource updated' do
+ resource.should be_updated
+ end
+ it 'logs that it deleted' do
+ @info.include?("link[#{target_file}] deleted").should be_true
+ end
+ end
+ end
+
+ shared_context 'create symbolic link succeeds' do
+ describe 'the :create action' do
+ before(:each) do
+ @info = []
+ Chef::Log.stub!(:info) { |msg| @info << msg }
+ resource.run_action(:create)
+ end
+
+ it 'links to the target file' do
+ symlink?(target_file).should be_true
+ readlink(target_file).should == canonicalize(to)
+ end
+ it 'marks the resource updated' do
+ resource.should be_updated
+ end
+ it 'logs that it created' do
+ @info.include?("link[#{target_file}] created").should be_true
+ end
+ end
+ end
+
+ shared_context 'create symbolic link is noop' do
+ describe 'the :create action' do
+ before(:each) do
+ @info = []
+ Chef::Log.stub!(:info) { |msg| @info << msg }
+ resource.run_action(:create)
+ end
+
+ it 'leaves the file linked' do
+ symlink?(target_file).should be_true
+ readlink(target_file).should == canonicalize(to)
+ end
+ it 'does not mark the resource updated' do
+ resource.should_not be_updated
+ end
+ it 'does not log that it created' do
+ @info.include?("link[#{target_file}] created").should be_false
+ end
+ end
+ end
+
+ shared_context 'create hard link succeeds' do
+ describe 'the :create action' do
+ before(:each) do
+ @info = []
+ Chef::Log.stub!(:info) { |msg| @info << msg }
+ resource.run_action(:create)
+ end
+ it 'preserves the hard link' do
+ File.exists?(target_file).should be_true
+ symlink?(target_file).should be_false
+ # Writing to one hardlinked file should cause both
+ # to have the new value.
+ IO.read(to).should == IO.read(target_file)
+ File.open(to, "w") { |file| file.write('wowzers') }
+ IO.read(target_file).should == 'wowzers'
+ end
+ it 'marks the resource updated' do
+ resource.should be_updated
+ end
+ it 'logs that it created' do
+ @info.include?("link[#{target_file}] created").should be_true
+ end
+ end
+ end
+
+ shared_context 'create hard link is noop' do
+ describe 'the :create action' do
+ before(:each) do
+ @info = []
+ Chef::Log.stub!(:info) { |msg| @info << msg }
+ resource.run_action(:create)
+ end
+ it 'links to the target file' do
+ File.exists?(target_file).should be_true
+ symlink?(target_file).should be_false
+ # Writing to one hardlinked file should cause both
+ # to have the new value.
+ IO.read(to).should == IO.read(target_file)
+ File.open(to, "w") { |file| file.write('wowzers') }
+ IO.read(target_file).should == 'wowzers'
+ end
+ it 'does not mark the resource updated' do
+ resource.should_not be_updated
+ end
+ it 'does not log that it created' do
+ @info.include?("link[#{target_file}] created").should be_false
+ end
+ end
+ end
+
+ context "is symbolic" do
+
+ context 'when the link destination is a file' do
+ before(:each) do
+ File.open(to, "w") do |file|
+ file.write('woohoo')
+ end
+ end
+ context 'and the link does not yet exist' do
+ include_context 'create symbolic link succeeds'
+ include_context 'delete is noop'
+ end
+ context 'and the link already exists and is a symbolic link' do
+ context 'pointing at the target' do
+ before(:each) do
+ symlink(to, target_file)
+ symlink?(target_file).should be_true
+ readlink(target_file).should == canonicalize(to)
+ end
+ include_context 'create symbolic link is noop'
+ include_context 'delete succeeds'
+ it 'the :delete action does not delete the target file' do
+ resource.run_action(:delete)
+ File.exists?(to).should be_true
+ end
+ end
+ context 'pointing somewhere else' do
+ before(:each) do
+ @other_target = File.join(base_dir, make_tmpname('other_spec', nil))
+ File.open(@other_target, 'w') { |file| file.write('eek') }
+ symlink(@other_target, target_file)
+ symlink?(target_file).should be_true
+ readlink(target_file).should == @other_target
+ end
+ after(:each) do
+ File.delete(@other_target)
+ end
+ include_context 'create symbolic link succeeds'
+ include_context 'delete succeeds'
+ it 'the :delete action does not delete the target file' do
+ resource.run_action(:delete)
+ File.exists?(to).should be_true
+ end
+ end
+ context 'pointing nowhere' do
+ before(:each) do
+ nonexistent = File.join(base_dir, make_tmpname('nonexistent_spec', nil))
+ symlink(nonexistent, target_file)
+ symlink?(target_file).should be_true
+ readlink(target_file).should == nonexistent
+ end
+ include_context 'create symbolic link succeeds'
+ include_context 'delete succeeds'
+ end
+ end
+ context 'and the link already exists and is a hard link to the file' do
+ before(:each) do
+ link(to, target_file)
+ File.exists?(target_file).should be_true
+ symlink?(target_file).should be_false
+ end
+ include_context 'create symbolic link succeeds'
+ it_behaves_like 'delete errors out'
+ end
+ context 'and the link already exists and is a file' do
+ before(:each) do
+ File.open(target_file, 'w') { |file| file.write('eek') }
+ end
+ include_context 'create symbolic link succeeds'
+ it_behaves_like 'delete errors out'
+ end
+ context 'and the link already exists and is a directory' do
+ before(:each) do
+ Dir.mkdir(target_file)
+ end
+ it 'create errors out' do
+ if windows?
+ lambda { resource.run_action(:create) }.should raise_error(Errno::EACCES)
+ elsif os_x? or solaris? or freebsd?
+ lambda { resource.run_action(:create) }.should raise_error(Errno::EPERM)
+ else
+ lambda { resource.run_action(:create) }.should raise_error(Errno::EISDIR)
+ end
+ end
+ it_behaves_like 'delete errors out'
+ end
+ context 'and the link already exists and is not writeable to this user', :pending do
+ end
+ it_behaves_like 'a securable resource' do
+ let(:path) { target_file }
+ def allowed_acl(sid, expected_perms)
+ [ ACE.access_allowed(sid, expected_perms[:specific]) ]
+ end
+ def denied_acl(sid, expected_perms)
+ [ ACE.access_denied(sid, expected_perms[:specific]) ]
+ end
+ end
+ end
+ context 'when the link destination is a directory' do
+ before(:each) do
+ Dir.mkdir(to)
+ end
+ # On Windows, readlink fails to open the link. FILE_FLAG_OPEN_REPARSE_POINT
+ # might help, from http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
+ context 'and the link does not yet exist' do
+ include_context 'create symbolic link succeeds'
+ include_context 'delete is noop'
+ end
+ end
+ context "when the link destination is a symbolic link" do
+ context 'to a file that exists' do
+ before(:each) do
+ @other_target = File.join(base_dir, make_tmpname("other_spec", nil))
+ File.open(@other_target, "w") { |file| file.write("eek") }
+ symlink(@other_target, to)
+ symlink?(to).should be_true
+ readlink(to).should == @other_target
+ end
+ after(:each) do
+ File.delete(@other_target)
+ end
+ context 'and the link does not yet exist' do
+ include_context 'create symbolic link succeeds'
+ include_context 'delete is noop'
+ end
+ end
+ context 'to a file that does not exist' do
+ before(:each) do
+ @other_target = File.join(base_dir, make_tmpname("other_spec", nil))
+ symlink(@other_target, to)
+ symlink?(to).should be_true
+ readlink(to).should == @other_target
+ end
+ context 'and the link does not yet exist' do
+ include_context 'create symbolic link succeeds'
+ include_context 'delete is noop'
+ end
+ end
+ end
+ context "when the link destination is not readable to this user", :pending do
+ end
+ context "when the link destination does not exist" do
+ include_context 'create symbolic link succeeds'
+ include_context 'delete is noop'
+ end
+
+ {
+ '../' => 'with a relative link destination',
+ '' => 'with a bare filename for the link destination'
+ }.each do |prefix, desc|
+ context desc do
+ let(:to) { "#{prefix}#{File.basename(absolute_to)}" }
+ let(:absolute_to) { File.join(base_dir, make_tmpname("to_spec", nil)) }
+ before(:each) do
+ resource.to(to)
+ end
+ context 'when the link does not yet exist' do
+ include_context 'create symbolic link succeeds'
+ include_context 'delete is noop'
+ end
+ context 'when the link already exists and points at the target' do
+ before(:each) do
+ symlink(to, target_file)
+ symlink?(target_file).should be_true
+ readlink(target_file).should == canonicalize(to)
+ end
+ include_context 'create symbolic link is noop'
+ include_context 'delete succeeds'
+ end
+ context 'when the link already exists and points at the target with an absolute path' do
+ before(:each) do
+ symlink(absolute_to, target_file)
+ symlink?(target_file).should be_true
+ readlink(target_file).should == canonicalize(absolute_to)
+ end
+ include_context 'create symbolic link succeeds'
+ include_context 'delete succeeds'
+ end
+ end
+ end
+ end
+
+ context "is a hard link" do
+ before(:each) do
+ resource.link_type(:hard)
+ end
+
+ context "when the link destination is a file" do
+ before(:each) do
+ File.open(to, "w") do |file|
+ file.write('woohoo')
+ end
+ end
+ context "and the link does not yet exist" do
+ include_context 'create hard link succeeds'
+ include_context 'delete is noop'
+ end
+ context "and the link already exists and is a symbolic link pointing at the same file" do
+ before(:each) do
+ symlink(to, target_file)
+ symlink?(target_file).should be_true
+ readlink(target_file).should == to
+ end
+ include_context 'create hard link succeeds'
+ it_behaves_like 'delete errors out'
+ end
+ context 'and the link already exists and is a hard link to the file' do
+ before(:each) do
+ link(to, target_file)
+ File.exists?(target_file).should be_true
+ symlink?(target_file).should be_false
+ end
+ include_context 'create hard link is noop'
+ include_context 'delete succeeds'
+ it 'the :delete action does not delete the target file' do
+ resource.run_action(:delete)
+ File.exists?(to).should be_true
+ end
+ end
+ context "and the link already exists and is a file" do
+ before(:each) do
+ File.open(target_file, 'w') { |file| file.write('tomfoolery') }
+ end
+ include_context 'create hard link succeeds'
+ it_behaves_like 'delete errors out'
+ end
+ context "and the link already exists and is a directory" do
+ before(:each) do
+ Dir.mkdir(target_file)
+ end
+ it 'errors out' do
+ if windows?
+ lambda { resource.run_action(:create) }.should raise_error(Errno::EACCES)
+ elsif os_x? or solaris? or freebsd?
+ lambda { resource.run_action(:create) }.should raise_error(Errno::EPERM)
+ else
+ lambda { resource.run_action(:create) }.should raise_error(Errno::EISDIR)
+ end
+ end
+ it_behaves_like 'delete errors out'
+ end
+ context "and the link already exists and is not writeable to this user", :pending do
+ end
+ context "and specifies security attributes" do
+ before(:each) do
+ resource.owner(windows? ? 'Guest' : 'nobody')
+ end
+ it 'ignores them' do
+ resource.run_action(:create)
+ if windows?
+ Chef::ReservedNames::Win32::Security.get_named_security_info(target_file).owner.should_not == SID.Guest
+ else
+ File.lstat(target_file).uid.should_not == Etc.getpwnam('nobody').uid
+ end
+ end
+ end
+ end
+ context "when the link destination is a directory" do
+ before(:each) do
+ Dir.mkdir(to)
+ end
+ context 'and the link does not yet exist' do
+ it 'create errors out' do
+ lambda { resource.run_action(:create) }.should raise_error(windows? ? Chef::Exceptions::Win32APIError : Errno::EPERM)
+ end
+ include_context 'delete is noop'
+ end
+ end
+ context "when the link destination is a symbolic link" do
+ context 'to a real file' do
+ before(:each) do
+ @other_target = File.join(base_dir, make_tmpname("other_spec", nil))
+ File.open(@other_target, "w") { |file| file.write("eek") }
+ symlink(@other_target, to)
+ symlink?(to).should be_true
+ readlink(to).should == @other_target
+ end
+ after(:each) do
+ File.delete(@other_target)
+ end
+ context 'and the link does not yet exist' do
+ it 'links to the target file' do
+ resource.run_action(:create)
+ File.exists?(target_file).should be_true
+ # OS X gets angry about this sort of link. Bug in OS X, IMO.
+ pending('OS X/FreeBSD symlink? and readlink working on hard links to symlinks', :if => (os_x? or freebsd?)) do
+ symlink?(target_file).should be_true
+ readlink(target_file).should == @other_target
+ end
+ end
+ include_context 'delete is noop'
+ end
+ end
+ context 'to a nonexistent file' do
+ before(:each) do
+ @other_target = File.join(base_dir, make_tmpname("other_spec", nil))
+ symlink(@other_target, to)
+ symlink?(to).should be_true
+ readlink(to).should == @other_target
+ end
+ context 'and the link does not yet exist' do
+ it 'links to the target file' do
+ pending('OS X/FreeBSD fails to create hardlinks to broken symlinks', :if => (os_x? or freebsd?)) do
+ resource.run_action(:create)
+ # Windows and Unix have different definitions of exists? here, and that's OK.
+ if windows?
+ File.exists?(target_file).should be_true
+ else
+ File.exists?(target_file).should be_false
+ end
+ symlink?(target_file).should be_true
+ readlink(target_file).should == @other_target
+ end
+ end
+ include_context 'delete is noop'
+ end
+ end
+ end
+ context "when the link destination is not readable to this user", :pending do
+ end
+ context "when the link destination does not exist" do
+ context 'and the link does not yet exist' do
+ it 'create errors out' do
+ lambda { resource.run_action(:create) }.should raise_error(Errno::ENOENT)
+ end
+ include_context 'delete is noop'
+ end
+ end
+ end
+end
diff --git a/spec/functional/resource/remote_directory_spec.rb b/spec/functional/resource/remote_directory_spec.rb
new file mode 100644
index 0000000000..c8319e37ba
--- /dev/null
+++ b/spec/functional/resource/remote_directory_spec.rb
@@ -0,0 +1,116 @@
+#
+# Author:: Seth Chisamore (<schisamo@opscode.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::Resource::RemoteDirectory do
+ include_context Chef::Resource::Directory
+
+ let(:directory_base) { "directory_spec" }
+
+ def create_resource
+ 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)
+
+ resource = Chef::Resource::RemoteDirectory.new(path, run_context)
+ resource.source "remotedir"
+ resource.cookbook('openldap')
+ resource
+ end
+
+ let!(:resource) do
+ create_resource
+ end
+
+ it_behaves_like "a directory resource"
+
+ context "when creating the remote directory" do
+ it "transfers the directory with all contents" do
+ resource.run_action(:create)
+ File.should exist(File.join(path, 'remote_dir_file1.txt'))
+ File.should exist(File.join(path, 'remote_dir_file2.txt'))
+ File.should exist(File.join(path, 'remotesubdir', 'remote_subdir_file1.txt'))
+ File.should exist(File.join(path, 'remotesubdir', 'remote_subdir_file2.txt'))
+ File.should exist(File.join(path, 'remotesubdir', '.a_dotfile'))
+ File.should exist(File.join(path, '.a_dotdir', '.a_dotfile_in_a_dotdir'))
+ end
+
+ context "with purging enabled" do
+ before(:each) do
+ resource.purge(true)
+ end
+
+ it "removes existing files if purge is true" do
+ FileUtils.mkdir_p(File.join(path, 'remotesubdir'))
+ existing1 = File.join(path, 'marked_for_death.txt')
+ existing2 = File.join(path, 'remotesubdir', 'marked_for_death_again.txt')
+ FileUtils.touch(existing1)
+ FileUtils.touch(existing2)
+
+ resource.run_action(:create)
+ File.should_not exist(existing1)
+ File.should_not exist(existing2)
+ end
+
+ it "removes files in subdirectories before files above" do
+ FileUtils.mkdir_p(File.join(path, 'a', 'multiply', 'nested', 'directory'))
+ existing1 = File.join(path, 'a', 'foo.txt')
+ existing2 = File.join(path, 'a', 'multiply', 'bar.txt')
+ existing3 = File.join(path, 'a', 'multiply', 'nested', 'baz.txt')
+ existing4 = File.join(path, 'a', 'multiply', 'nested', 'directory', 'qux.txt')
+ FileUtils.touch(existing1)
+ FileUtils.touch(existing2)
+ FileUtils.touch(existing3)
+ FileUtils.touch(existing4)
+
+ resource.run_action(:create)
+ File.should_not exist(existing1)
+ File.should_not exist(existing2)
+ File.should_not exist(existing3)
+ File.should_not exist(existing4)
+ end
+ end
+
+ describe "with overwrite disabled" do
+ before(:each) do
+ resource.purge(false)
+ resource.overwrite(false)
+ end
+
+ it "leaves modifications alone" do
+ FileUtils.mkdir_p(File.join(path, 'remotesubdir'))
+ modified_file = File.join(path, 'remote_dir_file1.txt')
+ modified_subdir_file = File.join(path, 'remotesubdir', 'remote_subdir_file1.txt')
+ File.open(modified_file, 'a') {|f| f.puts "santa is real"}
+ File.open(modified_subdir_file, 'a') {|f| f.puts "so is rudolph"}
+ modified_file_checksum = sha256_checksum(modified_file)
+ modified_subdir_file_checksum = sha256_checksum(modified_subdir_file)
+
+ resource.run_action(:create)
+ sha256_checksum(modified_file).should == modified_file_checksum
+ sha256_checksum(modified_subdir_file).should == modified_subdir_file_checksum
+ end
+ end
+ end
+end
diff --git a/spec/functional/resource/remote_file_spec.rb b/spec/functional/resource/remote_file_spec.rb
new file mode 100644
index 0000000000..e695e8feae
--- /dev/null
+++ b/spec/functional/resource/remote_file_spec.rb
@@ -0,0 +1,58 @@
+#
+# Author:: Seth Chisamore (<schisamo@opscode.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'
+require 'tiny_server'
+
+describe Chef::Resource::RemoteFile do
+ include_context Chef::Resource::File
+
+ let(:file_base) { "remote_file_spec" }
+ let(:source) { 'http://localhost:9000/nyan_cat.png' }
+ let(:expected_content) { IO.read(File.join(CHEF_SPEC_DATA, 'remote_file', 'nyan_cat.png')) }
+
+ def create_resource
+ node = Chef::Node.new
+ events = Chef::EventDispatch::Dispatcher.new
+ run_context = Chef::RunContext.new(node, {}, events)
+ resource = Chef::Resource::RemoteFile.new(path, run_context)
+ resource.source(source)
+ resource
+ end
+
+ let!(:resource) do
+ create_resource
+ end
+
+ before(:all) do
+ Thin::Logging.silent = false
+ @server = TinyServer::Manager.new
+ @server.start
+ @api = TinyServer::API.instance
+ @api.clear
+ @api.get("/nyan_cat.png", 200) {
+ IO.read(File.join(CHEF_SPEC_DATA, 'remote_file', 'nyan_cat.png'))
+ }
+ end
+
+ after(:all) do
+ @server.stop
+ end
+
+ it_behaves_like "a file resource"
+end
diff --git a/spec/functional/resource/template_spec.rb b/spec/functional/resource/template_spec.rb
new file mode 100644
index 0000000000..0dfdb17324
--- /dev/null
+++ b/spec/functional/resource/template_spec.rb
@@ -0,0 +1,70 @@
+#
+# Author:: Seth Chisamore (<schisamo@opscode.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::Resource::Template do
+
+ include_context Chef::Resource::File
+
+ let(:file_base) { "template_spec" }
+ let(:expected_content) { "slappiness is a warm gun" }
+
+ let(:node) do
+ node = Chef::Node.new
+ node.normal[:slappiness] = "a warm gun"
+ node
+ end
+
+ def create_resource
+ cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks"))
+ Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, cookbook_repo) }
+ 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)
+ resource = Chef::Resource::Template.new(path, run_context)
+ resource.source('openldap_stuff.conf.erb')
+ resource.cookbook('openldap')
+ resource
+ end
+
+ let!(:resource) do
+ create_resource
+ end
+
+ it_behaves_like "a file resource"
+
+ context "when the target file does not exist" do
+ it "creates the template with the rendered content using the variable attribute when the :create action is run" do
+ resource.source('openldap_variable_stuff.conf.erb')
+ resource.variables(:secret => "nutella")
+ resource.run_action(:create)
+ IO.read(path).should == "super secret is nutella"
+ end
+
+ it "creates the template with the rendered content using a local erb file when the :create action is run" do
+ resource.source(File.expand_path(File.join(CHEF_SPEC_DATA,'cookbooks','openldap','templates','default','openldap_stuff.conf.erb')))
+ resource.cookbook(nil)
+ resource.local(true)
+ resource.run_action(:create)
+ IO.read(path).should == expected_content
+ end
+ end
+end
diff --git a/spec/functional/run_lock_spec.rb b/spec/functional/run_lock_spec.rb
new file mode 100644
index 0000000000..af9bd1aa1f
--- /dev/null
+++ b/spec/functional/run_lock_spec.rb
@@ -0,0 +1,90 @@
+#
+# Author:: Daniel DeLeo (<dan@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 File.expand_path('../../spec_helper', __FILE__)
+require 'chef/client'
+
+describe Chef::RunLock do
+
+ # This behavior is believed to work on windows, but the tests use UNIX APIs.
+ describe "when locking the chef-client run", :unix_only => true do
+ it "allows only one chef client run per lockfile" do
+ read, write = IO.pipe
+ run_lock = Chef::RunLock.new(:file_cache_path => "/var/chef/cache", :lockfile => "/tmp/chef-client-running.pid")
+ p1 = fork do
+ run_lock.acquire
+ write.puts 1
+ #puts "[#{Time.new.to_i % 100}] p1 (#{Process.pid}) running with lock"
+ sleep 2
+ write.puts 2
+ #puts "[#{Time.new.to_i % 100}] p1 (#{Process.pid}) releasing lock"
+ run_lock.release
+ end
+
+ sleep 0.5
+
+ p2 = fork do
+ run_lock.acquire
+ write.puts 3
+ #puts "[#{Time.new.to_i % 100}] p2 (#{Process.pid}) running with lock"
+ run_lock.release
+ end
+
+ Process.waitpid2(p1)
+ Process.waitpid2(p2)
+
+ write.close
+ order = read.read
+ read.close
+
+ order.should == "1\n2\n3\n"
+ end
+
+ it "clears the lock if the process dies unexpectedly" do
+ read, write = IO.pipe
+ run_lock = Chef::RunLock.new(:file_cache_path => "/var/chef/cache", :lockfile => "/tmp/chef-client-running.pid")
+ p1 = fork do
+ run_lock.acquire
+ write.puts 1
+ #puts "[#{Time.new.to_i % 100}] p1 (#{Process.pid}) running with lock"
+ sleep 1
+ write.puts 2
+ #puts "[#{Time.new.to_i % 100}] p1 (#{Process.pid}) releasing lock"
+ run_lock.release
+ end
+
+ p2 = fork do
+ run_lock.acquire
+ write.puts 3
+ #puts "[#{Time.new.to_i % 100}] p2 (#{Process.pid}) running with lock"
+ run_lock.release
+ end
+ Process.kill(:KILL, p1)
+
+ Process.waitpid2(p1)
+ Process.waitpid2(p2)
+
+ write.close
+ order = read.read
+ read.close
+
+ order.should =~ /3\Z/
+ end
+ end
+
+end
+
diff --git a/spec/functional/tiny_server_spec.rb b/spec/functional/tiny_server_spec.rb
new file mode 100644
index 0000000000..0cfef4305f
--- /dev/null
+++ b/spec/functional/tiny_server_spec.rb
@@ -0,0 +1,77 @@
+#
+# 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 'tiny_server'
+
+describe TinyServer::API do
+ before do
+ @api = TinyServer::API.instance
+ @api.clear
+ end
+
+ it "is a Singleton" do
+ lambda {TinyServer::API.new}.should raise_error
+ end
+
+ it "clears the router" do
+ @api.get('/blargh', 200, "blargh")
+ @api.clear
+ @api.routes["GET"].should be_empty
+ end
+
+ it "creates a route for a GET request" do
+ @api.get('/foo/bar', 200, 'hello foobar')
+ response = @api.call("REQUEST_METHOD" => "GET", "REQUEST_URI" => '/foo/bar')
+ response.should == [200, {'Content-Type' => 'application/json'}, 'hello foobar']
+ end
+
+ it "creates a route for a request with a block" do
+ block_called = false
+ @api.get('/bar/baz', 200) { block_called = true; 'hello barbaz' }
+ response = @api.call("REQUEST_METHOD" => "GET", "REQUEST_URI" => '/bar/baz')
+ response.should == [200, {'Content-Type' => 'application/json'}, 'hello barbaz']
+ block_called.should be_true
+ end
+
+ it "returns debugging info for 404s" do
+ response = @api.call("REQUEST_METHOD" => "GET", "REQUEST_URI" => '/no_such_thing')
+ response[0].should == 404
+ response[1].should == {'Content-Type' => 'application/json'}
+ response[2].should be_a_kind_of(String)
+ response_obj = Chef::JSONCompat.from_json(response[2])
+ response_obj["message"].should == "no data matches the request for /no_such_thing"
+ response_obj["available_routes"].should == {"GET"=>[], "PUT"=>[], "POST"=>[], "DELETE"=>[]}
+ response_obj["request"].should == {"REQUEST_METHOD"=>"GET", "REQUEST_URI"=>"/no_such_thing"}
+ end
+
+end
+
+describe TinyServer::Manager do
+ it "runs the server" do
+ @server = TinyServer::Manager.new
+ @server.start
+
+ TinyServer::API.instance.get("/index", 200, "[\"hello\"]")
+
+ rest = Chef::REST.new('http://localhost:9000', false, false)
+ rest.get_rest("index").should == ["hello"]
+
+ @server.stop
+ end
+end