summaryrefslogtreecommitdiff
path: root/spec/support
diff options
context:
space:
mode:
Diffstat (limited to 'spec/support')
-rw-r--r--spec/support/chef_helpers.rb52
-rw-r--r--spec/support/lib/chef/provider/easy.rb35
-rw-r--r--spec/support/lib/chef/provider/snakeoil.rb40
-rw-r--r--spec/support/lib/chef/resource/cat.rb41
-rw-r--r--spec/support/lib/chef/resource/one_two_three_four.rb43
-rw-r--r--spec/support/lib/chef/resource/zen_master.rb46
-rw-r--r--spec/support/matchers/leak.rb96
-rw-r--r--spec/support/mock/constant.rb52
-rw-r--r--spec/support/mock/platform.rb18
-rw-r--r--spec/support/platform_helpers.rb31
-rw-r--r--spec/support/platforms/prof/gc.rb54
-rw-r--r--spec/support/platforms/prof/win32.rb46
-rw-r--r--spec/support/shared/functional/directory_resource.rb85
-rw-r--r--spec/support/shared/functional/file_resource.rb173
-rw-r--r--spec/support/shared/functional/knife.rb37
-rw-r--r--spec/support/shared/functional/securable_resource.rb394
-rw-r--r--spec/support/shared/unit/api_error_inspector.rb192
-rw-r--r--spec/support/shared/unit/file_system_support.rb110
18 files changed, 1545 insertions, 0 deletions
diff --git a/spec/support/chef_helpers.rb b/spec/support/chef_helpers.rb
new file mode 100644
index 0000000000..77f5fc7669
--- /dev/null
+++ b/spec/support/chef_helpers.rb
@@ -0,0 +1,52 @@
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+CHEF_SPEC_DATA = File.expand_path(File.dirname(__FILE__) + "/../data/")
+CHEF_SPEC_BACKUP_PATH = File.join(Dir.tmpdir, 'test-backup-path')
+
+Chef::Config[:log_level] = :fatal
+Chef::Config[:cache_type] = "Memory"
+Chef::Config[:cache_options] = { }
+Chef::Config[:persistent_queue] = false
+Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH
+
+Chef::Log.level(Chef::Config.log_level)
+Chef::Config.solo(false)
+
+Chef::Log.logger = Logger.new(StringIO.new)
+
+def sha256_checksum(path)
+ Digest::SHA256.hexdigest(File.read(path))
+end
+
+# From Ruby 1.9.2+
+# Here for backwards compatibility with Ruby 1.8.7
+# http://rubydoc.info/stdlib/tmpdir/1.9.2/Dir/Tmpname
+def make_tmpname(prefix_suffix, n)
+ case prefix_suffix
+ when String
+ prefix = prefix_suffix
+ suffix = ""
+ when Array
+ prefix = prefix_suffix[0]
+ suffix = prefix_suffix[1]
+ else
+ raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
+ end
+ t = Time.now.strftime("%Y%m%d")
+ path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
+ path << "-#{n}" if n
+ path << suffix
+end
diff --git a/spec/support/lib/chef/provider/easy.rb b/spec/support/lib/chef/provider/easy.rb
new file mode 100644
index 0000000000..054b45256c
--- /dev/null
+++ b/spec/support/lib/chef/provider/easy.rb
@@ -0,0 +1,35 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ class Provider
+ class Easy < Chef::Provider
+ def load_current_resource
+ true
+ end
+
+ def action_sell
+ true
+ end
+
+ def action_buy
+ true
+ end
+ end
+ end
+end
diff --git a/spec/support/lib/chef/provider/snakeoil.rb b/spec/support/lib/chef/provider/snakeoil.rb
new file mode 100644
index 0000000000..c5d8d52a82
--- /dev/null
+++ b/spec/support/lib/chef/provider/snakeoil.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ class Provider
+ class SnakeOil < Chef::Provider
+ def load_current_resource
+ true
+ end
+
+ def action_purr
+ @new_resource.updated_by_last_action(true)
+ true
+ end
+
+ def action_sell
+ true
+ end
+
+ def action_buy
+ true
+ end
+ end
+ end
+end
diff --git a/spec/support/lib/chef/resource/cat.rb b/spec/support/lib/chef/resource/cat.rb
new file mode 100644
index 0000000000..5809ad3275
--- /dev/null
+++ b/spec/support/lib/chef/resource/cat.rb
@@ -0,0 +1,41 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ class Resource
+ class Cat < Chef::Resource
+
+ attr_accessor :action
+
+ def initialize(name, run_context=nil)
+ @resource_name = :cat
+ super
+ @action = "sell"
+ end
+
+ def pretty_kitty(arg=nil)
+ set_if_args(@pretty_kitty, arg) do
+ case arg
+ when true, false
+ @pretty_kitty = arg
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/lib/chef/resource/one_two_three_four.rb b/spec/support/lib/chef/resource/one_two_three_four.rb
new file mode 100644
index 0000000000..4f4b063eb6
--- /dev/null
+++ b/spec/support/lib/chef/resource/one_two_three_four.rb
@@ -0,0 +1,43 @@
+#
+# Author:: John Hampton (<john@cleanoffer.com>)
+# Copyright:: Copyright (c) 2009 CleanOffer, 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.
+#
+
+class Chef
+ class Resource
+ class OneTwoThreeFour < Chef::Resource
+ attr_reader :i_can_count
+
+ def initialize(name, run_context)
+ @resource_name = :one_two_three_four
+ super
+ end
+
+ def i_can_count(tf)
+ @i_can_count = tf
+ end
+
+ def something(arg=nil)
+ set_if_args(@something, arg) do
+ case arg
+ when true, false
+ @something = arg
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/lib/chef/resource/zen_master.rb b/spec/support/lib/chef/resource/zen_master.rb
new file mode 100644
index 0000000000..71842b74c0
--- /dev/null
+++ b/spec/support/lib/chef/resource/zen_master.rb
@@ -0,0 +1,46 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008, 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+require 'chef/json_compat'
+
+class Chef
+ class Resource
+ class ZenMaster < Chef::Resource
+ attr_reader :peace
+
+ def initialize(name, run_context=nil)
+ @resource_name = :zen_master
+ super
+ end
+
+ def peace(tf)
+ @peace = tf
+ end
+
+ def something(arg=nil)
+ set_if_args(@something, arg) do
+ case arg
+ when true, false
+ @something = arg
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/matchers/leak.rb b/spec/support/matchers/leak.rb
new file mode 100644
index 0000000000..eb80fcd492
--- /dev/null
+++ b/spec/support/matchers/leak.rb
@@ -0,0 +1,96 @@
+#
+# 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.
+#
+
+module Matchers
+ module LeakBase
+ include RSpec::Matchers
+
+ def initialize(opts={}, &block)
+ @warmup = opts[:warmup] || 5
+ @iterations = opts[:iterations] || 100
+ @variance = opts[:variance] || 1000
+ end
+
+ def failure_message_for_should
+ "expected final measure [#{@final_measure}] to be greater than or within +/- #{@variance} delta of initial measure [#{@initial_measure}]"
+ end
+
+ def failure_message_for_should_not
+ "expected final measure [#{@final_measure}] to be less than or within +/- #{@variance} delta of initial measure [#{@initial_measure}]"
+ end
+
+ private
+ def match(measure, given_proc)
+ profiler.start
+
+ @initial_measure = 0
+ @final_measure = 0
+
+ @warmup.times do
+ given_proc.call
+ end
+
+ @initial_measure = profiler.send(measure)
+
+ @iterations.times do
+ given_proc.call
+ end
+
+ profiler.stop
+
+ @final_measure = profiler.send(measure)
+ @final_measure > (@initial_measure + @variance)
+ end
+
+ def profiler
+ @profiler ||= begin
+ if Chef::Platform.windows?
+ require File.join(File.dirname(__FILE__), '..', 'prof', 'win32')
+ RSpec::Prof::Win32::Profiler.new
+ else
+ require File.join(File.dirname(__FILE__), '..', 'prof', 'gc')
+ RSpec::Prof::GC::Profiler.new
+ end
+ end
+ end
+
+ end
+
+ class LeakMemory
+ include LeakBase
+
+ def matches?(given_proc)
+ match(:working_set_size, given_proc)
+ end
+ end
+
+ class LeakHandles
+ include LeakBase
+
+ def matches?(given_proc)
+ match(:handle_count, given_proc)
+ end
+ end
+
+ def leak_memory(opts, &block)
+ Matchers::LeakMemory.new(opts, &block)
+ end
+ def leak_handles(opts, &block)
+ Matchers::LeakHandles.new(opts, &block)
+ end
+end
diff --git a/spec/support/mock/constant.rb b/spec/support/mock/constant.rb
new file mode 100644
index 0000000000..c706ad29dd
--- /dev/null
+++ b/spec/support/mock/constant.rb
@@ -0,0 +1,52 @@
+# Allows easy mocking of global and class constants
+
+# Inspired by:
+# http://missingbit.blogspot.com/2011/07/stubbing-constants-in-rspec_20.html
+# http://digitaldumptruck.jotabout.com/?p=551
+
+def mock_constants(constants, &block)
+ saved_constants = {}
+ constants.each do |constant, val|
+ source_object, const_name = parse_constant(constant)
+ saved_constants[constant] = source_object.const_get(const_name)
+ with_warnings(nil) {source_object.const_set(const_name, val) }
+ end
+
+ begin
+ block.call
+ ensure
+ constants.each do |constant, val|
+ source_object, const_name = parse_constant(constant)
+ with_warnings(nil) { source_object.const_set(const_name, saved_constants[constant]) }
+ end
+ end
+end
+
+def parse_constant(constant)
+ source, _, constant_name = constant.to_s.rpartition('::')
+ [constantize(source), constant_name]
+end
+
+# Taken from ActiveSupport
+
+# File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 3
+#
+# Sets $VERBOSE for the duration of the block and back to its original value afterwards.
+def with_warnings(flag)
+ old_verbose, $VERBOSE = $VERBOSE, flag
+ yield
+ensure
+ $VERBOSE = old_verbose
+end
+
+# File activesupport/lib/active_support/inflector/methods.rb, line 209
+def constantize(camel_cased_word)
+ names = camel_cased_word.split('::')
+ names.shift if names.empty? || names.first.empty?
+
+ constant = Object
+ names.each do |name|
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
+ end
+ constant
+end
diff --git a/spec/support/mock/platform.rb b/spec/support/mock/platform.rb
new file mode 100644
index 0000000000..78b704ea9b
--- /dev/null
+++ b/spec/support/mock/platform.rb
@@ -0,0 +1,18 @@
+# makes Chef think it's running on a certain platform..useful for unit testing
+# platform-specific functionality.
+#
+# If a block is given yields to the block with +RUBY_PLATFORM+ set to
+# 'i386-mingw32' (windows) or 'x86_64-darwin11.2.0' (unix). Usueful for
+# testing code that mixes in platform specific modules like +Chef::Mixin::Securable+
+# or +Chef::FileAccessControl+
+def platform_mock(platform = :unix, &block)
+ Chef::Platform.stub!(:windows?).and_return(platform == :windows ? true : false)
+ ENV['SYSTEMDRIVE'] = (platform == :windows ? 'C:' : nil)
+ if block_given?
+ mock_constants({"RUBY_PLATFORM" => (platform == :windows ? 'i386-mingw32' : 'x86_64-darwin11.2.0'),
+ "File::PATH_SEPARATOR" => (platform == :windows ? ";" : ":"),
+ "File::ALT_SEPARATOR" => (platform == :windows ? "\\" : nil) }) do
+yield
+ end
+ end
+end
diff --git a/spec/support/platform_helpers.rb b/spec/support/platform_helpers.rb
new file mode 100644
index 0000000000..558817d72a
--- /dev/null
+++ b/spec/support/platform_helpers.rb
@@ -0,0 +1,31 @@
+def ruby_19?
+ !!(RUBY_VERSION =~ /^1.9/)
+end
+
+def ruby_18?
+ !!(RUBY_VERSION =~ /^1.8/)
+end
+
+def windows?
+ !!(RUBY_PLATFORM =~ /mswin|mingw|windows/)
+end
+
+# def jruby?
+
+def unix?
+ !windows?
+end
+
+def os_x?
+ !!(RUBY_PLATFORM =~ /darwin/)
+end
+
+def solaris?
+ !!(RUBY_PLATFORM =~ /solaris/)
+end
+
+def freebsd?
+ !!(RUBY_PLATFORM =~ /freebsd/)
+end
+
+DEV_NULL = windows? ? 'NUL' : '/dev/null'
diff --git a/spec/support/platforms/prof/gc.rb b/spec/support/platforms/prof/gc.rb
new file mode 100644
index 0000000000..6ca50df648
--- /dev/null
+++ b/spec/support/platforms/prof/gc.rb
@@ -0,0 +1,54 @@
+#
+# 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.
+#
+
+module RSpec
+ module Prof
+ module GC
+ class Profiler
+
+ # GC 1 invokes.
+ # Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC time(ms)
+ # 1 0.012 159240 212940 10647 0.00000000000001530000
+ LINE_PATTERN = /^\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)$/
+
+ def start
+ ::GC::Profiler.enable unless ::GC::Profiler.enabled?
+ end
+
+ def stop
+ ::GC::Profiler.disable
+ end
+
+ def working_set_size
+ begin
+ ::GC.start
+ ::GC::Profiler.result.scan(LINE_PATTERN)[-1][2].to_i if ::GC::Profiler.enabled?
+ ensure
+ ::GC::Profiler.clear
+ end
+ end
+
+ def handle_count
+ 0
+ end
+
+ end
+ end
+ end
+end
+
diff --git a/spec/support/platforms/prof/win32.rb b/spec/support/platforms/prof/win32.rb
new file mode 100644
index 0000000000..ab256ff0fc
--- /dev/null
+++ b/spec/support/platforms/prof/win32.rb
@@ -0,0 +1,46 @@
+#
+# 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 'chef/win32/process'
+
+module RSpec
+ module Prof
+ module Win32
+ class Profiler
+
+ def start
+ GC.start
+ end
+
+ def stop
+ GC.start
+ end
+
+ def working_set_size
+ Chef::ReservedNames::Win32::Process.get_current_process.memory_info[:WorkingSetSize]
+ end
+
+ def handle_count
+ Chef::ReservedNames::Win32::Process.get_current_process.handle_count
+ end
+ end
+
+ end
+ end
+end
+
diff --git a/spec/support/shared/functional/directory_resource.rb b/spec/support/shared/functional/directory_resource.rb
new file mode 100644
index 0000000000..14ab7a10e8
--- /dev/null
+++ b/spec/support/shared/functional/directory_resource.rb
@@ -0,0 +1,85 @@
+#
+# 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.
+#
+
+shared_examples_for "a directory resource" do
+ context "when the target directory does not exist" do
+ it "creates the directory when the :create action is run" do
+ resource.run_action(:create)
+ File.should exist(path)
+ end
+
+ it "recursively creates required directories if requested" do
+ resource.recursive(true)
+ recursive_path = File.join(path, 'red-headed-stepchild')
+ resource.path(recursive_path)
+ resource.run_action(:create)
+ File.should exist(path)
+ File.should exist(recursive_path)
+ end
+ end
+
+ context "when the target directory exists" do
+ before(:each) do
+ FileUtils.mkdir(path)
+ end
+
+ it "does not re-create the directory" do
+ resource.run_action(:create)
+ File.should exist(path)
+ end
+
+ it "deletes the directory when the :delete action is run" do
+ resource.run_action(:delete)
+ File.should_not exist(path)
+ end
+
+ it "recursively deletes directories if requested" do
+ FileUtils.mkdir(File.join(path, 'red-headed-stepchild'))
+ resource.recursive(true)
+ resource.run_action(:delete)
+ File.should_not exist(path)
+ end
+ end
+
+ # Set up the context for security tests
+ def allowed_acl(sid, expected_perms)
+ [
+ ACE.access_allowed(sid, expected_perms[:specific]),
+ ACE.access_allowed(sid, expected_perms[:generic], (Chef::ReservedNames::Win32::API::Security::INHERIT_ONLY_ACE | Chef::ReservedNames::Win32::API::Security::CONTAINER_INHERIT_ACE | Chef::ReservedNames::Win32::API::Security::OBJECT_INHERIT_ACE))
+ ]
+ end
+
+ def denied_acl(sid, expected_perms)
+ [
+ ACE.access_denied(sid, expected_perms[:specific]),
+ ACE.access_denied(sid, expected_perms[:generic], (Chef::ReservedNames::Win32::API::Security::INHERIT_ONLY_ACE | Chef::ReservedNames::Win32::API::Security::CONTAINER_INHERIT_ACE | Chef::ReservedNames::Win32::API::Security::OBJECT_INHERIT_ACE))
+ ]
+ end
+
+ it_behaves_like "a securable resource"
+end
+
+shared_context Chef::Resource::Directory do
+ let(:path) do
+ File.join(Dir.tmpdir, make_tmpname(directory_base, nil))
+ end
+
+ after(:each) do
+ FileUtils.rm_r(path) if File.exists?(path)
+ end
+end
diff --git a/spec/support/shared/functional/file_resource.rb b/spec/support/shared/functional/file_resource.rb
new file mode 100644
index 0000000000..631a5ed742
--- /dev/null
+++ b/spec/support/shared/functional/file_resource.rb
@@ -0,0 +1,173 @@
+#
+# 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.
+#
+
+shared_examples_for "a file with the wrong content" do
+ it "overwrites the file with the updated content when the :create action is run" do
+ Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH
+ sleep 1
+ resource.run_action(:create)
+ File.stat(path).mtime.should > @expected_mtime
+ sha256_checksum(path).should_not == @expected_checksum
+ end
+
+ it "doesn't overwrite the file when the :create_if_missing action is run" do
+ sleep 1
+ resource.run_action(:create_if_missing)
+ File.stat(path).mtime.should == @expected_mtime
+ sha256_checksum(path).should == @expected_checksum
+ end
+
+ it "should backup the existing file" do
+ Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH
+ resource.run_action(:create)
+ Dir.glob(backup_glob).size.should equal(1)
+ end
+
+ it "should not attempt to backup the existing file if :backup == 0" do
+ Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH
+ resource.backup(0)
+ resource.run_action(:create)
+ Dir.glob(backup_glob).size.should equal(0)
+ end
+
+ it "deletes the file when the :delete action is run" do
+ resource.run_action(:delete)
+ File.should_not exist(path)
+ end
+end
+
+shared_examples_for "a file with the correct content" do
+ it "does not overwrite the original when the :create action is run" do
+ resource.run_action(:create)
+ sha256_checksum(path).should == @expected_checksum
+ end
+
+ it "does not update the mtime/atime of the file when the :create action is run" do
+ sleep 1
+ File.stat(path).mtime.should == @expected_mtime
+ File.stat(path).atime.should be_within(2).of(@expected_atime)
+ end
+
+ it "doesn't overwrite the file when the :create_if_missing action is run" do
+ resource.run_action(:create_if_missing)
+ sha256_checksum(path).should == @expected_checksum
+ end
+
+ it "deletes the file when the :delete action is run" do
+ resource.run_action(:delete)
+ File.should_not exist(path)
+ end
+end
+
+shared_examples_for "a file resource" do
+ # note the stripping of the drive letter from the tmpdir on windows
+ let(:backup_glob) { File.join(CHEF_SPEC_BACKUP_PATH, Dir.tmpdir.sub(/^([A-Za-z]:)/, ""), "#{file_base}*") }
+
+ context "when the target file does not exist" do
+ it "creates the file when the :create action is run" do
+ resource.run_action(:create)
+ File.should exist(path)
+ end
+
+ it "creates the file with the correct content when the :create action is run" do
+ resource.run_action(:create)
+ IO.read(path).should == expected_content
+ end
+
+ it "creates the file with the correct content when the :create_if_missing action is run" do
+ resource.run_action(:create_if_missing)
+ IO.read(path).should == expected_content
+ end
+
+ it "deletes the file when the :delete action is run" do
+ resource.run_action(:delete)
+ File.should_not exist(path)
+ end
+ end
+
+ # Set up the context for security tests
+ 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
+
+
+ context "when the target file has the wrong content" do
+ before(:each) do
+ File.open(path, "w") { |f| f.print "This is so wrong!!!" }
+ @expected_mtime = File.stat(path).mtime
+ @expected_checksum = sha256_checksum(path)
+ end
+
+ describe "and the target file has the correct permissions" do
+ include_context "setup correct permissions"
+
+ it_behaves_like "a file with the wrong content"
+
+ it_behaves_like "a securable resource"
+ end
+
+ context "and the target file has incorrect permissions" do
+ include_context "setup broken permissions"
+
+ it_behaves_like "a file with the wrong content"
+
+ it_behaves_like "a securable resource"
+ end
+ end
+
+ context "when the target file has the correct content" do
+ before(:each) do
+ File.open(path, "w") { |f| f.print expected_content }
+ @expected_mtime = File.stat(path).mtime
+ @expected_atime = File.stat(path).atime
+ @expected_checksum = sha256_checksum(path)
+ end
+
+ describe "and the target file has the correct permissions" do
+ include_context "setup correct permissions"
+
+ it_behaves_like "a file with the correct content"
+
+ it_behaves_like "a securable resource"
+ end
+
+ context "and the target file has incorrect permissions" do
+ include_context "setup broken permissions"
+
+ it_behaves_like "a file with the correct content"
+
+ it_behaves_like "a securable resource"
+ end
+ end
+
+end
+
+shared_context Chef::Resource::File do
+ let(:path) do
+ File.join(Dir.tmpdir, make_tmpname(file_base, nil))
+ end
+
+ after(:each) do
+ FileUtils.rm_r(path) if File.exists?(path)
+ FileUtils.rm_r(CHEF_SPEC_BACKUP_PATH) if File.exists?(CHEF_SPEC_BACKUP_PATH)
+ end
+end
diff --git a/spec/support/shared/functional/knife.rb b/spec/support/shared/functional/knife.rb
new file mode 100644
index 0000000000..e96de7c27a
--- /dev/null
+++ b/spec/support/shared/functional/knife.rb
@@ -0,0 +1,37 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: AJ Christensen (<aj@junglist.gen.nz>)
+# Author:: Ho-Sheng Hsiao (<hosh@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+module SpecHelpers
+ module Knife
+ def redefine_argv(value)
+ Object.send(:remove_const, :ARGV)
+ Object.send(:const_set, :ARGV, value)
+ end
+
+ def with_argv(*argv)
+ original_argv = ARGV
+ redefine_argv(argv.flatten)
+ begin
+ yield
+ ensure
+ redefine_argv(original_argv)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared/functional/securable_resource.rb b/spec/support/shared/functional/securable_resource.rb
new file mode 100644
index 0000000000..2eeb16c784
--- /dev/null
+++ b/spec/support/shared/functional/securable_resource.rb
@@ -0,0 +1,394 @@
+#
+# Author:: Seth Chisamore (<schisamo@opscode.com>)
+# Author:: Mark Mzyk (<mmzyk@opscode.com>)
+# 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.
+#
+
+# TODO test that these work when you are logged on as a user joined to a domain (rather than local computer)
+# TODO test that you can set users from other domains
+
+require 'etc'
+
+shared_context "setup correct permissions" do
+ context "on unix", :unix_only do
+ context "with root", :requires_root do
+ before :each do
+ File.chown(Etc.getpwnam('nobody').uid, 1337, path)
+ File.chmod(0776, path)
+ end
+ end
+
+ context "without root", :requires_unprivileged_user do
+ before :each do
+ File.chmod(0776, path)
+ end
+ end
+ end
+
+ # FIXME: windows
+end
+
+shared_context "setup broken permissions" do
+ context "on unix", :unix_only do
+ context "with root", :requires_root do
+ before :each do
+ File.chown(0, 0, path)
+ File.chmod(0644, path)
+ end
+ end
+
+ context "without root", :requires_unprivileged_user do
+ before :each do
+ File.chmod(0644, path)
+ end
+ end
+ end
+
+ # FIXME: windows
+end
+
+shared_examples_for "a securable resource" do
+ context "on Unix", :unix_only do
+ let(:expected_user_name) { 'nobody' }
+ let(:expected_uid) { Etc.getpwnam(expected_user_name).uid }
+ let(:desired_gid) { 1337 }
+ let(:expected_gid) { 1337 }
+
+ pending "should set an owner (Rerun specs under root)", :requires_unprivileged_user => true
+ pending "should set a group (Rerun specs under root)", :requires_unprivileged_user => true
+
+ it "should set an owner", :requires_root do
+ resource.owner expected_user_name
+ resource.run_action(:create)
+ File.lstat(path).uid.should == expected_uid
+ end
+
+ it "should set a group", :requires_root do
+ resource.group desired_gid
+ resource.run_action(:create)
+ File.lstat(path).gid.should == expected_gid
+ end
+
+ it "should set permissions in string form as an octal number" do
+ mode_string = '776'
+ resource.mode mode_string
+ resource.run_action(:create)
+ pending('Linux does not support lchmod', :if => resource.instance_of?(Chef::Resource::Link) && !os_x? && !freebsd?) do
+ (File.lstat(path).mode & 007777).should == (mode_string.oct & 007777)
+ end
+ end
+
+ it "should set permissions in numeric form as a ruby-interpreted octal" do
+ mode_integer = 0776
+ resource.mode mode_integer
+ resource.run_action(:create)
+ pending('Linux does not support lchmod', :if => resource.instance_of?(Chef::Resource::Link) && !os_x? && !freebsd?) do
+ (File.lstat(path).mode & 007777).should == (mode_integer & 007777)
+ end
+ end
+ end
+
+ context "on Windows", :windows_only do
+
+ if windows?
+ SID = Chef::ReservedNames::Win32::Security::SID
+ ACE = Chef::ReservedNames::Win32::Security::ACE
+ end
+
+ def get_security_descriptor(path)
+ Chef::ReservedNames::Win32::Security.get_named_security_info(path)
+ end
+
+ def explicit_aces
+ descriptor.dacl.select { |ace| ace.explicit? }
+ end
+
+ def extract_ace_properties(aces)
+ hashes = []
+ aces.each do |ace|
+ hashes << { :mask => ace.mask, :type => ace.type, :flags => ace.flags }
+ end
+ hashes
+ end
+
+ # Standard expected rights
+ let(:expected_read_perms) do
+ {
+ :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_READ,
+ :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_READ,
+ }
+ end
+
+ let(:expected_read_execute_perms) do
+ {
+ :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_READ | Chef::ReservedNames::Win32::API::Security::GENERIC_EXECUTE,
+ :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_READ | Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_EXECUTE
+ }
+ end
+
+ let(:expected_write_perms) do
+ {
+ :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_WRITE,
+ :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_WRITE
+ }
+ end
+
+ let(:expected_modify_perms) do
+ {
+ :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_READ | Chef::ReservedNames::Win32::API::Security::GENERIC_WRITE | Chef::ReservedNames::Win32::API::Security::GENERIC_EXECUTE | Chef::ReservedNames::Win32::API::Security::DELETE,
+ :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_READ | Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_WRITE | Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_EXECUTE | Chef::ReservedNames::Win32::API::Security::DELETE
+ }
+ end
+
+ let(:expected_full_control_perms) do
+ {
+ :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_ALL,
+ :specific => Chef::ReservedNames::Win32::API::Security::FILE_ALL_ACCESS
+ }
+ end
+
+ RSpec::Matchers.define :have_expected_properties do |mask, type, flags|
+ match do |ace|
+ ace.mask == mask
+ ace.type == type
+ ace.flags == flags
+ end
+ end
+
+ def descriptor
+ get_security_descriptor(path)
+ end
+
+ before(:each) do
+ resource.run_action(:delete)
+ end
+
+ it "sets owner to Administrators on create if owner is not specified" do
+ File.exist?(path).should == false
+ resource.run_action(:create)
+ descriptor.owner.should == SID.Administrators
+ end
+
+ it "sets owner when owner is specified" do
+ resource.owner 'Guest'
+ resource.run_action(:create)
+ descriptor.owner.should == SID.Guest
+ end
+
+ it "fails to set owner when owner has invalid characters" do
+ lambda { resource.owner 'Lance "The Nose" Glindenberry III' }.should raise_error#(Chef::Exceptions::ValidationFailed)
+ end
+
+ it "sets owner when owner is specified with a \\" do
+ resource.owner "#{ENV['USERDOMAIN']}\\Guest"
+ resource.run_action(:create)
+ descriptor.owner.should == SID.Guest
+ end
+
+ it "leaves owner alone if owner is not specified and resource already exists" do
+ # Set owner to Guest so it's not the same as the current user (which is the default on create)
+ resource.owner 'Guest'
+ resource.run_action(:create)
+ descriptor.owner.should == SID.Guest
+
+ new_resource = create_resource
+ new_resource.owner.should == nil
+ new_resource.run_action(:create)
+ descriptor.owner.should == SID.Guest
+ end
+
+ it "sets group to None on create if group is not specified" do
+ resource.group.should == nil
+ File.exist?(path).should == false
+ resource.run_action(:create)
+ descriptor.group.should == SID.None
+ end
+
+ it "sets group when group is specified" do
+ resource.group 'Everyone'
+ resource.run_action(:create)
+ descriptor.group.should == SID.Everyone
+ end
+
+ it "fails to set group when group has invalid characters" do
+ lambda { resource.group 'Lance "The Nose" Glindenberry III' }.should raise_error(Chef::Exceptions::ValidationFailed)
+ end
+
+ it "sets group when group is specified with a \\" do
+ pending "Need to find a group containing a backslash that is on most peoples' machines" do
+ resource.group "#{ENV['COMPUTERNAME']}\\Administrators"
+ resource.run_action(:create)
+ descriptor.group.should == SID.Everyone
+ end
+ end
+
+ it "leaves group alone if group is not specified and resource already exists" do
+ # Set group to Everyone so it's not the default (None)
+ resource.group 'Everyone'
+ resource.run_action(:create)
+ descriptor.group.should == SID.Everyone
+
+ new_resource = create_resource
+ new_resource.group.should == nil
+ new_resource.run_action(:create)
+ descriptor.group.should == SID.Everyone
+ end
+
+ describe "with rights and deny_rights attributes" do
+
+ it "correctly sets :read rights" do
+ resource.rights(:read, 'Guest')
+ resource.run_action(:create)
+ explicit_aces.should == allowed_acl(SID.Guest, expected_read_perms)
+ end
+
+ it "correctly sets :read_execute rights" do
+ resource.rights(:read_execute, 'Guest')
+ resource.run_action(:create)
+ explicit_aces.should == allowed_acl(SID.Guest, expected_read_execute_perms)
+ end
+
+ it "correctly sets :write rights" do
+ resource.rights(:write, 'Guest')
+ resource.run_action(:create)
+ explicit_aces.should == allowed_acl(SID.Guest, expected_write_perms)
+ end
+
+ it "correctly sets :modify rights" do
+ resource.rights(:modify, 'Guest')
+ resource.run_action(:create)
+ explicit_aces.should == allowed_acl(SID.Guest, expected_modify_perms)
+ end
+
+ it "correctly sets :full_control rights" do
+ resource.rights(:full_control, 'Guest')
+ resource.run_action(:create)
+ explicit_aces.should == allowed_acl(SID.Guest, expected_full_control_perms)
+ end
+
+ it "correctly sets deny_rights" do
+ # deny is an ACE with full rights, but is a deny type ace, not an allow type
+ resource.deny_rights(:full_control, 'Guest')
+ resource.run_action(:create)
+ explicit_aces.should == denied_acl(SID.Guest, expected_full_control_perms)
+ end
+
+ it "Sets multiple rights" do
+ resource.rights(:read, 'Everyone')
+ resource.rights(:modify, 'Guest')
+ resource.run_action(:create)
+
+ explicit_aces.should ==
+ allowed_acl(SID.Everyone, expected_read_perms) +
+ allowed_acl(SID.Guest, expected_modify_perms)
+ end
+
+ it "Sets deny_rights ahead of rights" do
+ resource.rights(:read, 'Everyone')
+ resource.deny_rights(:modify, 'Guest')
+ resource.run_action(:create)
+
+ explicit_aces.should ==
+ denied_acl(SID.Guest, expected_modify_perms) +
+ allowed_acl(SID.Everyone, expected_read_perms)
+ end
+
+ it "Sets deny_rights ahead of rights when specified in reverse order" do
+ resource.deny_rights(:modify, 'Guest')
+ resource.rights(:read, 'Everyone')
+ resource.run_action(:create)
+
+ explicit_aces.should ==
+ denied_acl(SID.Guest, expected_modify_perms) +
+ allowed_acl(SID.Everyone, expected_read_perms)
+ end
+
+ end
+
+ context "with a mode attribute" do
+ if windows?
+ Security = Chef::ReservedNames::Win32::API::Security
+ end
+
+ it "respects mode in string form as an octal number" do
+ #on windows, mode cannot modify owner and/or group permissons
+ #unless the owner and/or group as appropriate is specified
+ resource.mode '400'
+ resource.owner 'Guest'
+ resource.group 'Everyone'
+ resource.run_action(:create)
+
+ explicit_aces.should == [ ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ) ]
+ end
+
+ it "respects mode in numeric form as a ruby-interpreted octal" do
+ resource.mode 0700
+ resource.owner 'Guest'
+ resource.run_action(:create)
+
+ explicit_aces.should == [ ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ | Security::FILE_GENERIC_WRITE | Security::FILE_GENERIC_EXECUTE | Security::DELETE) ]
+ end
+
+ it "respects the owner, group and everyone bits of mode" do
+ resource.mode 0754
+ resource.owner 'Guest'
+ resource.group 'Administrators'
+ resource.run_action(:create)
+
+ explicit_aces.should == [
+ ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ | Security::FILE_GENERIC_WRITE | Security::FILE_GENERIC_EXECUTE | Security::DELETE),
+ ACE.access_allowed(SID.Administrators, Security::FILE_GENERIC_READ | Security::FILE_GENERIC_EXECUTE),
+ ACE.access_allowed(SID.Everyone, Security::FILE_GENERIC_READ)
+ ]
+ end
+
+ it "respects the individual read, write and execute bits of mode" do
+ resource.mode 0421
+ resource.owner 'Guest'
+ resource.group 'Administrators'
+ resource.run_action(:create)
+
+ explicit_aces.should == [
+ ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ),
+ ACE.access_allowed(SID.Administrators, Security::FILE_GENERIC_WRITE | Security::DELETE),
+ ACE.access_allowed(SID.Everyone, Security::FILE_GENERIC_EXECUTE)
+ ]
+ end
+
+ it 'warns when mode tries to set owner bits but owner is not specified' do
+ @warn = []
+ Chef::Log.stub!(:warn) { |msg| @warn << msg }
+
+ resource.mode 0400
+ resource.run_action(:create)
+
+ @warn.include?("Mode 400 includes bits for the owner, but owner is not specified").should be_true
+ end
+
+ it 'warns when mode tries to set group bits but group is not specified' do
+ @warn = []
+ Chef::Log.stub!(:warn) { |msg| @warn << msg }
+
+ resource.mode 0040
+ resource.run_action(:create)
+
+ @warn.include?("Mode 040 includes bits for the group, but group is not specified").should be_true
+ end
+ end
+
+ end
+end
diff --git a/spec/support/shared/unit/api_error_inspector.rb b/spec/support/shared/unit/api_error_inspector.rb
new file mode 100644
index 0000000000..8231ceb195
--- /dev/null
+++ b/spec/support/shared/unit/api_error_inspector.rb
@@ -0,0 +1,192 @@
+#
+# 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.
+#
+
+
+
+# == API Error Inspector Examples
+# These tests are work in progress. They exercise the code enough to ensure it
+# runs without error, but don't make assertions about the output. This is
+# because aspects such as how information gets formatted, what's included, etc.
+# are still in flux. When testing an inspector, change the outputter to use
+# STDOUT and manually check the ouput.
+
+shared_examples_for "an api error inspector" do
+
+ before do
+ @node_name = "test-node.example.com"
+ @config = {
+ :validation_client_name => "testorg-validator",
+ :validation_key => "/etc/chef/testorg-validator.pem",
+ :chef_server_url => "https://chef-api.example.com",
+ :node_name => "testnode-name",
+ :client_key => "/etc/chef/client.pem"
+ }
+ @description = Chef::Formatters::ErrorDescription.new("Error registering the node:")
+ @outputter = Chef::Formatters::Outputter.new(StringIO.new, STDERR)
+ #@outputter = Chef::Formatters::Outputter.new(STDOUT, STDERR)
+
+ end
+
+ describe "when explaining a network error" do
+ before do
+ @exception = Errno::ECONNREFUSED.new("connection refused")
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+
+ end
+
+ describe "when explaining a 'private key missing' error" do
+ before do
+ @exception = Chef::Exceptions::PrivateKeyMissing.new("no private key yo")
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+
+ end
+
+ describe "when explaining a 401 caused by clock skew" do
+ before do
+ @response_body = "synchronize the clock on your host"
+ @response = Net::HTTPUnauthorized.new("1.1", "401", "(response) unauthorized")
+ @response.stub!(:body).and_return(@response_body)
+ @exception = Net::HTTPServerException.new("(exception) unauthorized", @response)
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+
+ end
+
+ describe "when explaining a 401 (no clock skew)" do
+ before do
+ @response_body = "check your key and node name"
+ @response = Net::HTTPUnauthorized.new("1.1", "401", "(response) unauthorized")
+ @response.stub!(:body).and_return(@response_body)
+ @exception = Net::HTTPServerException.new("(exception) unauthorized", @response)
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+
+ end
+
+ describe "when explaining a 403" do
+ before do
+ @response_body = "forbidden"
+ @response = Net::HTTPForbidden.new("1.1", "403", "(response) forbidden")
+ @response.stub!(:body).and_return(@response_body)
+ @exception = Net::HTTPServerException.new("(exception) forbidden", @response)
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+
+ end
+
+ describe "when explaining a 400" do
+ before do
+ @response_body = "didn't like your data"
+ @response = Net::HTTPBadRequest.new("1.1", "400", "(response) bad request")
+ @response.stub!(:body).and_return(@response_body)
+ @exception = Net::HTTPServerException.new("(exception) bad request", @response)
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+
+ end
+
+ describe "when explaining a 404" do
+ before do
+ @response_body = "probably caused by a redirect to a get"
+ @response = Net::HTTPNotFound.new("1.1", "404", "(response) not found")
+ @response.stub!(:body).and_return(@response_body)
+ @exception = Net::HTTPServerException.new("(exception) not found", @response)
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+ end
+
+ describe "when explaining a 500" do
+ before do
+ @response_body = "sad trombone"
+ @response = Net::HTTPInternalServerError.new("1.1", "500", "(response) internal server error")
+ @response.stub!(:body).and_return(@response_body)
+ @exception = Net::HTTPFatalError.new("(exception) internal server error", @response)
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+ end
+
+ describe "when explaining a 503" do
+ before do
+ @response_body = "sad trombone orchestra"
+ @response = Net::HTTPBadGateway.new("1.1", "502", "(response) bad gateway")
+ @response.stub!(:body).and_return(@response_body)
+ @exception = Net::HTTPFatalError.new("(exception) bad gateway", @response)
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+ end
+
+ describe "when explaining an unknown error" do
+ before do
+ @exception = RuntimeError.new("(exception) something went wrong")
+ @inspector = described_class.new(@node_name, @exception, @config)
+ @inspector.add_explanation(@description)
+ end
+
+ it "prints a nice message" do
+ @description.display(@outputter)
+ end
+ end
+
+end
diff --git a/spec/support/shared/unit/file_system_support.rb b/spec/support/shared/unit/file_system_support.rb
new file mode 100644
index 0000000000..8516292c67
--- /dev/null
+++ b/spec/support/shared/unit/file_system_support.rb
@@ -0,0 +1,110 @@
+#
+# Author:: John Keiser (<jkeiser@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 'chef/chef_fs/file_system'
+require 'chef/chef_fs/file_system/base_fs_dir'
+require 'chef/chef_fs/file_system/base_fs_object'
+
+module FileSystemSupport
+ class MemoryFile < Chef::ChefFS::FileSystem::BaseFSObject
+ def initialize(name, parent, value)
+ super(name, parent)
+ @value = value
+ end
+ def read
+ return @value
+ end
+ end
+
+ class MemoryDir < Chef::ChefFS::FileSystem::BaseFSDir
+ def initialize(name, parent)
+ super(name, parent)
+ @children = []
+ end
+ attr_reader :children
+ def child(name)
+ @children.select { |child| child.name == name }.first || Chef::ChefFS::FileSystem::NonexistentFSObject.new(name, self)
+ end
+ def add_child(child)
+ @children.push(child)
+ end
+ def can_have_child?(name, is_dir)
+ root.cannot_be_in_regex ? (name !~ root.cannot_be_in_regex) : true
+ end
+ end
+
+ class MemoryRoot < MemoryDir
+ def initialize(pretty_name, cannot_be_in_regex = nil)
+ super('', nil)
+ @pretty_name = pretty_name
+ @cannot_be_in_regex = cannot_be_in_regex
+ end
+
+ attr_reader :cannot_be_in_regex
+
+ def path_for_printing
+ @pretty_name
+ end
+ end
+
+ def memory_fs(pretty_name, value, cannot_be_in_regex = nil)
+ if !value.is_a?(Hash)
+ raise "memory_fs() must take a Hash"
+ end
+ dir = MemoryRoot.new(pretty_name, cannot_be_in_regex)
+ value.each do |key, child|
+ dir.add_child(memory_fs_value(child, key.to_s, dir))
+ end
+ dir
+ end
+
+ def memory_fs_value(value, name = '', parent = nil)
+ if value.is_a?(Hash)
+ dir = MemoryDir.new(name, parent)
+ value.each do |key, child|
+ dir.add_child(memory_fs_value(child, key.to_s, dir))
+ end
+ dir
+ else
+ MemoryFile.new(name, parent, value || "#{name}\n")
+ end
+ end
+
+ def pattern(p)
+ Chef::ChefFS::FilePattern.new(p)
+ end
+
+ def return_paths(*expected)
+ ReturnPaths.new(expected)
+ end
+
+ def no_blocking_calls_allowed
+ [ MemoryFile, MemoryDir ].each do |c|
+ [ :children, :exists?, :read ].each do |m|
+ c.any_instance.stub(m).and_raise("#{m.to_s} should not be called")
+ end
+ end
+ end
+
+ def list_should_yield_paths(fs, pattern_str, *expected_paths)
+ result_paths = []
+ Chef::ChefFS::FileSystem.list(fs, pattern(pattern_str)) { |result| result_paths << result.path }
+ result_paths.should =~ expected_paths
+ end
+end
+