summaryrefslogtreecommitdiffstats
path: root/spec
diff options
context:
space:
mode:
authorMax Martin <max@puppetlabs.com>2011-03-17 13:42:46 -0700
committerMax Martin <max@puppetlabs.com>2011-03-17 13:42:46 -0700
commit462a56060f0fffc7b8577f252000195e9384fb7a (patch)
treec3447659483c2e286df255053655b00a46af4070 /spec
parent17f673dd6fee08309970f8ff721855cf1644b45f (diff)
parentec1aa192825f17afbe4dc12be1e0f2d644d74155 (diff)
downloadpuppet-462a56060f0fffc7b8577f252000195e9384fb7a.tar.gz
puppet-462a56060f0fffc7b8577f252000195e9384fb7a.tar.xz
puppet-462a56060f0fffc7b8577f252000195e9384fb7a.zip
Merge branch 'feature/2.6.next/4884-an-exec-provider-that-executes-unfiltered-bash-code' into 2.6.next
* feature/2.6.next/4884-an-exec-provider-that-executes-unfiltered-bash-code: (#4884) Revise new exec tests, add a few more (#4884) Add an shell provider for execs (#4884) Fix Test::Unit exec tests (#4884) Break the exec type out to have a posix provider (#4884) Add consistent path validation and behavior (#4884) Add expand_path to requiring the spec_helper (#4884) Autorequire shared behaviors and method to silence warnings (#4884) Fix whitespace (#4884) Get rid of open3 require since it wasn't being used
Diffstat (limited to 'spec')
-rwxr-xr-xspec/integration/transaction_spec.rb24
-rw-r--r--spec/lib/puppet_spec/verbose.rb9
-rw-r--r--spec/shared_behaviours/path_parameters.rb185
-rw-r--r--spec/spec_helper.rb6
-rw-r--r--spec/unit/parameter/path_spec.rb24
-rwxr-xr-xspec/unit/provider/exec/posix_spec.rb120
-rw-r--r--spec/unit/provider/exec/shell_spec.rb50
-rwxr-xr-xspec/unit/type/exec_spec.rb674
-rwxr-xr-xspec/unit/type_spec.rb2
9 files changed, 995 insertions, 99 deletions
diff --git a/spec/integration/transaction_spec.rb b/spec/integration/transaction_spec.rb
index d5478d7a7..2c12b3d5f 100755
--- a/spec/integration/transaction_spec.rb
+++ b/spec/integration/transaction_spec.rb
@@ -107,29 +107,23 @@ describe Puppet::Transaction do
file1 = tmpfile("file1")
file2 = tmpfile("file2")
- file = Puppet::Type.type(:file).new(
-
- :path => path,
-
+ file = Puppet::Type.type(:file).new(
+ :path => path,
:ensure => "file"
)
- exec1 = Puppet::Type.type(:exec).new(
-
- :path => ENV["PATH"],
+ exec1 = Puppet::Type.type(:exec).new(
+ :path => ENV["PATH"],
:command => "touch #{file1}",
:refreshonly => true,
-
- :subscribe => Puppet::Resource.new(:file, path)
+ :subscribe => Puppet::Resource.new(:file, path)
)
- exec2 = Puppet::Type.type(:exec).new(
-
- :path => ENV["PATH"],
- :command => "touch #{file2}",
+ exec2 = Puppet::Type.type(:exec).new(
+ :path => ENV["PATH"],
+ :command => "touch #{file2}",
:refreshonly => true,
-
- :subscribe => Puppet::Resource.new(:file, path)
+ :subscribe => Puppet::Resource.new(:file, path)
)
catalog = mk_catalog(file, exec1, exec2)
diff --git a/spec/lib/puppet_spec/verbose.rb b/spec/lib/puppet_spec/verbose.rb
new file mode 100644
index 000000000..d9834f2d7
--- /dev/null
+++ b/spec/lib/puppet_spec/verbose.rb
@@ -0,0 +1,9 @@
+# Support code for running stuff with warnings disabled.
+module Kernel
+ def with_verbose_disabled
+ verbose, $VERBOSE = $VERBOSE, nil
+ result = yield
+ $VERBOSE = verbose
+ return result
+ end
+end
diff --git a/spec/shared_behaviours/path_parameters.rb b/spec/shared_behaviours/path_parameters.rb
new file mode 100644
index 000000000..b5a907900
--- /dev/null
+++ b/spec/shared_behaviours/path_parameters.rb
@@ -0,0 +1,185 @@
+# In order to use this correctly you must define a method to get an instance
+# of the type being tested, so that this code can remain generic:
+#
+# it_should_behave_like "all path parameters", :path do
+# def instance(path)
+# Puppet::Type.type(:example).new(
+# :name => 'foo', :require => 'bar', :path_param => path
+# )
+# end
+#
+# That method will be invoked for each test to create the instance that we
+# subsequently test through the system; you should ensure that the minimum of
+# possible attributes are set to keep the tests clean.
+#
+# You must also pass the symbolic name of the parameter being tested to the
+# block, and optionally can pass a hash of additional options to the block.
+#
+# The known options are:
+# :array :: boolean, does this support arrays of paths, default true.
+
+shared_examples_for "all pathname parameters with arrays" do |win32|
+ path_types = {
+ "unix absolute" => "/foo/bar",
+ "unix relative" => "foo/bar",
+ "win32 absolute" => %q{\foo\bar},
+ "win32 relative" => %q{foo\bar},
+ "drive absolute" => %q{c:\foo\bar},
+ "drive relative" => %q{c:foo\bar}
+ }
+
+ describe "when given an array of paths" do
+ (1..path_types.length).each do |n|
+ path_types.keys.combination(n) do |set|
+ data = path_types.collect { |k, v| set.member?(k) ? v : nil } .compact
+ reject = true
+ only_absolute = set.find { |k| k =~ /relative/ } .nil?
+ only_unix = set.reject { |k| k =~ /unix/ } .length == 0
+
+ if only_absolute and (only_unix or win32) then
+ reject = false
+ end
+
+ it "should #{reject ? 'reject' : 'accept'} #{set.join(", ")}" do
+ if reject then
+ expect { instance(data) }.
+ should raise_error Puppet::Error, /fully qualified/
+ else
+ instance = instance(data)
+ instance[@param].should == data
+ end
+ end
+
+ it "should #{reject ? 'reject' : 'accept'} #{set.join(", ")} doubled" do
+ if reject then
+ expect { instance(data + data) }.
+ should raise_error Puppet::Error, /fully qualified/
+ else
+ instance = instance(data + data)
+ instance[@param].should == (data + data)
+ end
+ end
+ end
+ end
+ end
+end
+
+
+shared_examples_for "all path parameters" do |param, options|
+ # Extract and process options to the block.
+ options ||= {}
+ array = options[:array].nil? ? true : options.delete(:array)
+ if options.keys.length > 0 then
+ fail "unknown options for 'all path parameters': " +
+ options.keys.sort.join(', ')
+ end
+
+ def instance(path)
+ fail "we didn't implement the 'instance(path)' method in the it_should_behave_like block"
+ end
+
+ ########################################################################
+ # The actual testing code...
+ before :all do
+ @param = param
+ end
+
+ before :each do
+ @file_separator = File::SEPARATOR
+ end
+ after :each do
+ with_verbose_disabled do
+ verbose, $VERBOSE = $VERBOSE, nil
+ File::SEPARATOR = @file_separator
+ $VERBOSE = verbose
+ end
+ end
+
+ describe "on a Unix-like platform it" do
+ before :each do
+ with_verbose_disabled do
+ File::SEPARATOR = '/'
+ end
+ Puppet.features.stubs(:microsoft_windows?).returns(false)
+ Puppet.features.stubs(:posix?).returns(true)
+ end
+
+ if array then
+ it_should_behave_like "all pathname parameters with arrays", false
+ end
+
+ it "should accept a fully qualified path" do
+ path = File.join('', 'foo')
+ instance = instance(path)
+ instance[@param].should == path
+ end
+
+ it "should give a useful error when the path is not absolute" do
+ path = 'foo'
+ expect { instance(path) }.
+ should raise_error Puppet::Error, /fully qualified/
+ end
+
+ { "Unix" => '/', "Win32" => '\\' }.each do |style, slash|
+ %w{q Q a A z Z c C}.sort.each do |drive|
+ it "should reject drive letter '#{drive}' with #{style} path separators" do
+ path = "#{drive}:#{slash}Program Files"
+ expect { instance(path) }.
+ should raise_error Puppet::Error, /fully qualified/
+ end
+ end
+ end
+ end
+
+ describe "on a Windows-like platform it" do
+ before :each do
+ with_verbose_disabled do
+ File::SEPARATOR = '\\'
+ end
+ Puppet.features.stubs(:microsoft_windows?).returns(true)
+ Puppet.features.stubs(:posix?).returns(false)
+ end
+
+ if array then
+ it_should_behave_like "all pathname parameters with arrays", true
+ end
+
+ it "should accept a fully qualified path" do
+ path = File.join('', 'foo')
+ instance = instance(path)
+ instance[@param].should == path
+ end
+
+ it "should give a useful error when the path is not absolute" do
+ path = 'foo'
+ expect { instance(path) }.
+ should raise_error Puppet::Error, /fully qualified/
+ end
+
+ it "also accepts Unix style path separators" do
+ path = '/Program Files'
+ instance = instance(path)
+ instance[@param].should == path
+ end
+
+ { "Unix" => '/', "Win32" => '\\' }.each do |style, slash|
+ %w{q Q a A z Z c C}.sort.each do |drive|
+ it "should accept drive letter '#{drive}' with #{style} path separators " do
+ path = "#{drive}:#{slash}Program Files"
+ instance = instance(path)
+ instance[@param].should == path
+ end
+ end
+ end
+
+ { "UNC paths" => %q{\\foo\bar},
+ "unparsed local paths" => %q{\\?\c:\foo},
+ "unparsed UNC paths" => %q{\\?\foo\bar}
+ }.each do |name, path|
+ it "should accept #{name} as absolute" do
+ instance = instance(path)
+ instance[@param].should == path
+ end
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index ae4edb2d9..505a8f973 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -23,10 +23,16 @@ end
module PuppetTest
end
+require 'pathname'
+require 'lib/puppet_spec/verbose'
require 'lib/puppet_spec/files'
require 'monkey_patches/alias_should_to_must'
require 'monkey_patches/publicize_methods'
+Pathname.glob("#{dir}/shared_behaviours/**/*.rb") do |behaviour|
+ require behaviour.relative_path_from(Pathname.new(dir))
+end
+
RSpec.configure do |config|
config.mock_with :mocha
diff --git a/spec/unit/parameter/path_spec.rb b/spec/unit/parameter/path_spec.rb
new file mode 100644
index 000000000..08a26de33
--- /dev/null
+++ b/spec/unit/parameter/path_spec.rb
@@ -0,0 +1,24 @@
+#!/usr/bin/env ruby
+require File.expand_path(File.join(File.dirname(__FILE__), '../../spec_helper'))
+
+require 'puppet/parameter/path'
+
+[false, true].each do |arrays|
+ describe "Puppet::Parameter::Path with arrays #{arrays}" do
+ it_should_behave_like "all path parameters", :path, :array => arrays do
+ # The new type allows us a test that is guaranteed to go direct to our
+ # validation code, without passing through any "real type" overrides or
+ # whatever on the way.
+ Puppet::newtype(:test_puppet_parameter_path) do
+ newparam(:path, :parent => Puppet::Parameter::Path, :arrays => arrays) do
+ isnamevar
+ accept_arrays arrays
+ end
+ end
+
+ def instance(path)
+ Puppet::Type.type(:test_puppet_parameter_path).new(:path => path)
+ end
+ end
+ end
+end
diff --git a/spec/unit/provider/exec/posix_spec.rb b/spec/unit/provider/exec/posix_spec.rb
new file mode 100755
index 000000000..d02099250
--- /dev/null
+++ b/spec/unit/provider/exec/posix_spec.rb
@@ -0,0 +1,120 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+provider_class = Puppet::Type.type(:exec).provider(:posix)
+
+describe provider_class do
+ before :each do
+ @resource = Puppet::Resource.new(:exec, 'foo')
+ @provider = provider_class.new(@resource)
+ end
+
+ ["posix", "microsoft_windows"].each do |feature|
+ describe "when in #{feature} environment" do
+ before :each do
+ if feature == "microsoft_windows"
+ Puppet.features.stubs(:microsoft_windows?).returns(true)
+ Puppet.features.stubs(:posix?).returns(false)
+ else
+ Puppet.features.stubs(:posix?).returns(true)
+ Puppet.features.stubs(:microsoft_windows?).returns(false)
+ end
+ end
+
+ describe "#validatecmd" do
+ it "should fail if no path is specified and the command is not fully qualified" do
+ lambda { @provider.validatecmd("foo") }.should raise_error(
+ Puppet::Error,
+ "'foo' is not qualified and no path was specified. Please qualify the command or specify a path."
+ )
+ end
+
+ it "should pass if a path is given" do
+ @provider.resource[:path] = ['/bogus/bin']
+ @provider.validatecmd("../foo")
+ end
+
+ it "should pass if command is fully qualifed" do
+ @provider.resource[:path] = ['/bogus/bin']
+ @provider.validatecmd("/bin/blah/foo")
+ end
+ end
+
+ describe "#run" do
+ it "should fail if no path is specified and command does not exist" do
+ lambda { @provider.run("foo") }.should raise_error(ArgumentError, "Could not find command 'foo'")
+ end
+
+ it "should fail if the command isn't in the path" do
+ @provider.resource[:path] = ['/bogus/bin']
+ lambda { @provider.run("foo") }.should raise_error(ArgumentError, "Could not find command 'foo'")
+ end
+
+ it "should fail if the command isn't executable" do
+ @provider.resource[:path] = ['/bogus/bin']
+ File.stubs(:exists?).with("foo").returns(true)
+
+ lambda { @provider.run("foo") }.should raise_error(ArgumentError, "'foo' is not executable")
+ end
+
+ it "should not be able to execute shell builtins" do
+ @provider.resource[:path] = ['/bin']
+ lambda { @provider.run("cd ..") }.should raise_error(ArgumentError, "Could not find command 'cd'")
+ end
+
+ it "should execute the command if the command given includes arguments or subcommands" do
+ @provider.resource[:path] = ['/bogus/bin']
+ File.stubs(:exists?).returns(false)
+ File.stubs(:exists?).with("foo").returns(true)
+ File.stubs(:executable?).with("foo").returns(true)
+
+ Puppet::Util.expects(:execute).with() { |command, arguments| (command == ['foo bar --sillyarg=true --blah']) && (arguments.is_a? Hash) }
+ @provider.run("foo bar --sillyarg=true --blah")
+ end
+
+ it "should fail if quoted command doesn't exist" do
+ @provider.resource[:path] = ['/bogus/bin']
+ File.stubs(:exists?).returns(false)
+ File.stubs(:exists?).with("foo").returns(true)
+ File.stubs(:executable?).with("foo").returns(true)
+
+ lambda { @provider.run('"foo bar --sillyarg=true --blah"') }.should raise_error(ArgumentError, "Could not find command 'foo bar --sillyarg=true --blah'")
+ end
+
+ it "should execute the command if it finds it in the path and is executable" do
+ @provider.resource[:path] = ['/bogus/bin']
+ File.stubs(:exists?).with("foo").returns(true)
+ File.stubs(:executable?).with("foo").returns(true)
+ Puppet::Util.expects(:execute).with() { |command, arguments| (command == ['foo']) && (arguments.is_a? Hash) }
+
+ @provider.run("foo")
+ end
+
+ if feature == "microsoft_windows"
+ [".exe", ".ps1", ".bat", ".com", ""].each do |extension|
+ it "should check file extension #{extension} when it can't find the executable" do
+ @provider.resource[:path] = ['/bogus/bin']
+ File.stubs(:exists?).returns(false)
+ File.stubs(:exists?).with("/bogus/bin/foo#{extension}").returns(true)
+ File.stubs(:executable?).with("foo").returns(true)
+ Puppet::Util.expects(:execute).with() { |command, arguments| (command == ['foo']) && (arguments.is_a? Hash) }
+
+ @provider.run("foo")
+ end
+ end
+ end
+
+ it "should warn if you're overriding something in environment" do
+ @provider.resource[:environment] = ['WHATEVER=/something/else', 'WHATEVER=/foo']
+ File.stubs(:exists?).returns(false)
+ File.stubs(:exists?).with("foo").returns(true)
+ File.stubs(:executable?).with("foo").returns(true)
+
+ Puppet::Util.expects(:execute).with() { |command, arguments| (command == ['foo']) && (arguments.is_a? Hash) }
+ @provider.run("foo")
+ @logs.map {|l| "#{l.level}: #{l.message}" }.should == ["warning: Overriding environment setting 'WHATEVER' with '/foo'"]
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/provider/exec/shell_spec.rb b/spec/unit/provider/exec/shell_spec.rb
new file mode 100644
index 000000000..a9b1f06ba
--- /dev/null
+++ b/spec/unit/provider/exec/shell_spec.rb
@@ -0,0 +1,50 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+provider_class = Puppet::Type.type(:exec).provider(:shell)
+
+describe provider_class do
+ before :each do
+ @resource = Puppet::Resource.new(:exec, 'foo')
+ @provider = provider_class.new(@resource)
+ end
+
+ describe "#run" do
+ it "should be able to run builtin shell commands" do
+ output, status = @provider.run("if [ 1 == 1 ]; then echo 'blah'; fi")
+ status.exitstatus.should == 0
+ output.should == "blah\n"
+ end
+
+ it "should be able to run commands with single quotes in them" do
+ output, status = @provider.run("echo 'foo bar'")
+ status.exitstatus.should == 0
+ output.should == "foo bar\n"
+ end
+
+ it "should be able to run commands with double quotes in them" do
+ output, status = @provider.run('echo "foo bar"')
+ status.exitstatus.should == 0
+ output.should == "foo bar\n"
+ end
+
+ it "should be able to run multiple commands separated by a semicolon" do
+ output, status = @provider.run("echo 'foo' ; echo 'bar'")
+ status.exitstatus.should == 0
+ output.should == "foo\nbar\n"
+ end
+
+ it "should be able to read values from the environment parameter" do
+ @resource[:environment] = "FOO=bar"
+ output, status = @provider.run("echo $FOO")
+ status.exitstatus.should == 0
+ output.should == "bar\n"
+ end
+ end
+
+ describe "#validatecmd" do
+ it "should always return true because builtins don't need path or to be fully qualified" do
+ @provider.validatecmd('whateverdoesntmatter').should == true
+ end
+ end
+end
diff --git a/spec/unit/type/exec_spec.rb b/spec/unit/type/exec_spec.rb
index e04cfc065..4d691d580 100755
--- a/spec/unit/type/exec_spec.rb
+++ b/spec/unit/type/exec_spec.rb
@@ -1,31 +1,32 @@
#!/usr/bin/env ruby
-
require File.dirname(__FILE__) + '/../../spec_helper'
describe Puppet::Type.type(:exec) do
-
- def create_resource(command, output, exitstatus, returns = 0)
- @user_name = 'some_user_name'
+ def exec_tester(command, exitstatus = 0, rest = {})
+ @user_name = 'some_user_name'
@group_name = 'some_group_name'
Puppet.features.stubs(:root?).returns(true)
- @execer = Puppet::Type.type(:exec).new(:name => command, :path => @example_path, :user => @user_name, :group => @group_name, :returns => returns)
- status = stub "process"
- status.stubs(:exitstatus).returns(exitstatus)
+ output = rest.delete(:output) || ''
+ tries = rest[:tries] || 1
- Puppet::Util::SUIDManager.expects(:run_and_capture).with([command], @user_name, @group_name).returns([output, status])
- end
+ args = {
+ :name => command,
+ :path => @example_path,
+ :user => @user_name,
+ :group => @group_name,
+ :logoutput => false,
+ :loglevel => :err,
+ :returns => 0
+ }.merge(rest)
- def create_logging_resource(command, output, exitstatus, logoutput, loglevel, returns = 0)
- create_resource(command, output, exitstatus, returns)
- @execer[:logoutput] = logoutput
- @execer[:loglevel] = loglevel
- end
+ exec = Puppet::Type.type(:exec).new(args)
- def expect_output(output, loglevel)
- output.split(/\n/).each do |line|
- @execer.property(:returns).expects(loglevel).with(line)
- end
+ status = stub "process", :exitstatus => exitstatus
+ Puppet::Util::SUIDManager.expects(:run_and_capture).times(tries).
+ with([command], @user_name, @group_name).returns([output, status])
+
+ return exec
end
before do
@@ -44,119 +45,626 @@ describe Puppet::Type.type(:exec) do
end
describe "when execing" do
-
it "should use the 'run_and_capture' method to exec" do
- command = "true"
- create_resource(command, "", 0)
-
- @execer.refresh.should == :executed_command
+ exec_tester("true").refresh.should == :executed_command
end
it "should report a failure" do
- command = "false"
- create_resource(command, "", 1)
-
- proc { @execer.refresh }.should raise_error(Puppet::Error)
+ proc { exec_tester('false', 1).refresh }.
+ should raise_error(Puppet::Error, /^false returned 1 instead of/)
end
it "should not report a failure if the exit status is specified in a returns array" do
- command = "false"
- create_resource(command, "", 1, [0,1])
- proc { @execer.refresh }.should_not raise_error(Puppet::Error)
+ proc { exec_tester("false", 1, :returns => [0, 1]).refresh }.should_not raise_error
end
it "should report a failure if the exit status is not specified in a returns array" do
- command = "false"
- create_resource(command, "", 1, [0,100])
- proc { @execer.refresh }.should raise_error(Puppet::Error)
+ proc { exec_tester('false', 1, :returns => [0, 100]).refresh }.
+ should raise_error(Puppet::Error, /^false returned 1 instead of/)
end
it "should log the output on success" do
- #Puppet::Util::Log.newdestination :console
- command = "false"
output = "output1\noutput2\n"
- create_logging_resource(command, output, 0, true, :err)
- expect_output(output, :err)
- @execer.refresh
+ exec_tester('false', 0, :output => output, :logoutput => true).refresh
+ output.split("\n").each do |line|
+ log = @logs.shift
+ log.level.should == :err
+ log.message.should == line
+ end
end
it "should log the output on failure" do
- #Puppet::Util::Log.newdestination :console
- command = "false"
output = "output1\noutput2\n"
- create_logging_resource(command, output, 1, true, :err)
- expect_output(output, :err)
+ proc { exec_tester('false', 1, :output => output, :logoutput => true).refresh }.
+ should raise_error(Puppet::Error)
- proc { @execer.refresh }.should raise_error(Puppet::Error)
+ output.split("\n").each do |line|
+ log = @logs.shift
+ log.level.should == :err
+ log.message.should == line
+ end
end
-
end
describe "when logoutput=>on_failure is set" do
-
it "should log the output on failure" do
- #Puppet::Util::Log.newdestination :console
- command = "false"
output = "output1\noutput2\n"
- create_logging_resource(command, output, 1, :on_failure, :err)
- expect_output(output, :err)
+ proc { exec_tester('false', 1, :output => output, :logoutput => :on_failure).refresh }.
+ should raise_error(Puppet::Error, /^false returned 1 instead of/)
- proc { @execer.refresh }.should raise_error(Puppet::Error)
+ output.split("\n").each do |line|
+ log = @logs.shift
+ log.level.should == :err
+ log.message.should == line
+ end
end
it "should log the output on failure when returns is specified as an array" do
- #Puppet::Util::Log.newdestination :console
- command = "false"
output = "output1\noutput2\n"
- create_logging_resource(command, output, 1, :on_failure, :err, [0, 100])
- expect_output(output, :err)
- proc { @execer.refresh }.should raise_error(Puppet::Error)
+ proc {
+ exec_tester('false', 1, :output => output, :returns => [0, 100],
+ :logoutput => :on_failure).refresh
+ }.should raise_error(Puppet::Error, /^false returned 1 instead of/)
+
+ output.split("\n").each do |line|
+ log = @logs.shift
+ log.level.should == :err
+ log.message.should == line
+ end
end
it "shouldn't log the output on success" do
- #Puppet::Util::Log.newdestination :console
- command = "true"
- output = "output1\noutput2\n"
- create_logging_resource(command, output, 0, :on_failure, :err)
- @execer.property(:returns).expects(:err).never
- @execer.refresh
+ exec_tester('true', 0, :output => "a\nb\nc\n", :logoutput => :on_failure).refresh
+ @logs.should == []
end
end
it "shouldn't log the output on success when non-zero exit status is in a returns array" do
- #Puppet::Util::Log.newdestination :console
- command = "true"
- output = "output1\noutput2\n"
- create_logging_resource(command, output, 100, :on_failure, :err, [1,100])
- @execer.property(:returns).expects(:err).never
- @execer.refresh
+ exec_tester("true", 100, :output => "a\n", :logoutput => :on_failure, :returns => [1, 100]).refresh
+ @logs.should == []
end
describe " when multiple tries are set," do
-
it "should repeat the command attempt 'tries' times on failure and produce an error" do
- Puppet.features.stubs(:root?).returns(true)
- command = "false"
- user = "user"
- group = "group"
tries = 5
- retry_exec = Puppet::Type.type(:exec).new(:name => command, :path => %w{/usr/bin /bin}, :user => user, :group => group, :returns => 0, :tries => tries, :try_sleep => 0)
- status = stub "process"
- status.stubs(:exitstatus).returns(1)
- Puppet::Util::SUIDManager.expects(:run_and_capture).with([command], user, group).times(tries).returns(["", status])
- proc { retry_exec.refresh }.should raise_error(Puppet::Error)
+ resource = exec_tester("false", 1, :tries => tries, :try_sleep => 0)
+ proc { resource.refresh }.should raise_error(Puppet::Error)
end
end
it "should be able to autorequire files mentioned in the command" do
catalog = Puppet::Resource::Catalog.new
- catalog.add_resource Puppet::Type.type(:file).new(:name => @executable)
- @execer = Puppet::Type.type(:exec).new(:name => @command)
- catalog.add_resource @execer
+ tmp = Puppet::Type.type(:file).new(:name => "/bin/foo")
+ catalog.add_resource tmp
+ execer = Puppet::Type.type(:exec).new(:name => "/bin/foo")
+ catalog.add_resource execer
+
+ catalog.relationship_graph.dependencies(execer).should == [tmp]
+ end
+
+ describe "when handling the path parameter" do
+ expect = %w{one two three four}
+ { "an array" => expect,
+ "a colon separated list" => "one:two:three:four",
+ "a semi-colon separated list" => "one;two;three;four",
+ "both array and colon lists" => ["one", "two:three", "four"],
+ "both array and semi-colon lists" => ["one", "two;three", "four"],
+ "colon and semi-colon lists" => ["one:two", "three;four"]
+ }.each do |test, input|
+ it "should accept #{test}" do
+ type = Puppet::Type.type(:exec).new(:name => @command, :path => input)
+ type[:path].should == expect
+ end
+ end
+ end
+
+ describe "when setting user" do
+ it "should fail if we are not root" do
+ Puppet.features.stubs(:root?).returns(false)
+ expect { Puppet::Type.type(:exec).new(:name => @command, :user => 'input') }.
+ should raise_error Puppet::Error, /Parameter user failed/
+ end
+
+ ['one', 2, 'root', 4294967295, 4294967296].each do |value|
+ it "should accept '#{value}' as user if we are root" do
+ Puppet.features.stubs(:root?).returns(true)
+ type = Puppet::Type.type(:exec).new(:name => @command, :user => value)
+ type[:user].should == value
+ end
+ end
+ end
+
+ describe "when setting group" do
+ shared_examples_for "exec[:group]" do
+ ['one', 2, 'wheel', 4294967295, 4294967296].each do |value|
+ it "should accept '#{value}' without error or judgement" do
+ type = Puppet::Type.type(:exec).new(:name => @command, :group => value)
+ type[:group].should == value
+ end
+ end
+ end
+
+ describe "when running as root" do
+ before :each do Puppet.features.stubs(:root?).returns(true) end
+ it_behaves_like "exec[:group]"
+ end
+
+ describe "when not running as root" do
+ before :each do Puppet.features.stubs(:root?).returns(false) end
+ it_behaves_like "exec[:group]"
+ end
+ end
+
+ describe "when setting cwd" do
+ it_should_behave_like "all path parameters", :cwd, :array => false do
+ def instance(path)
+ Puppet::Type.type(:exec).new(:name => '/bin/true', :cwd => path)
+ end
+ end
+ end
+
+ shared_examples_for "all exec command parameters" do |param|
+ { "relative" => "example", "absolute" => "/bin/example" }.sort.each do |name, command|
+ describe "if command is #{name}" do
+ before :each do
+ @param = param
+ end
+
+ def test(command, valid)
+ if @param == :name then
+ instance = Puppet::Type.type(:exec).new()
+ else
+ instance = Puppet::Type.type(:exec).new(:name => "/bin/true")
+ end
+ if valid then
+ instance.provider.expects(:validatecmd).returns(true)
+ else
+ instance.provider.expects(:validatecmd).raises(Puppet::Error, "from a stub")
+ end
+ instance[@param] = command
+ end
+
+ it "should work if the provider calls the command valid" do
+ expect { test(command, true) }.should_not raise_error
+ end
+
+ it "should fail if the provider calls the command invalid" do
+ expect { test(command, false) }.
+ should raise_error Puppet::Error, /Parameter #{@param} failed: from a stub/
+ end
+ end
+ end
+ end
+
+ shared_examples_for "all exec command parameters that take arrays" do |param|
+ describe "when given an array of inputs" do
+ before :each do
+ @test = Puppet::Type.type(:exec).new(:name => "/bin/true")
+ end
+
+ it "should accept the array when all commands return valid" do
+ input = %w{one two three}
+ @test.provider.expects(:validatecmd).times(input.length).returns(true)
+ @test[param] = input
+ @test[param].should == input
+ end
+
+ it "should reject the array when any commands return invalid" do
+ input = %w{one two three}
+ @test.provider.expects(:validatecmd).with(input.first).returns(false)
+ input[1..-1].each do |cmd|
+ @test.provider.expects(:validatecmd).with(cmd).returns(true)
+ end
+ @test[param] = input
+ @test[param].should == input
+ end
+
+ it "should reject the array when all commands return invalid" do
+ input = %w{one two three}
+ @test.provider.expects(:validatecmd).times(input.length).returns(false)
+ @test[param] = input
+ @test[param].should == input
+ end
+ end
+ end
+
+ describe "when setting refresh" do
+ it_should_behave_like "all exec command parameters", :refresh
+ end
+
+ describe "for simple parameters" do
+ before :each do
+ @exec = Puppet::Type.type(:exec).new(:name => '/bin/true')
+ end
+
+ describe "when setting env" do
+ it "should issue a deprecation warning" do
+ expect { @exec[:env] = 'foo=bar' }.should_not raise_error
+ @logs.first.message.should =~ /deprecate.*environment/
+ end
+
+ it "should update the value of env" do
+ data = ['foo=bar']
+ @exec[:env] = data
+ @exec[:env].should == data
+ end
+
+ it "should forward to environment" do
+ data = ['foo=bar']
+ @exec[:env] = data
+ @exec[:environment].should == data
+ end
+
+ it "should not override environment if both are set" do
+ pending "can't fix: too disruptive for 2.6, removed in 2.7"
+ # ...so this test is here to validate that we know about the problem.
+ # This ensures correct order of evaluation to trigger the bug; don't
+ # count on this happening in the constructor. --daniel 2011-03-01
+ @exec[:environment] = 'environment=true'
+ @exec[:env] = 'env=true'
+
+ @exec[:environment].should == "environment=true"
+ end
+ end
+
+ describe "when setting environment" do
+ { "single values" => "foo=bar",
+ "multiple values" => ["foo=bar", "baz=quux"],
+ }.each do |name, data|
+ it "should accept #{name}" do
+ @exec[:environment] = data
+ @exec[:environment].should == data
+ end
+ end
+
+ { "single values" => "foo",
+ "only values" => ["foo", "bar"],
+ "any values" => ["foo=bar", "baz"]
+ }.each do |name, data|
+ it "should reject #{name} without assignment" do
+ expect { @exec[:environment] = data }.
+ should raise_error Puppet::Error, /Invalid environment setting/
+ end
+ end
+ end
+
+ describe "when setting timeout" do
+ [-3.5, -1, 0, 0.1, 1, 10, 4294967295].each do |valid|
+ it "should accept '#{valid}' as valid" do
+ @exec[:timeout] = valid
+ @exec[:timeout].should == valid
+ end
+
+ it "should accept '#{valid}' in an array as valid" do
+ @exec[:timeout] = [valid]
+ @exec[:timeout].should == valid
+ end
+ end
+
+ ['1/2', '1_000_000', '+12', '', 'foo'].each do |invalid|
+ it "should reject '#{invalid}' as invalid" do
+ expect { @exec[:timeout] = invalid }.
+ should raise_error Puppet::Error, /The timeout must be a number/
+ end
+
+ it "should reject '#{invalid}' in an array as invalid" do
+ expect { @exec[:timeout] = [invalid] }.
+ should raise_error Puppet::Error, /The timeout must be a number/
+ end
+ end
+
+ it "should fail if timeout is exceeded" do
+ File.stubs(:exists?).with('/bin/sleep').returns(true)
+ sleep_exec = Puppet::Type.type(:exec).new(:name => 'sleep 1', :path => ['/bin'], :timeout => '0.2')
+ lambda { sleep_exec.refresh }.should raise_error Puppet::Error, "Command exceeded timeout"
+ end
+ end
+
+ describe "when setting tries" do
+ [1, 10, 4294967295].each do |valid|
+ it "should accept '#{valid}' as valid" do
+ @exec[:tries] = valid
+ @exec[:tries].should == valid
+ end
+
+ if "REVISIT: too much test log spam" == "a good thing" then
+ it "should accept '#{valid}' in an array as valid" do
+ pending "inconsistent, but this is not supporting arrays, unlike timeout"
+ @exec[:tries] = [valid]
+ @exec[:tries].should == valid
+ end
+ end
+ end
+
+ [-3.5, -1, 0, 0.2, '1/2', '1_000_000', '+12', '', 'foo'].each do |invalid|
+ it "should reject '#{invalid}' as invalid" do
+ expect { @exec[:tries] = invalid }.
+ should raise_error Puppet::Error, /Tries must be an integer/
+ end
- rels = @execer.autorequire
- rels[0].should be_instance_of(Puppet::Relationship)
- rels[0].target.should equal(@execer)
+ if "REVISIT: too much test log spam" == "a good thing" then
+ it "should reject '#{invalid}' in an array as invalid" do
+ pending "inconsistent, but this is not supporting arrays, unlike timeout"
+ expect { @exec[:tries] = [invalid] }.
+ should raise_error Puppet::Error, /Tries must be an integer/
+ end
+ end
+ end
+ end
+
+ describe "when setting try_sleep" do
+ [0, 0.2, 1, 10, 4294967295].each do |valid|
+ it "should accept '#{valid}' as valid" do
+ @exec[:try_sleep] = valid
+ @exec[:try_sleep].should == valid
+ end
+
+ if "REVISIT: too much test log spam" == "a good thing" then
+ it "should accept '#{valid}' in an array as valid" do
+ pending "inconsistent, but this is not supporting arrays, unlike timeout"
+ @exec[:try_sleep] = [valid]
+ @exec[:try_sleep].should == valid
+ end
+ end
+ end
+
+ { -3.5 => "cannot be a negative number",
+ -1 => "cannot be a negative number",
+ '1/2' => 'must be a number',
+ '1_000_000' => 'must be a number',
+ '+12' => 'must be a number',
+ '' => 'must be a number',
+ 'foo' => 'must be a number',
+ }.each do |invalid, error|
+ it "should reject '#{invalid}' as invalid" do
+ expect { @exec[:try_sleep] = invalid }.
+ should raise_error Puppet::Error, /try_sleep #{error}/
+ end
+
+ if "REVISIT: too much test log spam" == "a good thing" then
+ it "should reject '#{invalid}' in an array as invalid" do
+ pending "inconsistent, but this is not supporting arrays, unlike timeout"
+ expect { @exec[:try_sleep] = [invalid] }.
+ should raise_error Puppet::Error, /try_sleep #{error}/
+ end
+ end
+ end
+ end
+
+ describe "when setting refreshonly" do
+ [:true, :false].each do |value|
+ it "should accept '#{value}'" do
+ @exec[:refreshonly] = value
+ @exec[:refreshonly].should == value
+ end
+ end
+
+ [1, 0, "1", "0", "yes", "y", "no", "n"].each do |value|
+ it "should reject '#{value}'" do
+ expect { @exec[:refreshonly] = value }.
+ should raise_error(Puppet::Error,
+ /Invalid value #{value.inspect}\. Valid values are true, false/
+ )
+ end
+ end
+ end
+
+ describe "when setting creates" do
+ it_should_behave_like "all path parameters", :creates, :array => true do
+ def instance(path)
+ Puppet::Type.type(:exec).new(:name => '/bin/true', :creates => path)
+ end
+ end
+ end
+ end
+
+ describe "when setting unless" do
+ it_should_behave_like "all exec command parameters", :unless
+ it_should_behave_like "all exec command parameters that take arrays", :unless
+ end
+
+ describe "when setting onlyif" do
+ it_should_behave_like "all exec command parameters", :onlyif
+ it_should_behave_like "all exec command parameters that take arrays", :onlyif
+ end
+
+ describe "#check" do
+ before :each do
+ @test = Puppet::Type.type(:exec).new(:name => "/bin/true")
+ end
+
+ describe ":refreshonly" do
+ { :true => false, :false => true }.each do |input, result|
+ it "should return '#{result}' when given '#{input}'" do
+ @test[:refreshonly] = input
+ @test.check_all_attributes.should == result
+ end
+ end
+ end
+
+ describe ":creates" do
+ before :all do
+ @exist = "/"
+ @unexist = "/this/path/should/never/exist"
+ while FileTest.exist?(@unexist) do @unexist += "/foo" end
+ end
+
+ context "with a single item" do
+ it "should run when the item does not exist" do
+ @test[:creates] = @unexist
+ @test.check_all_attributes.should == true
+ end
+
+ it "should not run when the item exists" do
+ @test[:creates] = @exist
+ @test.check_all_attributes.should == false
+ end
+ end
+
+ context "with an array with one item" do
+ it "should run when the item does not exist" do
+ @test[:creates] = [@unexist]
+ @test.check_all_attributes.should == true
+ end
+
+ it "should not run when the item exists" do
+ @test[:creates] = [@exist]
+ @test.check_all_attributes.should == false
+ end
+ end
+
+ context "with an array with multiple items" do
+ it "should run when all items do not exist" do
+ @test[:creates] = [@unexist] * 3
+ @test.check_all_attributes.should == true
+ end
+
+ it "should not run when one item exists" do
+ @test[:creates] = [@unexist, @exist, @unexist]
+ @test.check_all_attributes.should == false
+ end
+
+ it "should not run when all items exist" do
+ @test[:creates] = [@exist] * 3
+ end
+ end
+ end
+
+ { :onlyif => { :pass => false, :fail => true },
+ :unless => { :pass => true, :fail => false },
+ }.each do |param, sense|
+ describe ":#{param}" do
+ before :each do
+ @pass = "/magic/pass"
+ @fail = "/magic/fail"
+
+ @pass_status = stub('status', :exitstatus => sense[:pass] ? 0 : 1)
+ @fail_status = stub('status', :exitstatus => sense[:fail] ? 0 : 1)
+
+ @test.provider.stubs(:checkexe).returns(true)
+ [true, false].each do |check|
+ @test.provider.stubs(:run).with(@pass, check).
+ returns(['test output', @pass_status])
+ @test.provider.stubs(:run).with(@fail, check).
+ returns(['test output', @fail_status])
+ end
+ end
+
+ context "with a single item" do
+ it "should run if the command exits non-zero" do
+ @test[param] = @fail
+ @test.check_all_attributes.should == true
+ end
+
+ it "should not run if the command exits zero" do
+ @test[param] = @pass
+ @test.check_all_attributes.should == false
+ end
+ end
+
+ context "with an array with a single item" do
+ it "should run if the command exits non-zero" do
+ @test[param] = [@fail]
+ @test.check_all_attributes.should == true
+ end
+
+ it "should not run if the command exits zero" do
+ @test[param] = [@pass]
+ @test.check_all_attributes.should == false
+ end
+ end
+
+ context "with an array with multiple items" do
+ it "should run if all the commands exits non-zero" do
+ @test[param] = [@fail] * 3
+ @test.check_all_attributes.should == true
+ end
+
+ it "should not run if one command exits zero" do
+ @test[param] = [@pass, @fail, @pass]
+ @test.check_all_attributes.should == false
+ end
+
+ it "should not run if all command exits zero" do
+ @test[param] = [@pass] * 3
+ @test.check_all_attributes.should == false
+ end
+ end
+ end
+ end
+ end
+
+ describe "#retrieve" do
+ before :each do
+ @exec_resource = Puppet::Type.type(:exec).new(:name => "/bogus/cmd")
+ end
+
+ it "should return :notrun when check_all_attributes returns true" do
+ @exec_resource.stubs(:check_all_attributes).returns true
+ @exec_resource.retrieve[:returns].should == :notrun
+ end
+
+ it "should return default exit code 0 when check_all_attributes returns false" do
+ @exec_resource.stubs(:check_all_attributes).returns false
+ @exec_resource.retrieve[:returns].should == ['0']
+ end
+
+ it "should return the specified exit code when check_all_attributes returns false" do
+ @exec_resource.stubs(:check_all_attributes).returns false
+ @exec_resource[:returns] = 42
+ @exec_resource.retrieve[:returns].should == ["42"]
+ end
+ end
+
+ describe "#output" do
+ before :each do
+ @exec_resource = Puppet::Type.type(:exec).new(:name => "/bogus/cmd")
+ end
+
+ it "should return the provider's run output" do
+ provider = stub 'provider'
+ status = stubs "process_status"
+ status.stubs(:exitstatus).returns("0")
+ provider.expects(:run).returns(["silly output", status])
+ @exec_resource.stubs(:provider).returns(provider)
+
+ @exec_resource.refresh
+ @exec_resource.output.should == 'silly output'
+ end
+ end
+
+ describe "#refresh" do
+ before :each do
+ @exec_resource = Puppet::Type.type(:exec).new(:name => "/bogus/cmd")
+ end
+
+ it "should call provider run with the refresh parameter if it is set" do
+ provider = stub 'provider'
+ @exec_resource.stubs(:provider).returns(provider)
+ @exec_resource.stubs(:[]).with(:refresh).returns('/myother/bogus/cmd')
+ provider.expects(:run).with('/myother/bogus/cmd')
+
+ @exec_resource.refresh
+ end
+
+ it "should call provider run with the specified command if the refresh parameter is not set" do
+ provider = stub 'provider'
+ status = stubs "process_status"
+ status.stubs(:exitstatus).returns("0")
+ provider.expects(:run).with('/bogus/cmd').returns(["silly output", status])
+ @exec_resource.stubs(:provider).returns(provider)
+
+ @exec_resource.refresh
+ end
+
+ it "should not run the provider if check_all_attributes is false" do
+ @exec_resource.stubs(:check_all_attributes).returns false
+ provider = stub 'provider'
+ provider.expects(:run).never
+ @exec_resource.stubs(:provider).returns(provider)
+
+ @exec_resource.refresh
+ end
end
end
diff --git a/spec/unit/type_spec.rb b/spec/unit/type_spec.rb
index 6d9d0b234..9b1f20500 100755
--- a/spec/unit/type_spec.rb
+++ b/spec/unit/type_spec.rb
@@ -1,6 +1,6 @@
#!/usr/bin/env ruby
-require File.dirname(__FILE__) + '/../spec_helper'
+require File.expand_path(File.join(File.dirname(__FILE__), '/../spec_helper'))
describe Puppet::Type do
it "should include the Cacher module" do