summaryrefslogtreecommitdiff
path: root/spec/unit/mixin
diff options
context:
space:
mode:
Diffstat (limited to 'spec/unit/mixin')
-rw-r--r--spec/unit/mixin/checksum_spec.rb41
-rw-r--r--spec/unit/mixin/command_spec.rb105
-rw-r--r--spec/unit/mixin/convert_to_class_name_spec.rb50
-rw-r--r--spec/unit/mixin/deep_merge_spec.rb314
-rw-r--r--spec/unit/mixin/deprecation_spec.rb34
-rw-r--r--spec/unit/mixin/enforce_ownership_and_permissions_spec.rb93
-rw-r--r--spec/unit/mixin/params_validate_spec.rb372
-rw-r--r--spec/unit/mixin/path_sanity_spec.rb80
-rw-r--r--spec/unit/mixin/securable_spec.rb254
-rw-r--r--spec/unit/mixin/shell_out_spec.rb109
-rw-r--r--spec/unit/mixin/template_spec.rb104
-rw-r--r--spec/unit/mixin/xml_escape_spec.rb54
12 files changed, 1610 insertions, 0 deletions
diff --git a/spec/unit/mixin/checksum_spec.rb b/spec/unit/mixin/checksum_spec.rb
new file mode 100644
index 0000000000..dec270e18f
--- /dev/null
+++ b/spec/unit/mixin/checksum_spec.rb
@@ -0,0 +1,41 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/mixin/checksum'
+require 'stringio'
+
+class Chef::CMCCheck
+ include Chef::Mixin::Checksum
+end
+
+describe Chef::Mixin::Checksum do
+ before(:each) do
+ @checksum_user = Chef::CMCCheck.new
+ @cache = Chef::ChecksumCache.instance
+ @file = CHEF_SPEC_DATA + "/checksum/random.txt"
+ @stat = mock("File::Stat", { :mtime => Time.at(0) })
+ File.stub!(:stat).and_return(@stat)
+ end
+
+ it "gets the checksum of a file" do
+ @checksum_user.checksum(@file).should == "09ee9c8cc70501763563bcf9c218d71b2fbf4186bf8e1e0da07f0f42c80a3394"
+ end
+
+end
+
diff --git a/spec/unit/mixin/command_spec.rb b/spec/unit/mixin/command_spec.rb
new file mode 100644
index 0000000000..e143b8728b
--- /dev/null
+++ b/spec/unit/mixin/command_spec.rb
@@ -0,0 +1,105 @@
+#
+# Author:: Hongli Lai (hongli@phusion.nl)
+# Copyright:: Copyright (c) 2009 Phusion
+# 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::Mixin::Command do
+
+ if windows?
+
+ pending("TODO MOVE: this is a platform specific integration test.")
+
+ else
+
+ describe "popen4" do
+ include Chef::Mixin::Command
+
+ it "should be possible to read the child process's stdout and stderr" do
+ popen4("sh -c 'echo hello && echo world >&2'") do |pid, stdin, stdout, stderr|
+ stdout.read.should == "hello\n"
+ stderr.read.should == "world\n"
+ end
+ end
+
+ it "should default all commands to be run in the POSIX standard C locale" do
+ popen4("echo $LC_ALL") do |pid, stdin, stdout, stderr|
+ stdout.read.strip.should == "C"
+ end
+ end
+
+ it "should respect locale when specified explicitly" do
+ popen4("echo $LC_ALL", :environment => {"LC_ALL" => "es"}) do |pid, stdin, stdout, stderr|
+ stdout.read.strip.should == "es"
+ end
+ end
+
+ it "should end when the child process reads from STDIN and a block is given" do
+ lambda {Timeout.timeout(10) do
+ popen4("ruby -e 'while gets; end'", :waitlast => true) do |pid, stdin, stdout, stderr|
+ (1..5).each { |i| stdin.puts "#{i}" }
+ end
+ end
+ }.should_not raise_error
+ end
+
+ describe "when a process detaches but doesn't close STDOUT and STDERR [CHEF-584]" do
+
+ it "returns immediately after the first child process exits" do
+ lambda {Timeout.timeout(10) do
+ pid, stdin,stdout,stderr = nil,nil,nil,nil
+ evil_forker="exit if fork; 10.times { sleep 1}"
+ popen4("ruby -e '#{evil_forker}'") do |pid,stdin,stdout,stderr|
+ end
+ end}.should_not raise_error
+ end
+
+ end
+
+ end
+
+ describe "run_command" do
+ include Chef::Mixin::Command
+
+ it "logs the command's stderr and stdout output if the command failed" do
+ Chef::Log.stub!(:level).and_return(:debug)
+ begin
+ run_command(:command => "sh -c 'echo hello; echo world >&2; false'")
+ violated "Exception expected, but nothing raised."
+ rescue => e
+ e.message.should =~ /STDOUT: hello/
+ e.message.should =~ /STDERR: world/
+ end
+ end
+
+ describe "when a process detaches but doesn't close STDOUT and STDERR [CHEF-584]" do
+ it "returns successfully" do
+ # CHEF-2916 might have added a slight delay here, or our CI
+ # infrastructure is burdened. Bumping timeout from 2 => 4 --
+ # btm
+ # Serdar - During Solaris tests, we've seen that processes
+ # are taking a long time to exit. Bumping timeout now to 10.
+ lambda {Timeout.timeout(10) do
+ evil_forker="exit if fork; 10.times { sleep 1}"
+ run_command(:command => "ruby -e '#{evil_forker}'")
+ end}.should_not raise_error
+ end
+
+ end
+ end
+ end
+end
diff --git a/spec/unit/mixin/convert_to_class_name_spec.rb b/spec/unit/mixin/convert_to_class_name_spec.rb
new file mode 100644
index 0000000000..b78d3f9101
--- /dev/null
+++ b/spec/unit/mixin/convert_to_class_name_spec.rb
@@ -0,0 +1,50 @@
+#
+# Author:: Daniel DeLeo (<dan@kallistec.com>)
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+class ConvertToClassTestHarness
+ include Chef::Mixin::ConvertToClassName
+end
+
+describe Chef::Mixin::ConvertToClassName do
+
+ before do
+ @convert = ConvertToClassTestHarness.new
+ end
+
+ it "converts a_snake_case_word to a CamelCaseWord" do
+ @convert.convert_to_class_name("now_camelized").should == "NowCamelized"
+ end
+
+ it "converts a CamelCaseWord to a snake_case_word" do
+ @convert.convert_to_snake_case("NowImASnake").should == "now_im_a_snake"
+ end
+
+ it "removes the base classes before snake casing" do
+ @convert.convert_to_snake_case("NameSpaced::Class::ThisIsWin", "NameSpaced::Class").should == "this_is_win"
+ end
+
+ it "removes the base classes without explicitly naming them and returns snake case" do
+ @convert.snake_case_basename("NameSpaced::Class::ExtraWin").should == "extra_win"
+ end
+
+ it "interprets non-alphanumeric characters in snake case as word boundaries" do
+ @convert.convert_to_class_name("now_camelized_without-hyphen").should == "NowCamelizedWithoutHyphen"
+ end
+end
diff --git a/spec/unit/mixin/deep_merge_spec.rb b/spec/unit/mixin/deep_merge_spec.rb
new file mode 100644
index 0000000000..cbc9b1544f
--- /dev/null
+++ b/spec/unit/mixin/deep_merge_spec.rb
@@ -0,0 +1,314 @@
+#
+# Author:: Matthew Kent (<mkent@magoazul.com>)
+# Author:: Steve Midgley (http://www.misuse.org/science)
+# Copyright:: Copyright (c) 2010 Matthew Kent
+# Copyright:: Copyright (c) 2008 Steve Midgley
+# 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.
+
+# Notice:
+# This code is imported from deep_merge by Steve Midgley. deep_merge is
+# available under the MIT license from
+# http://trac.misuse.org/science/wiki/DeepMerge
+
+require 'spec_helper'
+
+# Test coverage from the original author converted to rspec
+describe Chef::Mixin::DeepMerge, "deep_merge!" do
+ before do
+ @dm = Chef::Mixin::DeepMerge
+ @field_ko_prefix = '!merge'
+ end
+
+ # deep_merge core tests - moving from basic to more complex
+
+ it "tests merging an hash w/array into blank hash" do
+ hash_src = {'id' => '2'}
+ hash_dst = {}
+ @dm.deep_merge!(hash_src.dup, hash_dst)
+ hash_dst.should == hash_src
+ end
+
+ it "tests merging an hash w/array into blank hash" do
+ hash_src = {'region' => {'id' => ['227', '2']}}
+ hash_dst = {}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == hash_src
+ end
+
+ it "tests merge from empty hash" do
+ hash_src = {}
+ hash_dst = {"property" => ["2","4"]}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"property" => ["2","4"]}
+ end
+
+ it "tests merge to empty hash" do
+ hash_src = {"property" => ["2","4"]}
+ hash_dst = {}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"property" => ["2","4"]}
+ end
+
+ it "tests simple string overwrite" do
+ hash_src = {"name" => "value"}
+ hash_dst = {"name" => "value1"}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"name" => "value"}
+ end
+
+ it "tests simple string overwrite of empty hash" do
+ hash_src = {"name" => "value"}
+ hash_dst = {}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == hash_src
+ end
+
+ it "tests hashes holding array" do
+ hash_src = {"property" => ["1","3"]}
+ hash_dst = {"property" => ["2","4"]}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"property" => ["2","4","1","3"]}
+ end
+
+ it "tests hashes holding hashes holding arrays (array with duplicate elements is merged with dest then src" do
+ hash_src = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["1", "4+"]}}
+ hash_dst = {"property" => {"bedroom_count" => ["3", "2"], "bathroom_count" => ["2"]}}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"property" => {"bedroom_count" => ["3","2","1"], "bathroom_count" => ["2", "1", "4+"]}}
+ end
+
+ it "tests hash holding hash holding array v string (string is overwritten by array)" do
+ hash_src = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["1", "4+"]}}
+ hash_dst = {"property" => {"bedroom_count" => "3", "bathroom_count" => ["2"]}}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2","1","4+"]}}
+ end
+
+ it "tests hash holding hash holding string v array (array is overwritten by string)" do
+ hash_src = {"property" => {"bedroom_count" => "3", "bathroom_count" => ["1", "4+"]}}
+ hash_dst = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2"]}}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"property" => {"bedroom_count" => "3", "bathroom_count" => ["2","1","4+"]}}
+ end
+
+ it "tests hash holding hash holding hash v array (array is overwritten by hash)" do
+ hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["1", "4+"]}}
+ hash_dst = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2"]}}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["2","1","4+"]}}
+ end
+
+ it "tests 3 hash layers holding integers (integers are overwritten by source)" do
+ hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["1", "4+"]}}
+ hash_dst = {"property" => {"bedroom_count" => {"king_bed" => 2, "queen_bed" => 4}, "bathroom_count" => ["2"]}}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["2","1","4+"]}}
+ end
+
+ it "tests 3 hash layers holding arrays of int (arrays are merged)" do
+ hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3], "queen_bed" => [1]}, "bathroom_count" => ["1", "4+"]}}
+ hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4,1]}, "bathroom_count" => ["2","1","4+"]}}
+ end
+
+ it "tests 1 hash overwriting 3 hash layers holding arrays of int" do
+ hash_src = {"property" => "1"}
+ hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"property" => "1"}
+ end
+
+ it "tests 3 hash layers holding arrays of int (arrays are merged) but second hash's array is overwritten" do
+ hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3], "queen_bed" => [1]}, "bathroom_count" => "1"}}
+ hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4,1]}, "bathroom_count" => "1"}}
+ end
+
+ it "tests 3 hash layers holding arrays of int, but one holds int. This one overwrites, but the rest merge" do
+ hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => [1]}, "bathroom_count" => ["1"]}}
+ hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => [4,1]}, "bathroom_count" => ["2","1"]}}
+ end
+
+ it "tests 3 hash layers holding arrays of int, but source is incomplete." do
+ hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3]}, "bathroom_count" => ["1"]}}
+ hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4]}, "bathroom_count" => ["2","1"]}}
+ end
+
+ it "tests 3 hash layers holding arrays of int, but source is shorter and has new 2nd level ints." do
+ hash_src = {"property" => {"bedroom_count" => {2=>3, "king_bed" => [3]}, "bathroom_count" => ["1"]}}
+ hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"property" => {"bedroom_count" => {2=>3, "king_bed" => [2,3], "queen_bed" => [4]}, "bathroom_count" => ["2","1"]}}
+ end
+
+ it "tests 3 hash layers holding arrays of int, but source is empty" do
+ hash_src = {}
+ hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
+ end
+
+ it "tests 3 hash layers holding arrays of int, but dest is empty" do
+ hash_src = {"property" => {"bedroom_count" => {2=>3, "king_bed" => [3]}, "bathroom_count" => ["1"]}}
+ hash_dst = {}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"property" => {"bedroom_count" => {2=>3, "king_bed" => [3]}, "bathroom_count" => ["1"]}}
+ end
+
+ it "tests hash holding arrays of arrays" do
+ hash_src = {["1", "2", "3"] => ["1", "2"]}
+ hash_dst = {["4", "5"] => ["3"]}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {["1","2","3"] => ["1", "2"], ["4", "5"] => ["3"]}
+ end
+
+ it "tests merging of hash with blank hash, and make sure that source array split does not function when turned off" do
+ hash_src = {'property' => {'bedroom_count' => ["1","2,3"]}}
+ hash_dst = {}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {'property' => {'bedroom_count' => ["1","2,3"]}}
+ end
+
+ it "tests merging into a blank hash" do
+ hash_src = {"action"=>"browse", "controller"=>"results"}
+ hash_dst = {}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == hash_src
+ end
+
+ it "tests are unmerged hashes passed unmodified w/out :unpack_arrays?" do
+ hash_src = {"amenity"=>{"id"=>["26,27"]}}
+ hash_dst = {}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"amenity"=>{"id"=>["26,27"]}}
+ end
+
+ it "tests hash of array of hashes" do
+ hash_src = {"item" => [{"1" => "3"}, {"2" => "4"}]}
+ hash_dst = {"item" => [{"3" => "5"}]}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"item" => [{"3" => "5"}, {"1" => "3"}, {"2" => "4"}]}
+ end
+
+ # Additions since import
+ it "should overwrite true with false when merging boolean values" do
+ hash_src = {"valid" => false}
+ hash_dst = {"valid" => true}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"valid" => false}
+ end
+
+ it "should overwrite false with true when merging boolean values" do
+ hash_src = {"valid" => true}
+ hash_dst = {"valid" => false}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"valid" => true}
+ end
+
+ it "should overwrite a string with an empty string when merging string values" do
+ hash_src = {"item" => " "}
+ hash_dst = {"item" => "orange"}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"item" => " "}
+ end
+
+ it "should overwrite an empty string with a string when merging string values" do
+ hash_src = {"item" => "orange"}
+ hash_dst = {"item" => " "}
+ @dm.deep_merge!(hash_src, hash_dst)
+ hash_dst.should == {"item" => "orange"}
+ end
+end # deep_merge!
+
+# Chef specific
+describe Chef::Mixin::DeepMerge do
+ before do
+ @dm = Chef::Mixin::DeepMerge
+ end
+
+ describe "merge" do
+ it "should merge a hash into an empty hash" do
+ hash_dst = {}
+ hash_src = {'id' => '2'}
+ @dm.merge(hash_dst, hash_src).should == hash_src
+ end
+
+ it "should merge a nested hash into an empty hash" do
+ hash_dst = {}
+ hash_src = {'region' => {'id' => ['227', '2']}}
+ @dm.merge(hash_dst, hash_src).should == hash_src
+ end
+
+ it "should overwrite as string value when merging hashes" do
+ hash_dst = {"name" => "value1"}
+ hash_src = {"name" => "value"}
+ @dm.merge(hash_dst, hash_src).should == {"name" => "value"}
+ end
+
+ it "should merge arrays within hashes" do
+ hash_dst = {"property" => ["2","4"]}
+ hash_src = {"property" => ["1","3"]}
+ @dm.merge(hash_dst, hash_src).should == {"property" => ["2","4","1","3"]}
+ end
+
+ it "should merge deeply nested hashes" do
+ hash_dst = {"property" => {"values" => {"are" => "falling", "can" => "change"}}}
+ hash_src = {"property" => {"values" => {"are" => "stable", "may" => "rise"}}}
+ @dm.merge(hash_dst, hash_src).should == {"property" => {"values" => {"are" => "stable", "can" => "change", "may" => "rise"}}}
+ end
+
+ it "should not modify the source or destination during the merge" do
+ hash_dst = {"property" => ["1","2","3"]}
+ hash_src = {"property" => ["4","5","6"]}
+ ret = @dm.merge(hash_dst, hash_src)
+ hash_dst.should == {"property" => ["1","2","3"]}
+ hash_src.should == {"property" => ["4","5","6"]}
+ ret.should == {"property" => ["1","2","3","4","5","6"]}
+ end
+
+ end
+
+ describe "role_merge" do
+ it "errors out if knockout merge use is detected in an array" do
+ hash_dst = {"property" => ["2","4"]}
+ hash_src = {"property" => ["1","!merge:4"]}
+ lambda {@dm.role_merge(hash_dst, hash_src)}.should raise_error(Chef::Mixin::DeepMerge::InvalidSubtractiveMerge)
+ end
+
+ it "errors out if knockout merge use is detected in an array (reversed merge order)" do
+ hash_dst = {"property" => ["1","!merge:4"]}
+ hash_src = {"property" => ["2","4"]}
+ lambda {@dm.role_merge(hash_dst, hash_src)}.should raise_error(Chef::Mixin::DeepMerge::InvalidSubtractiveMerge)
+ end
+
+ it "errors out if knockout merge use is detected in a string" do
+ hash_dst = {"property" => ["2","4"]}
+ hash_src = {"property" => "!merge"}
+ lambda {@dm.role_merge(hash_dst, hash_src)}.should raise_error(Chef::Mixin::DeepMerge::InvalidSubtractiveMerge)
+ end
+
+ it "errors out if knockout merge use is detected in a string (reversed merge order)" do
+ hash_dst = {"property" => "!merge"}
+ hash_src= {"property" => ["2","4"]}
+ lambda {@dm.role_merge(hash_dst, hash_src)}.should raise_error(Chef::Mixin::DeepMerge::InvalidSubtractiveMerge)
+ end
+ end
+end
diff --git a/spec/unit/mixin/deprecation_spec.rb b/spec/unit/mixin/deprecation_spec.rb
new file mode 100644
index 0000000000..1b62dcd124
--- /dev/null
+++ b/spec/unit/mixin/deprecation_spec.rb
@@ -0,0 +1,34 @@
+#
+# 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 'chef/mixin/deprecation'
+
+describe Chef::Mixin::Deprecation::DeprecatedInstanceVariable do
+ before do
+ Chef::Log.logger = Logger.new(StringIO.new)
+
+ @deprecated_ivar = Chef::Mixin::Deprecation::DeprecatedInstanceVariable.new('value', 'an_ivar')
+ end
+
+ it "forward method calls to the target object" do
+ @deprecated_ivar.length.should == 5
+ @deprecated_ivar.to_sym.should == :value
+ end
+
+end
diff --git a/spec/unit/mixin/enforce_ownership_and_permissions_spec.rb b/spec/unit/mixin/enforce_ownership_and_permissions_spec.rb
new file mode 100644
index 0000000000..53a8260cdc
--- /dev/null
+++ b/spec/unit/mixin/enforce_ownership_and_permissions_spec.rb
@@ -0,0 +1,93 @@
+#
+# Author:: Mark Mzyk (<mmzyk@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 'etc'
+require 'ostruct'
+
+describe Chef::Mixin::EnforceOwnershipAndPermissions do
+
+ before(:each) do
+ @node = Chef::Node.new
+ @node.name "make_believe"
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+ @tmpdir = Dir.mktmpdir
+ @resource = Chef::Resource::File.new("#{@tmpdir}/madeup.txt")
+ FileUtils.touch @resource.path
+ @resource.owner "adam"
+ @provider = Chef::Provider::File.new(@resource, @run_context)
+ @provider.current_resource = @resource
+ end
+
+ after(:each) do
+ FileUtils.rm_rf(@tmpdir)
+ end
+
+ it "should call set_all on the file access control object" do
+ Chef::FileAccessControl.any_instance.should_receive(:set_all)
+ @provider.enforce_ownership_and_permissions
+ end
+
+ context "when nothing was updated" do
+ before do
+ Chef::FileAccessControl.any_instance.stub(:uid_from_resource).and_return(0)
+ Chef::FileAccessControl.any_instance.stub(:requires_changes?).and_return(false)
+
+ passwd_struct = if windows?
+ Struct::Passwd.new("root", "x", 0, 0, "/root", "/bin/bash")
+ else
+ Struct::Passwd.new("root", "x", 0, 0, "root", "/root", "/bin/bash")
+ end
+ group_struct = OpenStruct.new(:name => "root", :passwd => "x", :gid => 0)
+ Etc.stub!(:getpwuid).and_return(passwd_struct)
+ Etc.stub!(:getgrgid).and_return(group_struct)
+ end
+
+ it "does not set updated_by_last_action on the new resource" do
+ @provider.new_resource.should_not_receive(:updated_by_last_action)
+
+ Chef::FileAccessControl.any_instance.stub(:set_all)
+ @provider.run_action(:create)
+ end
+
+ end
+
+ context "when something was modified" do
+ before do
+ Chef::FileAccessControl.any_instance.stub(:requires_changes?).and_return(true)
+ Chef::FileAccessControl.any_instance.stub(:uid_from_resource).and_return(0)
+
+ passwd_struct = if windows?
+ Struct::Passwd.new("root", "x", 0, 0, "/root", "/bin/bash")
+ else
+ Struct::Passwd.new("root", "x", 0, 0, "root", "/root", "/bin/bash")
+ end
+ group_struct = OpenStruct.new(:name => "root", :passwd => "x", :gid => 0)
+ Etc.stub!(:getpwuid).and_return(passwd_struct)
+ Etc.stub!(:getgrgid).and_return(group_struct)
+ end
+
+ it "sets updated_by_last_action on the new resource" do
+ @provider.new_resource.should_receive(:updated_by_last_action)
+ Chef::FileAccessControl.any_instance.stub(:set_all)
+ @provider.run_action(:create)
+ end
+ end
+
+end
diff --git a/spec/unit/mixin/params_validate_spec.rb b/spec/unit/mixin/params_validate_spec.rb
new file mode 100644
index 0000000000..b79156109b
--- /dev/null
+++ b/spec/unit/mixin/params_validate_spec.rb
@@ -0,0 +1,372 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+class TinyClass
+ include Chef::Mixin::ParamsValidate
+
+ def music(is_good=true)
+ is_good
+ end
+end
+
+describe Chef::Mixin::ParamsValidate do
+ before(:each) do
+ @vo = TinyClass.new()
+ end
+
+ it "should allow a hash and a hash as arguments to validate" do
+ lambda { @vo.validate({:one => "two"}, {}) }.should_not raise_error(ArgumentError)
+ end
+
+ it "should raise an argument error if validate is called incorrectly" do
+ lambda { @vo.validate("one", "two") }.should raise_error(ArgumentError)
+ end
+
+ it "should require validation map keys to be symbols or strings" do
+ lambda { @vo.validate({:one => "two"}, { :one => true }) }.should_not raise_error(ArgumentError)
+ lambda { @vo.validate({:one => "two"}, { "one" => true }) }.should_not raise_error(ArgumentError)
+ lambda { @vo.validate({:one => "two"}, { Hash.new => true }) }.should raise_error(ArgumentError)
+ end
+
+ it "should allow options to be required with true" do
+ lambda { @vo.validate({:one => "two"}, { :one => true }) }.should_not raise_error(ArgumentError)
+ end
+
+ it "should allow options to be optional with false" do
+ lambda { @vo.validate({}, {:one => false})}.should_not raise_error(ArgumentError)
+ end
+
+ it "should allow you to check what kind_of? thing an argument is with kind_of" do
+ lambda {
+ @vo.validate(
+ {:one => "string"},
+ {
+ :one => {
+ :kind_of => String
+ }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+
+ lambda {
+ @vo.validate(
+ {:one => "string"},
+ {
+ :one => {
+ :kind_of => Array
+ }
+ }
+ )
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should allow you to specify an argument is required with required" do
+ lambda {
+ @vo.validate(
+ {:one => "string"},
+ {
+ :one => {
+ :required => true
+ }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+
+ lambda {
+ @vo.validate(
+ {:two => "string"},
+ {
+ :one => {
+ :required => true
+ }
+ }
+ )
+ }.should raise_error(ArgumentError)
+
+ lambda {
+ @vo.validate(
+ {:two => "string"},
+ {
+ :one => {
+ :required => false
+ }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+ end
+
+ it "should allow you to specify whether an object has a method with respond_to" do
+ lambda {
+ @vo.validate(
+ {:one => @vo},
+ {
+ :one => {
+ :respond_to => "validate"
+ }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+
+ lambda {
+ @vo.validate(
+ {:one => @vo},
+ {
+ :one => {
+ :respond_to => "monkey"
+ }
+ }
+ )
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should allow you to specify whether an object has all the given methods with respond_to and an array" do
+ lambda {
+ @vo.validate(
+ {:one => @vo},
+ {
+ :one => {
+ :respond_to => ["validate", "music"]
+ }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+
+ lambda {
+ @vo.validate(
+ {:one => @vo},
+ {
+ :one => {
+ :respond_to => ["monkey", "validate"]
+ }
+ }
+ )
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should let you set a default value with default => value" do
+ arguments = Hash.new
+ @vo.validate(arguments, {
+ :one => {
+ :default => "is the loneliest number"
+ }
+ })
+ arguments[:one].should == "is the loneliest number"
+ end
+
+ it "should let you check regular expressions" do
+ lambda {
+ @vo.validate(
+ { :one => "is good" },
+ {
+ :one => {
+ :regex => /^is good$/
+ }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+
+ lambda {
+ @vo.validate(
+ { :one => "is good" },
+ {
+ :one => {
+ :regex => /^is bad$/
+ }
+ }
+ )
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should let you specify your own callbacks" do
+ lambda {
+ @vo.validate(
+ { :one => "is good" },
+ {
+ :one => {
+ :callbacks => {
+ "should be equal to is good" => lambda { |a|
+ a == "is good"
+ },
+ }
+ }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+
+ lambda {
+ @vo.validate(
+ { :one => "is bad" },
+ {
+ :one => {
+ :callbacks => {
+ "should be equal to 'is good'" => lambda { |a|
+ a == "is good"
+ },
+ }
+ }
+ }
+ )
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should let you combine checks" do
+ args = { :one => "is good", :two => "is bad" }
+ lambda {
+ @vo.validate(
+ args,
+ {
+ :one => {
+ :kind_of => String,
+ :respond_to => [ :to_s, :upcase ],
+ :regex => /^is good/,
+ :callbacks => {
+ "should be your friend" => lambda { |a|
+ a == "is good"
+ }
+ },
+ :required => true
+ },
+ :two => {
+ :kind_of => String,
+ :required => false
+ },
+ :three => { :default => "neato mosquito" }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+ args[:three].should == "neato mosquito"
+ lambda {
+ @vo.validate(
+ args,
+ {
+ :one => {
+ :kind_of => String,
+ :respond_to => [ :to_s, :upcase ],
+ :regex => /^is good/,
+ :callbacks => {
+ "should be your friend" => lambda { |a|
+ a == "is good"
+ }
+ },
+ :required => true
+ },
+ :two => {
+ :kind_of => Hash,
+ :required => false
+ },
+ :three => { :default => "neato mosquito" }
+ }
+ )
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should raise an ArgumentError if the validation map has an unknown check" do
+ lambda { @vo.validate(
+ { :one => "two" },
+ {
+ :one => {
+ :busted => "check"
+ }
+ }
+ )
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should accept keys that are strings in the options" do
+ lambda {
+ @vo.validate({ "one" => "two" }, { :one => { :regex => /^two$/ }})
+ }.should_not raise_error(ArgumentError)
+ end
+
+ it "should allow an array to kind_of" do
+ lambda {
+ @vo.validate(
+ {:one => "string"},
+ {
+ :one => {
+ :kind_of => [ String, Array ]
+ }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+ lambda {
+ @vo.validate(
+ {:one => ["string"]},
+ {
+ :one => {
+ :kind_of => [ String, Array ]
+ }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+ lambda {
+ @vo.validate(
+ {:one => Hash.new},
+ {
+ :one => {
+ :kind_of => [ String, Array ]
+ }
+ }
+ )
+ }.should raise_error(ArgumentError)
+ end
+
+ it "asserts that a value returns false from a predicate method" do
+ lambda do
+ @vo.validate({:not_blank => "should pass"},
+ {:not_blank => {:cannot_be => :nil, :cannot_be => :empty}})
+ end.should_not raise_error
+ lambda do
+ @vo.validate({:not_blank => ""},
+ {:not_blank => {:cannot_be => :nil, :cannot_be => :empty}})
+ end.should raise_error(Chef::Exceptions::ValidationFailed)
+ end
+
+ it "should set and return a value, then return the same value" do
+ value = "meow"
+ @vo.set_or_return(:test, value, {}).object_id.should == value.object_id
+ @vo.set_or_return(:test, nil, {}).object_id.should == value.object_id
+ end
+
+ it "should set and return a default value when the argument is nil, then return the same value" do
+ value = "meow"
+ @vo.set_or_return(:test, nil, { :default => value }).object_id.should == value.object_id
+ @vo.set_or_return(:test, nil, {}).object_id.should == value.object_id
+ end
+
+ it "should raise an ArgumentError when argument is nil and required is true" do
+ lambda {
+ @vo.set_or_return(:test, nil, { :required => true })
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should not raise an error when argument is nil and required is false" do
+ lambda {
+ @vo.set_or_return(:test, nil, { :required => false })
+ }.should_not raise_error(ArgumentError)
+ end
+
+ it "should set and return @name, then return @name for foo when argument is nil" do
+ value = "meow"
+ @vo.set_or_return(:name, value, { }).object_id.should == value.object_id
+ @vo.set_or_return(:foo, nil, { :name_attribute => true }).object_id.should == value.object_id
+ end
+
+end
diff --git a/spec/unit/mixin/path_sanity_spec.rb b/spec/unit/mixin/path_sanity_spec.rb
new file mode 100644
index 0000000000..e38ee7dc8a
--- /dev/null
+++ b/spec/unit/mixin/path_sanity_spec.rb
@@ -0,0 +1,80 @@
+#
+# 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'
+
+class PathSanityTestHarness
+ include Chef::Mixin::PathSanity
+end
+
+describe Chef::Mixin::PathSanity do
+
+ before do
+ @sanity = PathSanityTestHarness.new
+ end
+
+ describe "when enforcing path sanity" do
+ before do
+ Chef::Config[:enforce_path_sanity] = true
+ @ruby_bindir = '/some/ruby/bin'
+ @gem_bindir = '/some/gem/bin'
+ Gem.stub!(:bindir).and_return(@gem_bindir)
+ RbConfig::CONFIG.stub!(:[]).with('bindir').and_return(@ruby_bindir)
+ Chef::Platform.stub!(:windows?).and_return(false)
+ end
+
+ it "adds all useful PATHs that are not yet in PATH to PATH" do
+ env = {"PATH" => ""}
+ @sanity.enforce_path_sanity(env)
+ env["PATH"].should == "#{@ruby_bindir}:#{@gem_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+ end
+
+ it "does not re-add paths that already exist in PATH" do
+ env = {"PATH" => "/usr/bin:/sbin:/bin"}
+ @sanity.enforce_path_sanity(env)
+ env["PATH"].should == "/usr/bin:/sbin:/bin:#{@ruby_bindir}:#{@gem_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin"
+ end
+
+ it "adds the current executing Ruby's bindir and Gem bindir to the PATH" do
+ env = {"PATH" => ""}
+ @sanity.enforce_path_sanity(env)
+ env["PATH"].should == "#{@ruby_bindir}:#{@gem_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+ end
+
+ it "does not create entries for Ruby/Gem bindirs if they exist in SANE_PATH or PATH" do
+ ruby_bindir = '/usr/bin'
+ gem_bindir = '/yo/gabba/gabba'
+ Gem.stub!(:bindir).and_return(gem_bindir)
+ RbConfig::CONFIG.stub!(:[]).with('bindir').and_return(ruby_bindir)
+ env = {"PATH" => gem_bindir}
+ @sanity.enforce_path_sanity(env)
+ env["PATH"].should == "/yo/gabba/gabba:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+ end
+
+ it "builds a valid windows path" do
+ ruby_bindir = 'C:\ruby\bin'
+ gem_bindir = 'C:\gems\bin'
+ Gem.stub!(:bindir).and_return(gem_bindir)
+ RbConfig::CONFIG.stub!(:[]).with('bindir').and_return(ruby_bindir)
+ Chef::Platform.stub!(:windows?).and_return(true)
+ env = {"PATH" => 'C:\Windows\system32;C:\mr\softie'}
+ @sanity.enforce_path_sanity(env)
+ env["PATH"].should == "C:\\Windows\\system32;C:\\mr\\softie;#{ruby_bindir};#{gem_bindir}"
+ end
+ end
+end
diff --git a/spec/unit/mixin/securable_spec.rb b/spec/unit/mixin/securable_spec.rb
new file mode 100644
index 0000000000..d2e8770c9d
--- /dev/null
+++ b/spec/unit/mixin/securable_spec.rb
@@ -0,0 +1,254 @@
+#
+# Author:: Mark Mzyk (<mmzyk@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::Mixin::Securable do
+
+ before(:each) do
+ @securable = Object.new
+ @securable.send(:extend, Chef::Mixin::Securable)
+ @securable.send(:extend, Chef::Mixin::ParamsValidate)
+ end
+
+ it "should accept a group name or id for group" do
+ lambda { @securable.group "root" }.should_not raise_error(ArgumentError)
+ lambda { @securable.group 123 }.should_not raise_error(ArgumentError)
+ lambda { @securable.group "root*goo" }.should raise_error(ArgumentError)
+ end
+
+ it "should accept a user name or id for owner" do
+ lambda { @securable.owner "root" }.should_not raise_error(ArgumentError)
+ lambda { @securable.owner 123 }.should_not raise_error(ArgumentError)
+ lambda { @securable.owner "root*goo" }.should raise_error(ArgumentError)
+ end
+
+ it "allows the owner to be specified as #user" do
+ @securable.should respond_to(:user)
+ end
+
+ describe "unix-specific behavior" do
+ before(:each) do
+ platform_mock :unix do
+ @original_config = Chef::Config.hash_dup
+ load File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "chef", "config.rb")
+ load File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "chef", "mixin", "securable.rb")
+ @securable = Object.new
+ @securable.send(:extend, Chef::Mixin::Securable)
+ @securable.send(:extend, Chef::Mixin::ParamsValidate)
+ end
+ end
+
+ after(:each) do
+ Chef::Config.configuration = @original_config
+ end
+
+ it "should accept a group name or id for group with spaces and backslashes" do
+ lambda { @securable.group 'test\ group' }.should_not raise_error(ArgumentError)
+ end
+
+ it "should accept a unix file mode in string form as an octal number" do
+ lambda { @securable.mode "0" }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode "0000" }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode "0111" }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode "0444" }.should_not raise_error(ArgumentError)
+
+ lambda { @securable.mode "111" }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode "444" }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode "7777" }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode "07777" }.should_not raise_error(ArgumentError)
+
+ lambda { @securable.mode "-01" }.should raise_error(ArgumentError)
+ lambda { @securable.mode "010000" }.should raise_error(ArgumentError)
+ lambda { @securable.mode "-1" }.should raise_error(ArgumentError)
+ lambda { @securable.mode "10000" }.should raise_error(ArgumentError)
+
+ lambda { @securable.mode "07778" }.should raise_error(ArgumentError)
+ lambda { @securable.mode "7778" }.should raise_error(ArgumentError)
+ lambda { @securable.mode "4095" }.should raise_error(ArgumentError)
+
+ lambda { @securable.mode "0foo1234" }.should raise_error(ArgumentError)
+ lambda { @securable.mode "foo1234" }.should raise_error(ArgumentError)
+ end
+
+ it "should accept a unix file mode in numeric form as a ruby-interpreted integer" do
+ lambda { @securable.mode 0 }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode 0000 }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode 444 }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode 0444 }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode 07777 }.should_not raise_error(ArgumentError)
+
+ lambda { @securable.mode 292 }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode 4095 }.should_not raise_error(ArgumentError)
+
+ lambda { @securable.mode 0111 }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode 73 }.should_not raise_error(ArgumentError)
+
+ lambda { @securable.mode -01 }.should raise_error(ArgumentError)
+ lambda { @securable.mode 010000 }.should raise_error(ArgumentError)
+ lambda { @securable.mode -1 }.should raise_error(ArgumentError)
+ lambda { @securable.mode 4096 }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "windows-specific behavior" do
+ before(:each) do
+ platform_mock :windows do
+ @original_config = Chef::Config.hash_dup
+ load File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "chef", "config.rb")
+ load File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "chef", "mixin", "securable.rb")
+ @securable = Object.new
+ @securable.send(:extend, Chef::Mixin::Securable)
+ @securable.send(:extend, Chef::Mixin::ParamsValidate)
+ end
+ end
+
+ after(:all) do
+ Chef::Config.configuration = @original_config if @original_config
+ end
+
+ after(:each) do
+ Chef::Config.configuration = @original_config if @original_config
+ end
+
+ it "should not accept a group name or id for group with spaces and multiple backslashes" do
+ lambda { @securable.group 'test\ \group' }.should raise_error(ArgumentError)
+ end
+
+ it "should accept a unix file mode in string form as an octal number" do
+ lambda { @securable.mode "0" }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode "0000" }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode "0111" }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode "0444" }.should_not raise_error(ArgumentError)
+
+ lambda { @securable.mode "111" }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode "444" }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode "7777" }.should raise_error(ArgumentError)
+ lambda { @securable.mode "07777" }.should raise_error(ArgumentError)
+
+ lambda { @securable.mode "-01" }.should raise_error(ArgumentError)
+ lambda { @securable.mode "010000" }.should raise_error(ArgumentError)
+ lambda { @securable.mode "-1" }.should raise_error(ArgumentError)
+ lambda { @securable.mode "10000" }.should raise_error(ArgumentError)
+
+ lambda { @securable.mode "07778" }.should raise_error(ArgumentError)
+ lambda { @securable.mode "7778" }.should raise_error(ArgumentError)
+ lambda { @securable.mode "4095" }.should raise_error(ArgumentError)
+
+ lambda { @securable.mode "0foo1234" }.should raise_error(ArgumentError)
+ lambda { @securable.mode "foo1234" }.should raise_error(ArgumentError)
+ end
+
+ it "should accept a unix file mode in numeric form as a ruby-interpreted integer" do
+ lambda { @securable.mode 0 }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode 0000 }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode 444 }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode 0444 }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode 07777 }.should raise_error(ArgumentError)
+
+ lambda { @securable.mode 292 }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode 4095 }.should raise_error(ArgumentError)
+
+ lambda { @securable.mode 0111 }.should_not raise_error(ArgumentError)
+ lambda { @securable.mode 73 }.should_not raise_error(ArgumentError)
+
+ lambda { @securable.mode -01 }.should raise_error(ArgumentError)
+ lambda { @securable.mode 010000 }.should raise_error(ArgumentError)
+ lambda { @securable.mode -1 }.should raise_error(ArgumentError)
+ lambda { @securable.mode 4096 }.should raise_error(ArgumentError)
+ end
+
+ it "should allow you to specify :full_control, :modify, :read_execute, :read, and :write rights" do
+ lambda { @securable.rights :full_control, "The Dude" }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :modify, "The Dude" }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read_execute, "The Dude" }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read, "The Dude" }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :write, "The Dude" }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :to_party, "The Dude" }.should raise_error(ArgumentError)
+ end
+
+ it "should allow you to specify :full_control, :modify, :read_execute, :read, and :write deny_rights" do
+ lambda { @securable.deny_rights :full_control, "The Dude" }.should_not raise_error(ArgumentError)
+ lambda { @securable.deny_rights :modify, "The Dude" }.should_not raise_error(ArgumentError)
+ lambda { @securable.deny_rights :read_execute, "The Dude" }.should_not raise_error(ArgumentError)
+ lambda { @securable.deny_rights :read, "The Dude" }.should_not raise_error(ArgumentError)
+ lambda { @securable.deny_rights :write, "The Dude" }.should_not raise_error(ArgumentError)
+ lambda { @securable.deny_rights :to_party, "The Dude" }.should raise_error(ArgumentError)
+ end
+
+ it "should accept a principal as a string or an array" do
+ lambda { @securable.rights :read, "The Dude" }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read, ["The Dude","Donny"] }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read, 3 }.should raise_error(ArgumentError)
+ end
+
+ it "should allow you to specify whether the permissions applies_to_children with true/false/:containers_only/:objects_only" do
+ lambda { @securable.rights :read, "The Dude", :applies_to_children => false }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read, "The Dude", :applies_to_children => true }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read, "The Dude", :applies_to_children => :containers_only }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read, "The Dude", :applies_to_children => :objects_only }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read, "The Dude", :applies_to_children => 'poop' }.should raise_error(ArgumentError)
+ end
+
+ it "should allow you to specify whether the permissions applies_to_self with true/false" do
+ lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :applies_to_self => false }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read, "The Dude", :applies_to_self => true }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read, "The Dude", :applies_to_self => 'poop' }.should raise_error(ArgumentError)
+ end
+
+ it "should allow you to specify whether the permissions applies one_level_deep with true/false" do
+ lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => false }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => true }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => 'poop' }.should raise_error(ArgumentError)
+ end
+
+ it "should allow multiple rights and deny_rights declarations" do
+ @securable.rights :read, "The Dude"
+ @securable.deny_rights :full_control, "The Dude"
+ @securable.rights :full_control, "The Dude"
+ @securable.rights :write, "The Dude"
+ @securable.deny_rights :read, "The Dude"
+ @securable.rights.size.should == 3
+ @securable.deny_rights.size.should == 2
+ end
+
+ it "should allow you to specify whether the permission applies_to_self only if you specified applies_to_children" do
+ lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :applies_to_self => true }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :applies_to_self => false }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read, "The Dude", :applies_to_children => false, :applies_to_self => true }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read, "The Dude", :applies_to_children => false, :applies_to_self => false }.should raise_error(ArgumentError)
+ lambda { @securable.rights :read, "The Dude", :applies_to_self => true }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read, "The Dude", :applies_to_self => false }.should_not raise_error(ArgumentError)
+ end
+
+ it "should allow you to specify whether the permission applies one_level_deep only if you specified applies_to_children" do
+ lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => true }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => false }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read, "The Dude", :applies_to_children => false, :one_level_deep => true }.should raise_error(ArgumentError)
+ lambda { @securable.rights :read, "The Dude", :applies_to_children => false, :one_level_deep => false }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read, "The Dude", :one_level_deep => true }.should_not raise_error(ArgumentError)
+ lambda { @securable.rights :read, "The Dude", :one_level_deep => false }.should_not raise_error(ArgumentError)
+ end
+
+ it "should allow you to specify whether the permissions inherit with true/false" do
+ lambda { @securable.inherits true }.should_not raise_error(ArgumentError)
+ lambda { @securable.inherits false }.should_not raise_error(ArgumentError)
+ lambda { @securable.inherits "monkey" }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/unit/mixin/shell_out_spec.rb b/spec/unit/mixin/shell_out_spec.rb
new file mode 100644
index 0000000000..6ca700fcdb
--- /dev/null
+++ b/spec/unit/mixin/shell_out_spec.rb
@@ -0,0 +1,109 @@
+#
+# Author:: Ho-Sheng Hsiao (hosh@opscode.com)
+# Code derived from spec/unit/mixin/command_spec.rb
+#
+# Original header:
+# Author:: Hongli Lai (hongli@phusion.nl)
+# Copyright:: Copyright (c) 2009 Phusion
+# 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::Mixin::ShellOut do
+ include Chef::Mixin::ShellOut
+
+ describe '#run_command_compatible_options' do
+ subject { run_command_compatible_options(command_args) }
+ let(:command_args) { [ cmd, options ] }
+ let(:cmd) { "echo '#{rand(1000)}'" }
+
+ let(:output) { StringIO.new }
+ let(:capture_log_output) { Chef::Log.logger = Logger.new(output) }
+ let(:assume_deprecation_log_level) { Chef::Log.stub!(:level).and_return(:warn) }
+
+ context 'without options' do
+ let(:command_args) { [ cmd ] }
+
+ it 'should not edit command args' do
+ should eql(command_args)
+ end
+ end
+
+ context 'without deprecated options' do
+ let(:options) { { :environment => environment } }
+ let(:environment) { { 'LC_ALL' => 'C' } }
+
+ it 'should not edit command args' do
+ should eql(command_args)
+ end
+ end
+
+ def self.should_emit_deprecation_warning_about(old_option, new_option)
+ it 'should emit a deprecation warning' do
+ assume_deprecation_log_level and capture_log_output
+ subject
+ output.string.should match /DEPRECATION:/
+ output.string.should match Regexp.escape(old_option.to_s)
+ output.string.should match Regexp.escape(new_option.to_s)
+ end
+ end
+
+ context 'with :command_log_level option' do
+ let(:options) { { :command_log_level => command_log_level } }
+ let(:command_log_level) { :warn }
+
+ it 'should convert :command_log_level to :log_level' do
+ should eql [ cmd, { :log_level => command_log_level } ]
+ end
+
+ should_emit_deprecation_warning_about :command_log_level, :log_level
+ end
+
+ context 'with :command_log_prepend option' do
+ let(:options) { { :command_log_prepend => command_log_prepend } }
+ let(:command_log_prepend) { 'PROVIDER:' }
+
+ it 'should convert :command_log_prepend to :log_tag' do
+ should eql [ cmd, { :log_tag => command_log_prepend } ]
+ end
+
+ should_emit_deprecation_warning_about :command_log_prepend, :log_tag
+ end
+
+ context "with 'command_log_level' option" do
+ let(:options) { { 'command_log_level' => command_log_level } }
+ let(:command_log_level) { :warn }
+
+ it "should convert 'command_log_level' to :log_level" do
+ should eql [ cmd, { :log_level => command_log_level } ]
+ end
+
+ should_emit_deprecation_warning_about :command_log_level, :log_level
+ end
+
+ context "with 'command_log_prepend' option" do
+ let(:options) { { 'command_log_prepend' => command_log_prepend } }
+ let(:command_log_prepend) { 'PROVIDER:' }
+
+ it "should convert 'command_log_prepend' to :log_tag" do
+ should eql [ cmd, { :log_tag => command_log_prepend } ]
+ end
+
+ should_emit_deprecation_warning_about :command_log_prepend, :log_tag
+ end
+
+ end
+end
diff --git a/spec/unit/mixin/template_spec.rb b/spec/unit/mixin/template_spec.rb
new file mode 100644
index 0000000000..3d8a723a75
--- /dev/null
+++ b/spec/unit/mixin/template_spec.rb
@@ -0,0 +1,104 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+class TinyTemplateClass; include Chef::Mixin::Template; end
+require 'cgi'
+describe Chef::Mixin::Template, "render_template" do
+
+ before :each do
+ @template = TinyTemplateClass.new
+ end
+
+ it "should render the template evaluated in the given context" do
+ @template.render_template("<%= @foo %>", { :foo => "bar" }) do |tmp|
+ tmp.open.read.should == "bar"
+ end
+ end
+
+ it "should provide a node method to access @node" do
+ @template.render_template("<%= node %>",{:node => "tehShizzle"}) do |tmp|
+ tmp.open.read.should == "tehShizzle"
+ end
+ end
+
+ it "should yield the tempfile it renders the template to" do
+ @template.render_template("abcdef", {}) do |tempfile|
+ tempfile.should be_kind_of(Tempfile)
+ end
+ end
+
+ describe "when an exception is raised in the template" do
+ def do_raise
+ @context = {:chef => "cool"}
+ @template.render_template("foo\nbar\nbaz\n<%= this_is_not_defined %>\nquin\nqunx\ndunno", @context) {|r| r}
+ end
+
+ it "should catch and re-raise the exception as a TemplateError" do
+ lambda { do_raise }.should raise_error(Chef::Mixin::Template::TemplateError)
+ end
+
+ it "should raise an error if an attempt is made to access node but it is nil" do
+ lambda {@template.render_template("<%= node %>",{}) {|r| r}}.should raise_error(Chef::Mixin::Template::TemplateError)
+ end
+
+ describe "the raised TemplateError" do
+ before :each do
+ begin
+ do_raise
+ rescue Chef::Mixin::Template::TemplateError => e
+ @exception = e
+ end
+ end
+
+ it "should have the original exception" do
+ @exception.original_exception.should be
+ @exception.original_exception.message.should =~ /undefined local variable or method `this_is_not_defined'/
+ end
+
+ it "should determine the line number of the exception" do
+ @exception.line_number.should == 4
+ end
+
+ it "should provide a source listing of the template around the exception" do
+ @exception.source_listing.should == " 2: bar\n 3: baz\n 4: <%= this_is_not_defined %>\n 5: quin\n 6: qunx"
+ end
+
+ it "should provide the evaluation context of the template" do
+ @exception.context.should == @context
+ end
+
+ it "should defer the message to the original exception" do
+ @exception.message.should =~ /undefined local variable or method `this_is_not_defined'/
+ end
+
+ it "should provide a nice source location" do
+ @exception.source_location.should == "on line #4"
+ end
+
+ it "should create a pretty output for the terminal" do
+ @exception.to_s.should =~ /Chef::Mixin::Template::TemplateError/
+ @exception.to_s.should =~ /undefined local variable or method `this_is_not_defined'/
+ @exception.to_s.should include(" 2: bar\n 3: baz\n 4: <%= this_is_not_defined %>\n 5: quin\n 6: qunx")
+ @exception.to_s.should include(@exception.original_exception.backtrace.first)
+ end
+ end
+ end
+end
+
diff --git a/spec/unit/mixin/xml_escape_spec.rb b/spec/unit/mixin/xml_escape_spec.rb
new file mode 100644
index 0000000000..d05854ade4
--- /dev/null
+++ b/spec/unit/mixin/xml_escape_spec.rb
@@ -0,0 +1,54 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+class XMLEscapingTestHarness
+ include Chef::Mixin::XMLEscape
+end
+
+describe Chef::Mixin::XMLEscape do
+ before do
+ @escaper = XMLEscapingTestHarness.new
+ end
+
+ it "escapes ampersands to '&amp;'" do
+ @escaper.xml_escape("&").should == "&amp;"
+ end
+
+ it "escapes angle brackets to &lt; or &gt;" do
+ @escaper.xml_escape("<").should == "&lt;"
+ @escaper.xml_escape(">").should == "&gt;"
+ end
+
+ it "does not modify ASCII strings" do
+ @escaper.xml_escape('foobarbaz!@#$%^*()').should == 'foobarbaz!@#$%^*()'
+ end
+
+ it "converts invalid bytes to asterisks" do
+ @escaper.xml_escape("\x00").should == "*"
+ end
+
+ it "converts UTF-8 correctly" do
+ @escaper.xml_escape("\xC2\xA9").should == '&#169;'
+ end
+
+ it "converts win 1252 characters correctly" do
+ @escaper.xml_escape("\x80").should == '&#8364;'
+ end
+end