diff options
| author | Michael V. O'Brien <michael@reductivelabs.com> | 2007-09-25 12:00:07 -0500 |
|---|---|---|
| committer | Michael V. O'Brien <michael@reductivelabs.com> | 2007-09-25 12:00:07 -0500 |
| commit | ff2828f5dbe68ff1cb06a3503590a3e4bd1b59e3 (patch) | |
| tree | 8c8960cac1d7b3e8b48e44163062be3b3f4c201f /spec | |
| parent | f8ab62b212788a4591276c95b5f67217f7517e4e (diff) | |
| parent | ffaa8ce07979f4db860950fa9be08ca37964206f (diff) | |
Merge branch 'master' of git://reductivelabs.com/puppet
Diffstat (limited to 'spec')
38 files changed, 3548 insertions, 430 deletions
diff --git a/spec/Rakefile b/spec/Rakefile index 40d107312..bb2a75de5 100644 --- a/spec/Rakefile +++ b/spec/Rakefile @@ -2,9 +2,15 @@ require File.join(File.dirname(__FILE__), "spec_helper.rb") require 'rake' require 'spec/rake/spectask' +basedir = File.dirname(__FILE__) +puppetlibdir = File.join(basedir, "../lib") +puppettestlibdir = File.join(basedir, "../test/lib") +speclibdir = File.join(basedir, "lib") + desc "Run all spec unit tests" Spec::Rake::SpecTask.new('unit') do |t| t.spec_files = FileList['unit/**/*.rb'] + t.libs = [puppetlibdir, puppettestlibdir, speclibdir] end task :default => [:unit] diff --git a/spec/bin/spec b/spec/bin/spec index a7e6ce0cb..aaf320f34 100755 --- a/spec/bin/spec +++ b/spec/bin/spec @@ -1,3 +1,4 @@ +#!/usr/bin/env ruby $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) require 'spec' ::Spec::Runner::CommandLine.run(ARGV, STDERR, STDOUT, true, true) diff --git a/spec/integration/checksum.rb b/spec/integration/checksum.rb new file mode 100755 index 000000000..f112f7502 --- /dev/null +++ b/spec/integration/checksum.rb @@ -0,0 +1,48 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-22. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../spec_helper' + +require 'puppet/checksum' + +describe Puppet::Checksum, " when using the file terminus" do + before do + Puppet[:checksum_terminus] = "file" + + @content = "this is some content" + @sum = Puppet::Checksum.new(@content) + + @file = Puppet::Checksum.indirection.terminus.path(@sum.checksum) + end + + it "should store content at a path determined by its checksum" do + File.stubs(:directory?).returns(true) + filehandle = mock 'filehandle' + filehandle.expects(:print).with(@content) + File.expects(:open).with(@file, "w").yields(filehandle) + + @sum.save + end + + it "should retrieve stored content when the checksum is provided as the key" do + File.stubs(:exist?).returns(true) + File.expects(:read).with(@file).returns(@content) + + newsum = Puppet::Checksum.find(@sum.checksum) + + newsum.content.should == @content + end + + it "should remove specified files when asked" do + File.stubs(:exist?).returns(true) + File.expects(:unlink).with(@file) + + Puppet::Checksum.destroy(@sum) + end + + after do + Puppet.settings.clear + end +end diff --git a/spec/integration/node.rb b/spec/integration/node.rb new file mode 100755 index 000000000..8bc641bae --- /dev/null +++ b/spec/integration/node.rb @@ -0,0 +1,46 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-23. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../spec_helper' + +require 'puppet/node' + +describe Puppet::Node, " when using the memory terminus" do + before do + @name = "me" + @node = Puppet::Node.new(@name) + Puppet[:node_terminus] = "memory" + end + + it "should find no nodes by default" do + Puppet::Node.find(@name).should be_nil + end + + it "should be able to find nodes that were previously saved" do + @node.save + Puppet::Node.find(@name).should equal(@node) + end + + it "should replace existing saved nodes when a new node with the same name is saved" do + @node.save + two = Puppet::Node.new(@name) + two.save + Puppet::Node.find(@name).should equal(two) + end + + it "should be able to remove previously saved nodes" do + @node.save + Puppet::Node.destroy(@node) + Puppet::Node.find(@name).should be_nil + end + + it "should fail when asked to destroy a node that does not exist" do + proc { Puppet::Node.destroy(@node) }.should raise_error(ArgumentError) + end + + after do + Puppet.settings.clear + end +end diff --git a/spec/lib/spec/dsl/behaviour.rb b/spec/lib/spec/dsl/behaviour.rb index 5158bb673..159a0ba7e 100644 --- a/spec/lib/spec/dsl/behaviour.rb +++ b/spec/lib/spec/dsl/behaviour.rb @@ -1,6 +1,10 @@ +require(File.expand_path(File.dirname(__FILE__) + '../../../../../test/lib/puppettest/runnable_test.rb')) + module Spec module DSL - class EvalModule < Module; end + class EvalModule < Module; + include PuppetTest::RunnableTest + end class Behaviour extend BehaviourCallbacks diff --git a/spec/lib/spec/runner/behaviour_runner.rb b/spec/lib/spec/runner/behaviour_runner.rb index 1ac891f3c..078490e92 100644 --- a/spec/lib/spec/runner/behaviour_runner.rb +++ b/spec/lib/spec/runner/behaviour_runner.rb @@ -55,6 +55,8 @@ module Spec def run_behaviours @behaviours.each do |behaviour| + # LAK:NOTE: this 'runnable' test is Puppet-specific. + next unless behaviour.runnable? behaviour.run(@options.reporter, @options.dry_run, @options.reverse, @options.timeout) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index aa56fd93e..3017f272a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,13 +1,21 @@ -dir = File.dirname(__FILE__) -$:.unshift("#{dir}/lib").unshift("#{dir}/../lib") +dir = File.expand_path(File.dirname(__FILE__)) +$:.unshift("#{dir}/lib") +$:.unshift("#{dir}/../lib") # Add the old test dir, so that we can still find mocha and spec $:.unshift("#{dir}/../test/lib") require 'mocha' -require 'spec' require 'puppettest' +require 'spec' Spec::Runner.configure do |config| config.mock_with :mocha + config.prepend_before :each do + setup() if respond_to? :setup + end + + config.prepend_after :each do + teardown() if respond_to? :teardown + end end diff --git a/spec/unit/indirector/code.rb b/spec/unit/indirector/code.rb new file mode 100755 index 000000000..f34dcf402 --- /dev/null +++ b/spec/unit/indirector/code.rb @@ -0,0 +1,33 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' +require 'puppet/indirector/code' + +describe Puppet::Indirector::Code do + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @model = mock 'model' + @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model + Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) + + @code_class = Class.new(Puppet::Indirector::Code) do + def self.to_s + "Testing" + end + end + + @searcher = @code_class.new + end + + it "should not have a find() method defined" do + @searcher.should_not respond_to(:find) + end + + it "should not have a save() method defined" do + @searcher.should_not respond_to(:save) + end + + it "should not have a destroy() method defined" do + @searcher.should_not respond_to(:destroy) + end +end diff --git a/spec/unit/indirector/code/configuration.rb b/spec/unit/indirector/code/configuration.rb new file mode 100755 index 000000000..8652f342d --- /dev/null +++ b/spec/unit/indirector/code/configuration.rb @@ -0,0 +1,158 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-23. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/code/configuration' + +describe Puppet::Indirector::Code::Configuration do + # LAK:TODO I have no idea how to do this, or even if it should be in this class or test or what. + # This is used for determining if the client should recompile its configuration, so it's not sufficient + # to recompile and compare versions. + # It might be that the right solution is to require configuration caching, and then compare the cached + # configuration version to the current version, via some querying mechanism (i.e., the client asks for just + # the configuration's 'up-to-date' attribute, rather than the whole configuration). + it "should provide a mechanism for determining if the client's configuration is up to date" +end + +describe Puppet::Indirector::Code::Configuration do + before do + Puppet.expects(:version).returns(1) + Facter.expects(:value).with('fqdn').returns("my.server.com") + Facter.expects(:value).with('ipaddress').returns("my.ip.address") + end + + it "should gather data about itself" do + Puppet::Indirector::Code::Configuration.new + end + + it "should cache the server metadata and reuse it" do + compiler = Puppet::Indirector::Code::Configuration.new + node1 = stub 'node1', :merge => nil + node2 = stub 'node2', :merge => nil + compiler.stubs(:compile) + Puppet::Node.stubs(:search).with('node1').returns(node1) + Puppet::Node.stubs(:search).with('node2').returns(node2) + + compiler.find('node1') + compiler.find('node2') + end +end + +describe Puppet::Indirector::Code::Configuration, " when creating the interpreter" do + before do + @compiler = Puppet::Indirector::Code::Configuration.new + end + + it "should not create the interpreter until it is asked for the first time" do + interp = mock 'interp' + Puppet::Parser::Interpreter.expects(:new).with({}).returns(interp) + @compiler.interpreter.should equal(interp) + end + + it "should use the same interpreter for all compiles" do + interp = mock 'interp' + Puppet::Parser::Interpreter.expects(:new).with({}).returns(interp) + @compiler.interpreter.should equal(interp) + @compiler.interpreter.should equal(interp) + end + + it "should provide a mechanism for setting the code to pass to the interpreter" do + @compiler.should respond_to(:code=) + end + + it "should pass any specified code on to the interpreter when it is being initialized" do + code = "some code" + @compiler.code = code + interp = mock 'interp' + Puppet::Parser::Interpreter.expects(:new).with(:Code => code).returns(interp) + @compiler.send(:interpreter).should equal(interp) + end + +end + +describe Puppet::Indirector::Code::Configuration, " when finding nodes" do + before do + @compiler = Puppet::Indirector::Code::Configuration.new + @name = "me" + @node = mock 'node' + @compiler.stubs(:compile) + end + + it "should look node information up via the Node class with the provided key" do + @node.stubs :merge + Puppet::Node.expects(:search).with(@name).returns(@node) + @compiler.find(@name) + end + + it "should fail if it cannot find the node" do + @node.stubs :merge + Puppet::Node.expects(:search).with(@name).returns(nil) + proc { @compiler.find(@name) }.should raise_error(Puppet::Error) + end +end + +describe Puppet::Indirector::Code::Configuration, " after finding nodes" do + before do + Puppet.expects(:version).returns(1) + Puppet.settings.stubs(:value).with(:node_name).returns("cert") + Facter.expects(:value).with('fqdn').returns("my.server.com") + Facter.expects(:value).with('ipaddress').returns("my.ip.address") + @compiler = Puppet::Indirector::Code::Configuration.new + @name = "me" + @node = mock 'node' + @compiler.stubs(:compile) + Puppet::Node.stubs(:search).with(@name).returns(@node) + end + + it "should add the server's Puppet version to the node's parameters as 'serverversion'" do + @node.expects(:merge).with { |args| args["serverversion"] == "1" } + @compiler.find(@name) + end + + it "should add the server's fqdn to the node's parameters as 'servername'" do + @node.expects(:merge).with { |args| args["servername"] == "my.server.com" } + @compiler.find(@name) + end + + it "should add the server's IP address to the node's parameters as 'serverip'" do + @node.expects(:merge).with { |args| args["serverip"] == "my.ip.address" } + @compiler.find(@name) + end + + # LAK:TODO This is going to be difficult, because this whole process is so + # far removed from the actual connection that the certificate information + # will be quite hard to come by, dum by, gum by. + it "should search for the name using the client certificate's DN if the :node_name setting is set to 'cert'" +end + +describe Puppet::Indirector::Code::Configuration, " when creating configurations" do + before do + @compiler = Puppet::Indirector::Code::Configuration.new + @name = "me" + @node = stub 'node', :merge => nil, :name => @name, :environment => "yay" + Puppet::Node.stubs(:search).with(@name).returns(@node) + end + + it "should pass the found node to the interpreter for compiling" do + config = mock 'config' + @compiler.interpreter.expects(:compile).with(@node) + @compiler.find(@name) + end + + it "should return the results of compiling as the configuration" do + config = mock 'config' + @compiler.interpreter.expects(:compile).with(@node).returns(:configuration) + @compiler.find(@name).should == :configuration + end + + it "should benchmark the compile process" do + @compiler.expects(:benchmark).with do |level, message| + level == :notice and message =~ /^Compiled configuration/ + end + @compiler.interpreter.stubs(:compile).with(@node) + @compiler.find(@name) + end +end diff --git a/spec/unit/indirector/exec.rb b/spec/unit/indirector/exec.rb new file mode 100755 index 000000000..42fbe0955 --- /dev/null +++ b/spec/unit/indirector/exec.rb @@ -0,0 +1,49 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/indirector/exec' + +describe Puppet::Indirector::Exec do + before do + @indirection = mock 'indirection' + Puppet::Indirector::Indirection.expects(:instance).with(:testing).returns(@indirection) + @exec_class = Class.new(Puppet::Indirector::Exec) do + def self.to_s + "Testing" + end + + attr_accessor :command + end + + @searcher = @exec_class.new + @searcher.command = ["/echo"] + end + + it "should throw an exception if the command is not an array" do + @searcher.command = "/usr/bin/echo" + proc { @searcher.find("foo") }.should raise_error(Puppet::DevError) + end + + it "should throw an exception if the command is not fully qualified" do + @searcher.command = ["mycommand"] + proc { @searcher.find("foo") }.should raise_error(ArgumentError) + end + + it "should execute the command with the object name as the only argument" do + @searcher.expects(:execute).with(%w{/echo yay}) + @searcher.find("yay") + end + + it "should return the output of the script" do + @searcher.expects(:execute).with(%w{/echo yay}).returns("whatever") + @searcher.find("yay").should == "whatever" + end + + it "should return nil when the command produces no output" do + @searcher.expects(:execute).with(%w{/echo yay}).returns(nil) + @searcher.find("yay").should be_nil + end + + it "should be able to execute commands with multiple arguments" +end diff --git a/spec/unit/indirector/exec/node.rb b/spec/unit/indirector/exec/node.rb new file mode 100755 index 000000000..47f4ce7a5 --- /dev/null +++ b/spec/unit/indirector/exec/node.rb @@ -0,0 +1,62 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/exec/node' + +describe Puppet::Indirector::Exec::Node, " when constructing the command to run" do + before do + @indirection = mock 'indirection' + Puppet.settings.stubs(:value).with(:external_nodes).returns("/echo") + @searcher = Puppet::Indirector::Exec::Node.new + end + + it "should use the external_node script as the command" do + Puppet.expects(:[]).with(:external_nodes).returns("/bin/echo") + @searcher.command.should == %w{/bin/echo} + end + + it "should throw an exception if no external node command is set" do + Puppet.expects(:[]).with(:external_nodes).returns("none") + proc { @searcher.find("foo") }.should raise_error(ArgumentError) + end +end + +describe Puppet::Indirector::Exec::Node, " when handling the results of the command" do + before do + @indirection = mock 'indirection' + Puppet.settings.stubs(:value).with(:external_nodes).returns("/echo") + @searcher = Puppet::Indirector::Exec::Node.new + @node = stub 'node', :fact_merge => nil + @name = "yay" + Puppet::Node.expects(:new).with(@name).returns(@node) + @result = {} + # Use a local variable so the reference is usable in the execute() definition. + result = @result + @searcher.meta_def(:execute) do |command| + return YAML.dump(result) + end + end + + it "should translate the YAML into a Node instance" do + # Use an empty hash + @searcher.find(@name).should equal(@node) + end + + it "should set the resulting parameters as the node parameters" do + @result[:parameters] = {"a" => "b", "c" => "d"} + @node.expects(:parameters=).with "a" => "b", "c" => "d" + @searcher.find(@name) + end + + it "should set the resulting classes as the node classes" do + @result[:classes] = %w{one two} + @node.expects(:classes=).with %w{one two} + @searcher.find(@name) + end + + it "should merge the node's facts with its parameters" do + @node.expects(:fact_merge) + @searcher.find(@name) + end +end diff --git a/spec/unit/indirector/file.rb b/spec/unit/indirector/file.rb new file mode 100755 index 000000000..cc86f9fa9 --- /dev/null +++ b/spec/unit/indirector/file.rb @@ -0,0 +1,160 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' +require 'puppet/indirector/file' + +module FileTerminusTesting + def setup + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @model = mock 'model' + @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model + Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) + + @file_class = Class.new(Puppet::Indirector::File) do + def self.to_s + "Testing" + end + end + + @searcher = @file_class.new + + @path = "/my/file" + @dir = "/my" + end +end + +describe Puppet::Indirector::File, " when finding files" do + include FileTerminusTesting + + it "should provide a method to return file contents at a specified path" do + @searcher.should respond_to(:find) + end + + it "should return file contents as an instance of the model" do + content = "my content" + + file = mock 'file' + @model.expects(:new).with(content).returns(file) + + File.expects(:exist?).with(@path).returns(true) + File.expects(:read).with(@path).returns(content) + @searcher.find(@path) + end + + it "should create the model instance with the content as the only argument to initialization" do + content = "my content" + + file = mock 'file' + @model.expects(:new).with(content).returns(file) + + File.expects(:exist?).with(@path).returns(true) + File.expects(:read).with(@path).returns(content) + @searcher.find(@path).should equal(file) + end + + it "should return nil if no file is found" do + File.expects(:exist?).with(@path).returns(false) + @searcher.find(@path).should be_nil + end + + it "should fail intelligently if a found file cannot be read" do + File.expects(:exist?).with(@path).returns(true) + File.expects(:read).with(@path).raises(RuntimeError) + proc { @searcher.find(@path) }.should raise_error(Puppet::Error) + end + + it "should use the path() method to calculate the path if it exists" do + @searcher.meta_def(:path) do |name| + name.upcase + end + + File.expects(:exist?).with(@path.upcase).returns(false) + @searcher.find(@path) + end +end + +describe Puppet::Indirector::File, " when saving files" do + include FileTerminusTesting + + it "should provide a method to save file contents at a specified path" do + filehandle = mock 'file' + content = "my content" + File.expects(:directory?).with(@dir).returns(true) + File.expects(:open).with(@path, "w").yields(filehandle) + filehandle.expects(:print).with(content) + + file = stub 'file', :content => content, :path => @path, :name => @path + + @searcher.save(file) + end + + it "should fail intelligently if the file's parent directory does not exist" do + File.expects(:directory?).with(@dir).returns(false) + + file = stub 'file', :path => @path, :name => @path + + proc { @searcher.save(file) }.should raise_error(Puppet::Error) + end + + it "should fail intelligently if a file cannot be written" do + filehandle = mock 'file' + content = "my content" + File.expects(:directory?).with(@dir).returns(true) + File.expects(:open).with(@path, "w").yields(filehandle) + filehandle.expects(:print).with(content).raises(ArgumentError) + + file = stub 'file', :content => content, :path => @path, :name => @path + + proc { @searcher.save(file) }.should raise_error(Puppet::Error) + end + + it "should use the path() method to calculate the path if it exists" do + @searcher.meta_def(:path) do |name| + name.upcase + end + + file = stub 'file', :name => "/yay" + + File.expects(:open).with("/YAY", "w") + @searcher.save(file) + end +end + +describe Puppet::Indirector::File, " when removing files" do + include FileTerminusTesting + + it "should provide a method to remove files at a specified path" do + file = stub 'file', :path => @path, :name => @path + File.expects(:exist?).with(@path).returns(true) + File.expects(:unlink).with(@path) + + @searcher.destroy(file) + end + + it "should throw an exception if the file is not found" do + file = stub 'file', :path => @path, :name => @path + File.expects(:exist?).with(@path).returns(false) + + proc { @searcher.destroy(file) }.should raise_error(Puppet::Error) + end + + it "should fail intelligently if the file cannot be removed" do + file = stub 'file', :path => @path, :name => @path + File.expects(:exist?).with(@path).returns(true) + File.expects(:unlink).with(@path).raises(ArgumentError) + + proc { @searcher.destroy(file) }.should raise_error(Puppet::Error) + end + + it "should use the path() method to calculate the path if it exists" do + @searcher.meta_def(:path) do |name| + name.upcase + end + + file = stub 'file', :name => "/yay" + File.expects(:exist?).with("/YAY").returns(true) + File.expects(:unlink).with("/YAY") + + @searcher.destroy(file) + end +end diff --git a/spec/unit/indirector/file/checksum.rb b/spec/unit/indirector/file/checksum.rb new file mode 100755 index 000000000..539bb973c --- /dev/null +++ b/spec/unit/indirector/file/checksum.rb @@ -0,0 +1,142 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-22. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/file/checksum' + +module FileChecksumTesting + def setup + Puppet.settings.stubs(:use) + @store = Puppet::Indirector::File::Checksum.new + + @value = "70924d6fa4b2d745185fa4660703a5c0" + @sum = stub 'sum', :name => @value + + @dir = "/what/ever" + + Puppet.stubs(:[]).with(:bucketdir).returns(@dir) + + @path = @store.path(@value) + end +end + +describe Puppet::Indirector::File::Checksum do + it "should be a subclass of the File terminus class" do + Puppet::Indirector::File::Checksum.superclass.should equal(Puppet::Indirector::File) + end + + it "should have documentation" do + Puppet::Indirector::File::Checksum.doc.should be_instance_of(String) + end +end + +describe Puppet::Indirector::File::Checksum, " when initializing" do + it "should use the filebucket settings section" do + Puppet.settings.expects(:use).with(:filebucket) + Puppet::Indirector::File::Checksum.new + end +end + +describe Puppet::Indirector::File::Checksum, " when determining file paths" do + include FileChecksumTesting + + # I was previously passing the object in. + it "should use the value passed in to path() as the checksum" do + @value.expects(:name).never + @store.path(@value) + end + + it "should use the value of the :bucketdir setting as the root directory" do + @path.should =~ %r{^#{@dir}} + end + + it "should choose a path 8 directories deep with each directory name being the respective character in the checksum" do + dirs = @value[0..7].split("").join(File::SEPARATOR) + @path.should be_include(dirs) + end + + it "should use the full checksum as the final directory name" do + File.basename(File.dirname(@path)).should == @value + end + + it "should use 'contents' as the actual file name" do + File.basename(@path).should == "contents" + end + + it "should use the bucketdir, the 8 sum character directories, the full checksum, and 'contents' as the full file name" do + @path.should == [@dir, @value[0..7].split(""), @value, "contents"].flatten.join(File::SEPARATOR) + end +end + +describe Puppet::Indirector::File::Checksum, " when retrieving files" do + include FileChecksumTesting + + # The smallest test that will use the calculated path + it "should look for the calculated path" do + File.expects(:exist?).with(@path).returns(false) + @store.find(@value) + end + + it "should return an instance of Puppet::Checksum created with the content if the file exists" do + content = "my content" + sum = stub 'file' + Puppet::Checksum.expects(:new).with(content).returns(sum) + + File.expects(:exist?).with(@path).returns(true) + File.expects(:read).with(@path).returns(content) + + @store.find(@value).should equal(sum) + end + + it "should return nil if no file is found" do + File.expects(:exist?).with(@path).returns(false) + @store.find(@value).should be_nil + end + + it "should fail intelligently if a found file cannot be read" do + File.expects(:exist?).with(@path).returns(true) + File.expects(:read).with(@path).raises(RuntimeError) + proc { @store.find(@value) }.should raise_error(Puppet::Error) + end +end + +describe Puppet::Indirector::File::Checksum, " when saving files" do + include FileChecksumTesting + + # LAK:FIXME I don't know how to include in the spec the fact that we're + # using the superclass's save() method and thus are acquiring all of + # it's behaviours. + it "should save the content to the calculated path" do + File.stubs(:directory?).with(File.dirname(@path)).returns(true) + File.expects(:open).with(@path, "w") + + file = stub 'file', :name => @value + @store.save(file) + end + + it "should make any directories necessary for storage" do + FileUtils.expects(:mkdir_p).with do |arg| + File.umask == 0007 and arg == File.dirname(@path) + end + File.expects(:directory?).with(File.dirname(@path)).returns(true) + File.expects(:open).with(@path, "w") + + file = stub 'file', :name => @value + @store.save(file) + end +end + +describe Puppet::Indirector::File::Checksum, " when deleting files" do + include FileChecksumTesting + + it "should remove the file at the calculated path" do + File.expects(:exist?).with(@path).returns(true) + File.expects(:unlink).with(@path) + + file = stub 'file', :name => @value + @store.destroy(file) + end +end diff --git a/spec/unit/indirector/indirection.rb b/spec/unit/indirector/indirection.rb new file mode 100755 index 000000000..326b6b470 --- /dev/null +++ b/spec/unit/indirector/indirection.rb @@ -0,0 +1,160 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/indirector' + +describe Puppet::Indirector::Indirection, " when initializing" do + it "should keep a reference to the indirecting model" do + model = mock 'model' + @indirection = Puppet::Indirector::Indirection.new(model, :myind) + @indirection.model.should equal(model) + end + + it "should set the name" do + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :myind) + @indirection.name.should == :myind + end + + it "should require indirections to have unique names" do + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) + proc { Puppet::Indirector::Indirection.new(:test) }.should raise_error(ArgumentError) + end + + after do + @indirection.delete if defined? @indirection + end +end + +describe Puppet::Indirector::Indirection, " when managing indirection instances" do + it "should allow an indirection to be retrieved by name" do + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) + Puppet::Indirector::Indirection.instance(:test).should equal(@indirection) + end + + it "should return nil when the named indirection has not been created" do + Puppet::Indirector::Indirection.instance(:test).should be_nil + end + + after do + @indirection.delete if defined? @indirection + end +end + +describe Puppet::Indirector::Indirection, " when choosing terminus types" do + before do + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) + @terminus = mock 'terminus' + @terminus_class = stub 'terminus class', :new => @terminus + end + + it "should follow a convention on using per-model configuration parameters to determine the terminus class" do + Puppet.settings.expects(:valid?).with('test_terminus').returns(true) + Puppet.settings.expects(:value).with('test_terminus').returns(:foo) + Puppet::Indirector::Terminus.expects(:terminus_class).with(:foo, :test).returns(@terminus_class) + @indirection.terminus.should equal(@terminus) + end + + it "should use a default system-wide configuration parameter parameter to determine the terminus class when no + per-model configuration parameter is available" do + Puppet.settings.expects(:valid?).with('test_terminus').returns(false) + Puppet.settings.expects(:value).with(:default_terminus).returns(:foo) + Puppet::Indirector::Terminus.expects(:terminus_class).with(:foo, :test).returns(@terminus_class) + @indirection.terminus.should equal(@terminus) + end + + it "should select the specified terminus class if a name is provided" do + Puppet::Indirector::Terminus.expects(:terminus_class).with(:foo, :test).returns(@terminus_class) + @indirection.terminus(:foo).should equal(@terminus) + end + + it "should fail when the terminus class name is an empty string" do + proc { @indirection.terminus("") }.should raise_error(ArgumentError) + end + + it "should fail when the terminus class name is nil" do + proc { @indirection.terminus(nil) }.should raise_error(ArgumentError) + end + + it "should fail when the specified terminus class cannot be found" do + Puppet::Indirector::Terminus.expects(:terminus_class).with(:foo, :test).returns(nil) + proc { @indirection.terminus(:foo) }.should raise_error(ArgumentError) + end + + after do + @indirection.delete if defined? @indirection + end +end + +describe Puppet::Indirector::Indirection, " when managing terminus instances" do + before do + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) + @terminus = mock 'terminus' + @terminus_class = mock 'terminus class' + Puppet::Indirector::Terminus.stubs(:terminus_class).with(:foo, :test).returns(@terminus_class) + end + + it "should create an instance of the chosen terminus class" do + @terminus_class.stubs(:new).returns(@terminus) + @indirection.terminus(:foo).should equal(@terminus) + end + + it "should allow the clearance of cached terminus instances" do + terminus1 = mock 'terminus1' + terminus2 = mock 'terminus2' + @terminus_class.stubs(:new).returns(terminus1, terminus2, ArgumentError) + @indirection.terminus(:foo).should equal(terminus1) + @indirection.class.clear_cache + @indirection.terminus(:foo).should equal(terminus2) + end + + # Make sure it caches the terminus. + it "should return the same terminus instance each time for a given name" do + @terminus_class.stubs(:new).returns(@terminus) + @indirection.terminus(:foo).should equal(@terminus) + @indirection.terminus(:foo).should equal(@terminus) + end + + it "should not create a terminus instance until one is actually needed" do + Puppet::Indirector.expects(:terminus).never + indirection = Puppet::Indirector::Indirection.new(mock('model'), :lazytest) + end + + after do + @indirection.delete + Puppet::Indirector::Indirection.clear_cache + end +end + +describe Puppet::Indirector::Indirection do + before do + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) + @terminus = mock 'terminus' + @indirection.stubs(:terminus).returns(@terminus) + end + + it "should handle lookups of a model instance by letting the appropriate terminus perform the lookup" do + @terminus.expects(:find).with(:mything).returns(:whev) + @indirection.find(:mything).should == :whev + end + + it "should handle removing model instances from a terminus letting the appropriate terminus remove the instance" do + @terminus.expects(:destroy).with(:mything).returns(:whev) + @indirection.destroy(:mything).should == :whev + end + + it "should handle searching for model instances by letting the appropriate terminus find the matching instances" do + @terminus.expects(:search).with(:mything).returns(:whev) + @indirection.search(:mything).should == :whev + end + + it "should handle storing a model instance by letting the appropriate terminus store the instance" do + @terminus.expects(:save).with(:mything).returns(:whev) + @indirection.save(:mything).should == :whev + end + + after do + @indirection.delete + Puppet::Indirector::Indirection.clear_cache + end +end diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb new file mode 100755 index 000000000..1702bf51f --- /dev/null +++ b/spec/unit/indirector/indirector.rb @@ -0,0 +1,86 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/defaults' +require 'puppet/indirector' + +describe Puppet::Indirector, " when available to a model" do + before do + @thingie = Class.new do + extend Puppet::Indirector + end + end + + it "should provide a way for the model to register an indirection under a name" do + @thingie.should respond_to(:indirects) + end +end + +describe Puppet::Indirector, "when registering an indirection" do + before do + @thingie = Class.new do + extend Puppet::Indirector + end + end + + it "should require a name when registering a model" do + Proc.new {@thingie.send(:indirects) }.should raise_error(ArgumentError) + end + + it "should create an indirection instance to manage each indirecting model" do + @indirection = @thingie.indirects(:test) + @indirection.should be_instance_of(Puppet::Indirector::Indirection) + end + + it "should not allow a model to register under multiple names" do + # Keep track of the indirection instance so we can delete it on cleanup + @indirection = @thingie.indirects :first + Proc.new { @thingie.indirects :second }.should raise_error(ArgumentError) + end + + it "should make the indirection available via an accessor" do + @indirection = @thingie.indirects :first + @thingie.indirection.should equal(@indirection) + end + + after do + @indirection.delete if @indirection + end +end + +describe Puppet::Indirector, " when redirecting a model" do + before do + @thingie = Class.new do + extend Puppet::Indirector + end + @mock_terminus = mock('Terminus') + @indirection = @thingie.send(:indirects, :test) + @thingie.expects(:indirection).returns(@mock_terminus) + end + + it "should give the model the ability to lookup a model instance by letting the indirection perform the lookup" do + @mock_terminus.expects(:find) + @thingie.find + end + + it "should give the model the ability to remove model instances from a terminus by letting the indirection remove the instance" do + @mock_terminus.expects(:destroy) + @thingie.destroy + end + + it "should give the model the ability to search for model instances by letting the indirection find the matching instances" do + @mock_terminus.expects(:search) + @thingie.search + end + + it "should give the model the ability to store a model instance by letting the indirection store the instance" do + thing = @thingie.new + @mock_terminus.expects(:save).with(thing) + thing.save + end + + after do + @indirection.delete + end +end diff --git a/spec/unit/indirector/ldap.rb b/spec/unit/indirector/ldap.rb new file mode 100755 index 000000000..fe9408986 --- /dev/null +++ b/spec/unit/indirector/ldap.rb @@ -0,0 +1,147 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/indirector/ldap' + +describe Puppet::Indirector::Ldap, " when searching ldap" do + before do + @indirection = mock 'indirection' + Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) + @ldap_class = Class.new(Puppet::Indirector::Ldap) do + def self.to_s + "Testing" + end + end + + @connection = mock 'ldap' + + @searcher = @ldap_class.new + + # Stub everything, and we can selectively replace with an expect as + # we need to for testing. + @searcher.stubs(:connection).returns(@connection) + @searcher.stubs(:search_filter).returns(:filter) + @searcher.stubs(:search_base).returns(:base) + @searcher.stubs(:process) + end + + it "should call the ldapsearch method with the name being searched for" do + @searcher.expects(:ldapsearch).with("yay") + @searcher.find "yay" + end + + it "should fail if no block is passed to the ldapsearch method" do + proc { @searcher.ldapsearch("blah") }.should raise_error(ArgumentError) + end + + it "should use the results of the ldapbase method as the ldap search base" do + @searcher.stubs(:search_base).returns("mybase") + @connection.expects(:search).with do |*args| + args[0].should == "mybase" + true + end + @searcher.find "yay" + end + + it "should default to the value of the :search_base setting as the result of the ldapbase method" do + Puppet.expects(:[]).with(:ldapbase).returns("myldapbase") + searcher = @ldap_class.new + searcher.search_base.should == "myldapbase" + end + + it "should use the results of the :search_attributes method as the list of attributes to return" do + @searcher.stubs(:search_attributes).returns(:myattrs) + @connection.expects(:search).with do |*args| + args[3].should == :myattrs + true + end + @searcher.find "yay" + end + + it "should use the results of the :search_filter method as the search filter" do + @searcher.stubs(:search_filter).with("yay").returns("yay's filter") + @connection.expects(:search).with do |*args| + args[2].should == "yay's filter" + true + end + @searcher.find "yay" + end + + it "should use depth 2 when searching" do + @connection.expects(:search).with do |*args| + args[1].should == 2 + true + end + @searcher.find "yay" + end + + it "should call process() on the first found entry" do + @connection.expects(:search).yields("myresult") + @searcher.expects(:process).with("yay", "myresult") + @searcher.find "yay" + end + + it "should reconnect and retry the search if there is a failure" do + run = false + @connection.stubs(:search).with do |*args| + if run + true + else + run = true + raise "failed" + end + end.yields("myresult") + @searcher.expects(:process).with("yay", "myresult") + + @searcher.find "yay" + end + + it "should not reconnect on failure more than once" do + count = 0 + @connection.stubs(:search).with do |*args| + count += 1 + raise ArgumentError, "yay" + end + proc { @searcher.find("whatever") }.should raise_error(Puppet::Error) + count.should == 2 + end + + it "should return true if an entry is found" do + @connection.expects(:search).yields("result") + @searcher.ldapsearch("whatever") { |r| }.should be_true + end +end + +describe Puppet::Indirector::Ldap, " when connecting to ldap" do + confine "LDAP is not available" => Puppet.features.ldap? + confine "No LDAP test data for networks other than Luke's" => Facter.value(:domain) == "madstop.com" + + it "should only create the ldap connection when asked for it the first time" + + it "should throw an exception if it cannot connect to LDAP" + + it "should use SSL when the :ldapssl setting is true" + + it "should connect to the server specified in the :ldapserver setting" + + it "should use the port specified in the :ldapport setting" + + it "should use protocol version 3" + + it "should follow referrals" + + it "should use the user specified in the :ldapuser setting" + + it "should use the password specified in the :ldappassord setting" + + it "should have an ldap method that returns an LDAP connection object" + + it "should fail when LDAP support is missing" +end + +describe Puppet::Indirector::Ldap, " when reconnecting to ldap" do + confine "Not running on culain as root" => (Puppet::Util::SUIDManager.uid == 0 and Facter.value("hostname") == "culain") + + it "should reconnect to ldap when connections are lost" +end diff --git a/spec/unit/indirector/ldap/node.rb b/spec/unit/indirector/ldap/node.rb new file mode 100755 index 000000000..37953db19 --- /dev/null +++ b/spec/unit/indirector/ldap/node.rb @@ -0,0 +1,240 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/ldap/node' + +module LdapNodeSearching + def setup + @searcher = Puppet::Indirector::Ldap::Node.new + @entries = {} + entries = @entries + + @connection = mock 'connection' + @entry = mock 'entry' + @connection.stubs(:search).yields(@entry) + @searcher.stubs(:connection).returns(@connection) + @searcher.stubs(:class_attributes).returns([]) + @searcher.stubs(:parent_attribute).returns(nil) + @searcher.stubs(:search_base).returns(:yay) + @searcher.stubs(:search_filter).returns(:filter) + + @node = mock 'node' + @node.stubs(:fact_merge) + @name = "mynode" + Puppet::Node.stubs(:new).with(@name).returns(@node) + end +end + +describe Puppet::Indirector::Ldap::Node, " when searching for nodes" do + include LdapNodeSearching + + it "should return nil if no results are found in ldap" do + @connection.stubs(:search) + @searcher.find("mynode").should be_nil + end + + it "should return a node object if results are found in ldap" do + @entry.stubs(:to_hash).returns({}) + @searcher.find("mynode").should equal(@node) + end + + it "should deduplicate class values" do + @entry.stubs(:to_hash).returns({}) + @searcher.stubs(:class_attributes).returns(%w{one two}) + @entry.stubs(:vals).with("one").returns(%w{a b}) + @entry.stubs(:vals).with("two").returns(%w{b c}) + @node.expects(:classes=).with(%w{a b c}) + @searcher.find("mynode") + end + + it "should add any values stored in the class_attributes attributes to the node classes" do + @entry.stubs(:to_hash).returns({}) + @searcher.stubs(:class_attributes).returns(%w{one two}) + @entry.stubs(:vals).with("one").returns(%w{a b}) + @entry.stubs(:vals).with("two").returns(%w{c d}) + @node.expects(:classes=).with(%w{a b c d}) + @searcher.find("mynode") + end + + it "should add all entry attributes as node parameters" do + @entry.stubs(:to_hash).returns("one" => ["two"], "three" => ["four"]) + @node.expects(:parameters=).with("one" => "two", "three" => "four") + @searcher.find("mynode") + end + + it "should retain false parameter values" do + @entry.stubs(:to_hash).returns("one" => [false]) + @node.expects(:parameters=).with("one" => false) + @searcher.find("mynode") + end + + it "should turn single-value parameter value arrays into single non-arrays" do + @entry.stubs(:to_hash).returns("one" => ["a"]) + @node.expects(:parameters=).with("one" => "a") + @searcher.find("mynode") + end + + it "should keep multi-valued parametes as arrays" do + @entry.stubs(:to_hash).returns("one" => ["a", "b"]) + @node.expects(:parameters=).with("one" => ["a", "b"]) + @searcher.find("mynode") + end +end + +describe Puppet::Indirector::Ldap::Node, " when a parent node exists" do + include LdapNodeSearching + + before do + @parent = mock 'parent' + @parent_parent = mock 'parent_parent' + + @searcher.meta_def(:search_filter) do |name| + return name + end + @connection.stubs(:search).with { |*args| args[2] == @name }.yields(@entry) + @connection.stubs(:search).with { |*args| args[2] == 'parent' }.yields(@parent) + @connection.stubs(:search).with { |*args| args[2] == 'parent_parent' }.yields(@parent_parent) + + @searcher.stubs(:parent_attribute).returns(:parent) + end + + it "should add any parent classes to the node's classes" do + @entry.stubs(:to_hash).returns({}) + @entry.stubs(:vals).with(:parent).returns(%w{parent}) + @entry.stubs(:vals).with("one").returns(%w{a b}) + + @parent.stubs(:to_hash).returns({}) + @parent.stubs(:vals).with("one").returns(%w{c d}) + @parent.stubs(:vals).with(:parent).returns(nil) + + @searcher.stubs(:class_attributes).returns(%w{one}) + @node.expects(:classes=).with(%w{a b c d}) + @searcher.find("mynode") + end + + it "should add any parent parameters to the node's parameters" do + @entry.stubs(:to_hash).returns("one" => "two") + @entry.stubs(:vals).with(:parent).returns(%w{parent}) + + @parent.stubs(:to_hash).returns("three" => "four") + @parent.stubs(:vals).with(:parent).returns(nil) + + @node.expects(:parameters=).with("one" => "two", "three" => "four") + @searcher.find("mynode") + end + + it "should prefer node parameters over parent parameters" do + @entry.stubs(:to_hash).returns("one" => "two") + @entry.stubs(:vals).with(:parent).returns(%w{parent}) + + @parent.stubs(:to_hash).returns("one" => "three") + @parent.stubs(:vals).with(:parent).returns(nil) + + @node.expects(:parameters=).with("one" => "two") + @searcher.find("mynode") + end + + it "should recursively look up parent information" do + @entry.stubs(:to_hash).returns("one" => "two") + @entry.stubs(:vals).with(:parent).returns(%w{parent}) + + @parent.stubs(:to_hash).returns("three" => "four") + @parent.stubs(:vals).with(:parent).returns(['parent_parent']) + + @parent_parent.stubs(:to_hash).returns("five" => "six") + @parent_parent.stubs(:vals).with(:parent).returns(nil) + @parent_parent.stubs(:vals).with(:parent).returns(nil) + + @node.expects(:parameters=).with("one" => "two", "three" => "four", "five" => "six") + @searcher.find("mynode") + end + + it "should not allow loops in parent declarations" do + @entry.stubs(:to_hash).returns("one" => "two") + @entry.stubs(:vals).with(:parent).returns(%w{parent}) + + @parent.stubs(:to_hash).returns("three" => "four") + @parent.stubs(:vals).with(:parent).returns([@name]) + proc { @searcher.find("mynode") }.should raise_error(ArgumentError) + end +end + +describe Puppet::Indirector::Ldap::Node, " when developing the search query" do + before do + @searcher = Puppet::Indirector::Ldap::Node.new + end + + it "should return the value of the :ldapclassattrs split on commas as the class attributes" do + Puppet.stubs(:[]).with(:ldapclassattrs).returns("one,two") + @searcher.class_attributes.should == %w{one two} + end + + it "should return nil as the parent attribute if the :ldapparentattr is set to an empty string" do + Puppet.stubs(:[]).with(:ldapparentattr).returns("") + @searcher.parent_attribute.should be_nil + end + + it "should return the value of the :ldapparentattr as the parent attribute" do + Puppet.stubs(:[]).with(:ldapparentattr).returns("pere") + @searcher.parent_attribute.should == "pere" + end + + it "should use the value of the :ldapstring as the search filter" do + Puppet.stubs(:[]).with(:ldapstring).returns("mystring") + @searcher.search_filter("testing").should == "mystring" + end + + it "should replace '%s' with the node name in the search filter if it is present" do + Puppet.stubs(:[]).with(:ldapstring).returns("my%sstring") + @searcher.search_filter("testing").should == "mytestingstring" + end + + it "should not modify the global :ldapstring when replacing '%s' in the search filter" do + filter = mock 'filter' + filter.expects(:include?).with("%s").returns(true) + filter.expects(:gsub).with("%s", "testing").returns("mynewstring") + Puppet.stubs(:[]).with(:ldapstring).returns(filter) + @searcher.search_filter("testing").should == "mynewstring" + end +end + +describe Puppet::Indirector::Ldap::Node, " when deciding attributes to search for" do + before do + @searcher = Puppet::Indirector::Ldap::Node.new + end + + it "should use 'nil' if the :ldapattrs setting is 'all'" do + Puppet.stubs(:[]).with(:ldapattrs).returns("all") + @searcher.search_attributes.should be_nil + end + + it "should split the value of :ldapattrs on commas and use the result as the attribute list" do + Puppet.stubs(:[]).with(:ldapattrs).returns("one,two") + @searcher.stubs(:class_attributes).returns([]) + @searcher.stubs(:parent_attribute).returns(nil) + @searcher.search_attributes.should == %w{one two} + end + + it "should add the class attributes to the search attributes if not returning all attributes" do + Puppet.stubs(:[]).with(:ldapattrs).returns("one,two") + @searcher.stubs(:class_attributes).returns(%w{three four}) + @searcher.stubs(:parent_attribute).returns(nil) + # Sort them so i don't have to care about return order + @searcher.search_attributes.sort.should == %w{one two three four}.sort + end + + it "should add the parent attribute to the search attributes if not returning all attributes" do + Puppet.stubs(:[]).with(:ldapattrs).returns("one,two") + @searcher.stubs(:class_attributes).returns([]) + @searcher.stubs(:parent_attribute).returns("parent") + @searcher.search_attributes.sort.should == %w{one two parent}.sort + end + + it "should not add nil parent attributes to the search attributes" do + Puppet.stubs(:[]).with(:ldapattrs).returns("one,two") + @searcher.stubs(:class_attributes).returns([]) + @searcher.stubs(:parent_attribute).returns(nil) + @searcher.search_attributes.should == %w{one two} + end +end diff --git a/spec/unit/indirector/memory.rb b/spec/unit/indirector/memory.rb new file mode 100755 index 000000000..ac6f055ce --- /dev/null +++ b/spec/unit/indirector/memory.rb @@ -0,0 +1,53 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' +require 'puppet/indirector/memory' + +describe "A Memory Terminus", :shared => true do + it "should find no instances by default" do + @searcher.find(@name).should be_nil + end + + it "should be able to find instances that were previously saved" do + @searcher.save(@instance) + @searcher.find(@name).should equal(@instance) + end + + it "should replace existing saved instances when a new instance with the same name is saved" do + @searcher.save(@instance) + two = stub 'second', :name => @name + @searcher.save(two) + @searcher.find(@name).should equal(two) + end + + it "should be able to remove previously saved instances" do + @searcher.save(@instance) + @searcher.destroy(@instance) + @searcher.find(@name).should be_nil + end + + it "should fail when asked to destroy an instance that does not exist" do + proc { @searcher.destroy(@instance) }.should raise_error(ArgumentError) + end +end + +describe Puppet::Indirector::Memory do + it_should_behave_like "A Memory Terminus" + + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @model = mock 'model' + @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model + Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) + + @memory_class = Class.new(Puppet::Indirector::Memory) do + def self.to_s + "Testing" + end + end + + @searcher = @memory_class.new + @name = "me" + @instance = stub 'instance', :name => @name + end +end diff --git a/spec/unit/indirector/memory/node.rb b/spec/unit/indirector/memory/node.rb new file mode 100755 index 000000000..cba4af53a --- /dev/null +++ b/spec/unit/indirector/memory/node.rb @@ -0,0 +1,19 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/memory/node' + +# All of our behaviour is described here, so we always have to +# include it. +require 'unit/indirector/memory' + +describe Puppet::Indirector::Memory::Node do + before do + @name = "me" + @searcher = Puppet::Indirector::Memory::Node.new + @instance = stub 'instance', :name => @name + end + + it_should_behave_like "A Memory Terminus" +end diff --git a/spec/unit/indirector/null.rb b/spec/unit/indirector/null.rb new file mode 100755 index 000000000..9e1dcb07c --- /dev/null +++ b/spec/unit/indirector/null.rb @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' +require 'puppet/indirector/null' + +describe Puppet::Indirector::Null do + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @model = mock 'model' + @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model + Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) + + @null_class = Class.new(Puppet::Indirector::Null) do + def self.to_s + "Testing" + end + end + + @searcher = @null_class.new + end + + it "should return return an instance of the indirected model" do + object = mock 'object' + @model.expects(:new).with("yay").returns object + @searcher.find("yay").should equal(object) + end +end diff --git a/spec/unit/indirector/null/node.rb b/spec/unit/indirector/null/node.rb new file mode 100755 index 000000000..c589e5820 --- /dev/null +++ b/spec/unit/indirector/null/node.rb @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/null/node' + +describe Puppet::Indirector::Null::Node do + before do + @searcher = Puppet::Indirector::Null::Node.new + end + + it "should call node_merge() on the returned node" do + node = mock 'node' + Puppet::Node.expects(:new).with("mynode").returns(node) + node.expects(:fact_merge) + @searcher.find("mynode") + end +end diff --git a/spec/unit/indirector/terminus.rb b/spec/unit/indirector/terminus.rb new file mode 100755 index 000000000..dc86cf315 --- /dev/null +++ b/spec/unit/indirector/terminus.rb @@ -0,0 +1,226 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'puppet/defaults' +require 'puppet/indirector' + +describe Puppet::Indirector::Terminus do + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + + @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil + Puppet::Indirector::Indirection.stubs(:instance).with(:mystuff).returns(@indirection) + @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Abstract" + end + end + @terminus = Class.new(@abstract_terminus) do + def self.to_s + "Terminus::Type::MyStuff" + end + end + end + + it "should provide a method for setting terminus class documentation" do + @terminus.should respond_to(:desc) + end + + it "should support a class-level name attribute" do + @terminus.should respond_to(:name) + end + + it "should support a class-level indirection attribute" do + @terminus.should respond_to(:indirection) + end + + it "should support a class-level terminus-type attribute" do + @terminus.should respond_to(:terminus_type) + end + + it "should support a class-level model attribute" do + @terminus.should respond_to(:model) + end + + it "should accept indirection instances as its indirection" do + indirection = stub 'indirection', :is_a? => true, :register_terminus_type => nil + proc { @terminus.indirection = indirection }.should_not raise_error + @terminus.indirection.should equal(indirection) + end + + it "should look up indirection instances when only a name has been provided" do + indirection = mock 'indirection' + Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns(indirection) + @terminus.indirection = :myind + @terminus.indirection.should equal(indirection) + end + + it "should fail when provided a name that does not resolve to an indirection" do + Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns(nil) + proc { @terminus.indirection = :myind }.should raise_error(ArgumentError) + + # It shouldn't overwrite our existing one (or, more normally, it shouldn't set + # anything). + @terminus.indirection.should equal(@indirection) + end +end + +# LAK: This could reasonably be in the Indirection instances, too. It doesn't make +# a whole heckuva lot of difference, except that with the instance loading in +# the Terminus base class, we have to have a check to see if we're already +# instance-loading a given terminus class type. +describe Puppet::Indirector::Terminus, " when managing terminus classes" do + it "should provide a method for registering terminus classes" do + Puppet::Indirector::Terminus.should respond_to(:register_terminus_class) + end + + it "should provide a method for returning terminus classes by name and type" do + terminus = stub 'terminus_type', :terminus_type => :abstract, :name => :whatever + Puppet::Indirector::Terminus.register_terminus_class(terminus) + Puppet::Indirector::Terminus.terminus_class(:abstract, :whatever).should equal(terminus) + end + + it "should set up autoloading for any terminus class types requested" do + Puppet::Indirector::Terminus.expects(:instance_load).with(:test2, "puppet/indirector/test2") + Puppet::Indirector::Terminus.terminus_class(:test2, :whatever) + end + + it "should load terminus classes that are not found" do + # Set up instance loading; it would normally happen automatically + Puppet::Indirector::Terminus.instance_load :test1, "puppet/indirector/test1" + Puppet::Indirector::Terminus.instance_loader(:test1).expects(:load).with(:yay) + Puppet::Indirector::Terminus.terminus_class(:test1, :yay) + end + + it "should fail when no indirection can be found" do + Puppet::Indirector::Indirection.expects(:instance).with(:myindirection).returns(nil) + + @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Abstract" + end + end + proc { + @terminus = Class.new(@abstract_terminus) do + def self.to_s + "MyIndirection" + end + end + }.should raise_error(ArgumentError) + end + + it "should register the terminus class with the terminus base class" do + Puppet::Indirector::Terminus.expects(:register_terminus_class).with do |type| + type.terminus_type == :abstract and type.name == :myindirection + end + @indirection = stub 'indirection', :name => :myind, :register_terminus_type => nil + Puppet::Indirector::Indirection.expects(:instance).with(:myindirection).returns(@indirection) + + @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Abstract" + end + end + + @terminus = Class.new(@abstract_terminus) do + def self.to_s + "MyIndirection" + end + end + end +end + +describe Puppet::Indirector::Terminus, " when creating terminus class types" do + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @subclass = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Puppet::Indirector::Terminus::MyTermType" + end + end + end + + it "should set the name of the abstract subclass to be its class constant" do + @subclass.name.should equal(:mytermtype) + end + + it "should mark abstract terminus types as such" do + @subclass.should be_abstract_terminus + end + + it "should not allow instances of abstract subclasses to be created" do + proc { @subclass.new }.should raise_error(Puppet::DevError) + end +end + +describe Puppet::Indirector::Terminus, " when creating terminus classes" do + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + + @indirection = stub 'indirection', :name => :myind, :register_terminus_type => nil + Puppet::Indirector::Indirection.expects(:instance).with(:myindirection).returns(@indirection) + + @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Abstract" + end + end + @terminus = Class.new(@abstract_terminus) do + def self.to_s + "MyIndirection" + end + end + end + + it "should associate the subclass with an indirection based on the subclass constant" do + @terminus.indirection.should equal(@indirection) + end + + it "should set the subclass's type to the abstract terminus name" do + @terminus.terminus_type.should == :abstract + end + + it "should set the subclass's name to the indirection name" do + @terminus.name.should == :myindirection + end + + it "should set the subclass's model to the indirection model" do + @indirection.expects(:model).returns :yay + @terminus.model.should == :yay + end +end + +describe Puppet::Indirector::Terminus, " when a terminus instance" do + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @indirection = stub 'indirection', :name => :myyaml, :register_terminus_type => nil + Puppet::Indirector::Indirection.stubs(:instance).with(:mystuff).returns(@indirection) + @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Abstract" + end + end + @terminus_class = Class.new(@abstract_terminus) do + def self.to_s + "MyStuff" + end + end + @terminus_class.name = :test + @terminus = @terminus_class.new + end + + it "should return the class's name as its name" do + @terminus.name.should == :test + end + + it "should return the class's indirection as its indirection" do + @terminus.indirection.should equal(@indirection) + end + + it "should set the instances's type to the abstract terminus type's name" do + @terminus.terminus_type.should == :abstract + end + + it "should set the instances's model to the indirection's model" do + @indirection.expects(:model).returns :yay + @terminus.model.should == :yay + end +end diff --git a/spec/unit/indirector/yaml.rb b/spec/unit/indirector/yaml.rb new file mode 100755 index 000000000..9e1d65e49 --- /dev/null +++ b/spec/unit/indirector/yaml.rb @@ -0,0 +1,104 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/indirector/yaml' + +module YamlTesting + def setup + @indirection = stub 'indirection', :name => :myyaml, :register_terminus_type => nil + Puppet::Indirector::Indirection.stubs(:instance).with(:myyaml).returns(@indirection) + @store_class = Class.new(Puppet::Indirector::Yaml) do + def self.to_s + "MyYaml" + end + end + @store = @store_class.new + + @subject = Object.new + @subject.metaclass.send(:attr_accessor, :name) + @subject.name = :me + + @dir = "/what/ever" + Puppet.settings.stubs(:use) + Puppet.settings.stubs(:value).with(:yamldir).returns(@dir) + end +end + +describe Puppet::Indirector::Yaml, " when choosing file location" do + include YamlTesting + + it "should store all files in a single file root set in the Puppet defaults" do + @store.send(:path, :me).should =~ %r{^#{@dir}} + end + + it "should use the terminus name for choosing the subdirectory" do + @store.send(:path, :me).should =~ %r{^#{@dir}/myyaml} + end + + it "should use the object's name to determine the file name" do + @store.send(:path, :me).should =~ %r{me.yaml$} + end +end + +describe Puppet::Indirector::Yaml, " when storing objects as YAML" do + include YamlTesting + + it "should only store objects that respond to :name" do + proc { @store.save(Object.new) }.should raise_error(ArgumentError) + end + + it "should convert Ruby objects to YAML and write them to disk" do + yaml = @subject.to_yaml + file = mock 'file' + path = @store.send(:path, @subject.name) + FileTest.expects(:exist?).with(File.dirname(path)).returns(true) + File.expects(:open).with(path, "w", 0660).yields(file) + file.expects(:print).with(yaml) + + @store.save(@subject) + end + + it "should create the indirection subdirectory if it does not exist" do + yaml = @subject.to_yaml + file = mock 'file' + path = @store.send(:path, @subject.name) + dir = File.dirname(path) + FileTest.expects(:exist?).with(dir).returns(false) + Dir.expects(:mkdir).with(dir) + File.expects(:open).with(path, "w", 0660).yields(file) + file.expects(:print).with(yaml) + + @store.save(@subject) + end +end + +describe Puppet::Indirector::Yaml, " when retrieving YAML" do + include YamlTesting + + it "should require the name of the object to retrieve" do + proc { @store.find(nil) }.should raise_error(ArgumentError) + end + + it "should read YAML in from disk and convert it to Ruby objects" do + path = @store.send(:path, @subject.name) + + yaml = @subject.to_yaml + FileTest.expects(:exist?).with(path).returns(true) + File.expects(:read).with(path).returns(yaml) + + @store.find(@subject.name).instance_variable_get("@name").should == :me + end + + it "should fail coherently when the stored YAML is invalid" do + path = @store.send(:path, @subject.name) + + # Something that will fail in yaml + yaml = "--- !ruby/object:Hash" + + FileTest.expects(:exist?).with(path).returns(true) + File.expects(:read).with(path).returns(yaml) + + proc { @store.find(@subject.name) }.should raise_error(Puppet::Error) + end +end diff --git a/spec/unit/indirector/yaml/facts.rb b/spec/unit/indirector/yaml/facts.rb new file mode 100755 index 000000000..f1256cfa4 --- /dev/null +++ b/spec/unit/indirector/yaml/facts.rb @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/node/facts' +require 'puppet/indirector/yaml/facts' + +describe Puppet::Indirector::Yaml::Facts do + it "should be a subclass of the Yaml terminus" do + Puppet::Indirector::Yaml::Facts.superclass.should equal(Puppet::Indirector::Yaml) + end + + + it "should have documentation" do + Puppet::Indirector::Yaml::Facts.doc.should_not be_nil + end + + it "should be registered with the facts indirection" do + indirection = Puppet::Indirector::Indirection.instance(:facts) + Puppet::Indirector::Yaml::Facts.indirection.should equal(indirection) + end + + it "should have its name set to :facts" do + Puppet::Indirector::Yaml::Facts.name.should == :facts + end +end diff --git a/spec/unit/node/configuration.rb b/spec/unit/node/configuration.rb index 4429fe3a3..8ba55f50c 100755 --- a/spec/unit/node/configuration.rb +++ b/spec/unit/node/configuration.rb @@ -133,3 +133,340 @@ describe Puppet::Node::Configuration, " when extracting transobjects" do botarray.include?(:botres).should be_true end end + +describe Puppet::Node::Configuration, " when functioning as a resource container" do + before do + @config = Puppet::Node::Configuration.new("host") + @one = stub 'resource1', :ref => "Me[you]", :configuration= => nil + @two = stub 'resource2', :ref => "Me[him]", :configuration= => nil + @dupe = stub 'resource3', :ref => "Me[you]", :configuration= => nil + end + + it "should provide a method to add one or more resources" do + @config.add_resource @one, @two + @config.resource(@one.ref).should equal(@one) + @config.resource(@two.ref).should equal(@two) + end + + it "should make all vertices available by resource reference" do + @config.add_resource(@one) + @config.resource(@one.ref).should equal(@one) + @config.vertices.find { |r| r.ref == @one.ref }.should equal(@one) + end + + it "should not allow two resources with the same resource reference" do + @config.add_resource(@one) + proc { @config.add_resource(@dupe) }.should raise_error(ArgumentError) + end + + it "should not store objects that do not respond to :ref" do + proc { @config.add_resource("thing") }.should raise_error(ArgumentError) + end + + it "should remove all resources when asked" do + @config.add_resource @one + @config.add_resource @two + @one.expects :remove + @two.expects :remove + @config.clear(true) + end + + it "should support a mechanism for finishing resources" do + @one.expects :finish + @two.expects :finish + @config.add_resource @one + @config.add_resource @two + + @config.finalize + end + + it "should optionally support an initialization block and should finalize after such blocks" do + @one.expects :finish + @two.expects :finish + config = Puppet::Node::Configuration.new("host") do |conf| + conf.add_resource @one + conf.add_resource @two + end + end + + it "should inform the resource that it is the resource's configuration" do + @one.expects(:configuration=).with(@config) + @config.add_resource @one + end + + it "should be able to find resources by reference" do + @config.add_resource @one + @config.resource(@one.ref).should equal(@one) + end + + it "should be able to find resources by reference or by type/title tuple" do + @config.add_resource @one + @config.resource("me", "you").should equal(@one) + end + + it "should have a mechanism for removing resources" do + @config.add_resource @one + @one.expects :remove + @config.remove_resource(@one) + @config.resource(@one.ref).should be_nil + @config.vertex?(@one).should be_false + end +end + +module ApplyingConfigurations + def setup + @config = Puppet::Node::Configuration.new("host") + + @config.retrieval_duration = Time.now + @transaction = mock 'transaction' + Puppet::Transaction.stubs(:new).returns(@transaction) + @transaction.stubs(:evaluate) + @transaction.stubs(:cleanup) + @transaction.stubs(:addtimes) + end +end + +describe Puppet::Node::Configuration, " when applying" do + include ApplyingConfigurations + + it "should create and evaluate a transaction" do + @transaction.expects(:evaluate) + @config.apply + end + + it "should provide the configuration time to the transaction" do + @transaction.expects(:addtimes).with do |arg| + arg[:config_retrieval].should be_instance_of(Time) + true + end + @config.apply + end + + it "should clean up the transaction" do + @transaction.expects :cleanup + @config.apply + end + + it "should return the transaction" do + @config.apply.should equal(@transaction) + end + + it "should yield the transaction if a block is provided" do + @config.apply do |trans| + trans.should equal(@transaction) + end + end + + it "should default to not being a host configuration" do + @config.host_config.should be_nil + end + + it "should pass supplied tags on to the transaction" do + @transaction.expects(:tags=).with(%w{one two}) + @config.apply(:tags => %w{one two}) + end + + it "should set ignoreschedules on the transaction if specified in apply()" do + @transaction.expects(:ignoreschedules=).with(true) + @config.apply(:ignoreschedules => true) + end +end + +describe Puppet::Node::Configuration, " when applying host configurations" do + include ApplyingConfigurations + + # super() doesn't work in the setup method for some reason + before do + @config.host_config = true + end + + it "should send a report if reporting is enabled" do + Puppet[:report] = true + @transaction.expects :send_report + @config.apply + end + + it "should send a report if report summaries are enabled" do + Puppet[:summarize] = true + @transaction.expects :send_report + @config.apply + end + + it "should initialize the state database before applying a configuration" do + Puppet::Util::Storage.expects(:load) + + # Short-circuit the apply, so we know we're loading before the transaction + Puppet::Transaction.expects(:new).raises ArgumentError + proc { @config.apply }.should raise_error(ArgumentError) + end + + it "should sync the state database after applying" do + Puppet::Util::Storage.expects(:store) + @config.apply + end + + after { Puppet.settings.clear } +end + +describe Puppet::Node::Configuration, " when applying non-host configurations" do + include ApplyingConfigurations + + before do + @config.host_config = false + end + + it "should never send reports" do + Puppet[:report] = true + Puppet[:summarize] = true + @transaction.expects(:send_report).never + @config.apply + end + + it "should never modify the state database" do + Puppet::Util::Storage.expects(:load).never + Puppet::Util::Storage.expects(:store).never + @config.apply + end + + after { Puppet.settings.clear } +end + +describe Puppet::Node::Configuration, " when creating a relationship graph" do + before do + @config = Puppet::Node::Configuration.new("host") + @compone = Puppet::Type::Component.create :name => "one" + @comptwo = Puppet::Type::Component.create :name => "two", :require => ["class", "one"] + @file = Puppet::Type.type(:file) + @one = @file.create :path => "/one" + @two = @file.create :path => "/two" + @config.add_edge! @compone, @one + @config.add_edge! @comptwo, @two + + @three = @file.create :path => "/three" + @four = @file.create :path => "/four", :require => ["file", "/three"] + @five = @file.create :path => "/five" + @config.add_resource @compone, @comptwo, @one, @two, @three, @four, @five + @relationships = @config.relationship_graph + end + + it "should be able to create a relationship graph" do + @relationships.should be_instance_of(Puppet::Node::Configuration) + end + + it "should copy its host_config setting to the relationship graph" do + config = Puppet::Node::Configuration.new + config.host_config = true + config.relationship_graph.host_config.should be_true + end + + it "should not have any components" do + @relationships.vertices.find { |r| r.instance_of?(Puppet::Type::Component) }.should be_nil + end + + it "should have all non-component resources from the configuration" do + # The failures print out too much info, so i just do a class comparison + @relationships.vertex?(@five).should be_true + end + + it "should have all resource relationships set as edges" do + @relationships.edge?(@three, @four).should be_true + end + + it "should copy component relationships to all contained resources" do + @relationships.edge?(@one, @two).should be_true + end + + it "should get removed when the configuration is cleaned up" do + @relationships.expects(:clear).with(false) + @config.clear + @config.instance_variable_get("@relationship_graph").should be_nil + end + + it "should create a new relationship graph after clearing the old one" do + @relationships.expects(:clear).with(false) + @config.clear + @config.relationship_graph.should be_instance_of(Puppet::Node::Configuration) + end + + it "should look up resources in the relationship graph if not found in the main configuration" do + five = stub 'five', :ref => "File[five]", :configuration= => nil + @relationships.add_resource five + @config.resource(five.ref).should equal(five) + end + + it "should provide a method to create additional resources that also registers the resource" do + args = {:name => "/yay", :ensure => :file} + resource = stub 'file', :ref => "File[/yay]", :configuration= => @config + Puppet::Type.type(:file).expects(:create).with(args).returns(resource) + @config.create_resource :file, args + @config.resource("File[/yay]").should equal(resource) + end + + it "should provide a mechanism for creating implicit resources" do + args = {:name => "/yay", :ensure => :file} + resource = stub 'file', :ref => "File[/yay]", :configuration= => @config + Puppet::Type.type(:file).expects(:create).with(args).returns(resource) + resource.expects(:implicit=).with(true) + @config.create_implicit_resource :file, args + @config.resource("File[/yay]").should equal(resource) + end + + it "should remove resources created mid-transaction" do + args = {:name => "/yay", :ensure => :file} + resource = stub 'file', :ref => "File[/yay]", :configuration= => @config + @transaction = mock 'transaction' + Puppet::Transaction.stubs(:new).returns(@transaction) + @transaction.stubs(:evaluate) + @transaction.stubs(:cleanup) + @transaction.stubs(:addtimes) + Puppet::Type.type(:file).expects(:create).with(args).returns(resource) + resource.expects :remove + @config.apply do |trans| + @config.create_resource :file, args + @config.resource("File[/yay]").should equal(resource) + end + @config.resource("File[/yay]").should be_nil + end + + it "should remove resources from the relationship graph if it exists" do + @config.remove_resource(@one) + @config.relationship_graph.vertex?(@one).should be_false + end + + after do + Puppet::Type.allclear + end +end + +describe Puppet::Node::Configuration, " when writing dot files" do + before do + @config = Puppet::Node::Configuration.new("host") + @name = :test + @file = File.join(Puppet[:graphdir], @name.to_s + ".dot") + end + it "should only write when it is a host configuration" do + File.expects(:open).with(@file).never + @config.host_config = false + Puppet[:graph] = true + @config.write_graph(@name) + end + + it "should only write when graphing is enabled" do + File.expects(:open).with(@file).never + @config.host_config = true + Puppet[:graph] = false + @config.write_graph(@name) + end + + it "should write a dot file based on the passed name" do + File.expects(:open).with(@file, "w").yields(stub("file", :puts => nil)) + @config.expects(:to_dot).with("name" => @name.to_s.capitalize) + @config.host_config = true + Puppet[:graph] = true + @config.write_graph(@name) + end + + after do + Puppet.settings.clear + end +end diff --git a/spec/unit/node/facts.rb b/spec/unit/node/facts.rb new file mode 100755 index 000000000..61f05a2b2 --- /dev/null +++ b/spec/unit/node/facts.rb @@ -0,0 +1,39 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/node/facts' + +describe Puppet::Node::Facts, " when indirecting" do + before do + @terminus = mock 'terminus' + Puppet::Node::Facts.stubs(:indirection).returns(@terminus) + + # We have to clear the cache so that the facts ask for our terminus stub, + # instead of anything that might be cached. + Puppet::Indirector::Indirection.clear_cache + @facts = Puppet::Node::Facts.new("me", "one" => "two") + end + + it "should redirect to the specified fact store for retrieval" do + @terminus.expects(:find).with(:my_facts) + Puppet::Node::Facts.find(:my_facts) + end + + it "should redirect to the specified fact store for storage" do + @terminus.expects(:save).with(@facts) + @facts.save + end + + after do + mocha_verify + Puppet::Indirector::Indirection.clear_cache + end +end + +describe Puppet::Node::Facts, " when storing and retrieving" do + it "should add metadata to the facts" do + facts = Puppet::Node::Facts.new("me", "one" => "two", "three" => "four") + facts.values[:_timestamp].should be_instance_of(Time) + end +end diff --git a/spec/unit/other/node.rb b/spec/unit/node/node.rb index 66d5ba9d7..fe5d2be8b 100755 --- a/spec/unit/other/node.rb +++ b/spec/unit/node/node.rb @@ -11,6 +11,10 @@ describe Puppet::Node, " when initializing" do @node.name.should == "testnode" end + it "should not allow nil node names" do + proc { Puppet::Node.new(nil) }.should raise_error(ArgumentError) + end + it "should default to an empty parameter hash" do @node.parameters.should == {} end @@ -62,7 +66,7 @@ describe Puppet::Node, " when returning the environment" do end it "should return the central environment if there is no environment fact nor explicit environment" do - Puppet.config.expects(:[]).with(:environment).returns(:centralenv) + Puppet.settings.expects(:[]).with(:environment).returns(:centralenv) @node.environment.should == :centralenv end @@ -77,7 +81,7 @@ describe Puppet::Node, " when returning the environment" do end it "should not use an explicit environment that is an empty string" do - Puppet.config.expects(:[]).with(:environment).returns(nil) + Puppet.settings.expects(:[]).with(:environment).returns(nil) @node.environment.should be_nil end end @@ -85,17 +89,47 @@ end describe Puppet::Node, " when merging facts" do before do @node = Puppet::Node.new("testnode") + Puppet::Node::Facts.stubs(:find).with(@node.name).returns(Puppet::Node::Facts.new(@node.name, "one" => "c", "two" => "b")) end it "should prefer parameters already set on the node over facts from the node" do @node.parameters = {"one" => "a"} - @node.fact_merge("one" => "c") + @node.fact_merge @node.parameters["one"].should == "a" end it "should add passed parameters to the parameter list" do @node.parameters = {"one" => "a"} - @node.fact_merge("two" => "b") + @node.fact_merge @node.parameters["two"].should == "b" end + + it "should accept arbitrary parameters to merge into its parameters" do + @node.parameters = {"one" => "a"} + @node.merge "two" => "three" + @node.parameters["two"].should == "three" + end +end + +describe Puppet::Node, " when indirecting" do + before do + @terminus = mock 'terminus' + Puppet::Node.stubs(:indirection).returns(@terminus) + end + + it "should redirect to the specified node source" do + @terminus.expects(:find).with(:my_node.to_s) + Puppet::Node.find(:my_node.to_s) + end + + after do + Puppet::Indirector::Indirection.clear_cache + end +end + +describe Puppet::Node do + # LAK:NOTE This is used to keep track of when a given node has connected, + # so we can report on nodes that do not appear to connecting to the + # central server. + it "should provide a method for noting that the node has connected" end diff --git a/spec/unit/node/searching.rb b/spec/unit/node/searching.rb new file mode 100755 index 000000000..e747996e4 --- /dev/null +++ b/spec/unit/node/searching.rb @@ -0,0 +1,79 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' +require 'puppet/node/searching' +require 'puppet/node/facts' + +describe Puppet::Node::Searching, " when searching for nodes" do + before do + @searcher = Object.new + @searcher.extend(Puppet::Node::Searching) + @facts = Puppet::Node::Facts.new("foo", "hostname" => "yay", "domain" => "domain.com") + @node = Puppet::Node.new("foo") + Puppet::Node::Facts.stubs(:find).with("foo").returns(@facts) + end + + it "should search for the node by its key first" do + names = [] + @searcher.expects(:find).with do |name| + names << name + names == %w{foo} + end.returns(@node) + @searcher.search("foo").should equal(@node) + end + + it "should return the first node found using the generated list of names" do + names = [] + @searcher.expects(:find).with("foo").returns(nil) + @searcher.expects(:find).with("yay.domain.com").returns(@node) + @searcher.search("foo").should equal(@node) + end + + it "should search for the rest of the names inversely by length" do + names = [] + @facts.values["fqdn"] = "longer.than.the.normal.fqdn.com" + @searcher.stubs(:find).with do |name| + names << name + end + @searcher.search("foo") + # Strip off the key + names.shift + + # And the 'default' + names.pop + + length = 100 + names.each do |name| + (name.length < length).should be_true + length = name.length + end + end + + it "should attempt to find a default node if no names are found" do + names = [] + @searcher.stubs(:find).with do |name| + names << name + end.returns(nil) + @searcher.search("foo") + names[-1].should == "default" + end + + it "should cache the nodes" do + @searcher.expects(:find).with("foo").returns(@node) + @searcher.search("foo").should equal(@node) + @searcher.search("foo").should equal(@node) + end + + it "should flush the node cache using the :filetimeout parameter" do + node2 = Puppet::Node.new("foo2") + Puppet[:filetimeout] = -1 + # I couldn't get this to work with :expects + @searcher.stubs(:find).returns(@node, node2).then.raises(ArgumentError) + @searcher.search("foo").should equal(@node) + @searcher.search("foo").should equal(node2) + end + + after do + Puppet.settings.clear + end +end diff --git a/spec/unit/other/checksum.rb b/spec/unit/other/checksum.rb new file mode 100755 index 000000000..6a63e833d --- /dev/null +++ b/spec/unit/other/checksum.rb @@ -0,0 +1,92 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-22. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/checksum' + +describe Puppet::Checksum do + it "should have 'Checksum' and the checksum algorithm when converted to a string" do + inst = Puppet::Checksum.new("whatever", "md5") + inst.to_s.should == "Checksum<{md5}#{inst.checksum}>" + end + + it "should convert algorithm names to symbols when they are set after checksum creation" do + sum = Puppet::Checksum.new("whatever") + sum.algorithm = "md5" + sum.algorithm.should == :md5 + end + + it "should return the checksum as the name" do + sum = Puppet::Checksum.new("whatever") + sum.checksum.should == sum.name + end +end + +describe Puppet::Checksum, " when initializing" do + before do + @content = "this is some content" + @sum = Puppet::Checksum.new(@content) + end + + it "should require content" do + proc { Puppet::Checksum.new(nil) }.should raise_error(ArgumentError) + end + + it "should set the content appropriately" do + @sum.content.should == @content + end + + it "should calculate the checksum" do + require 'digest/md5' + Digest::MD5.expects(:hexdigest).with(@content).returns(:mychecksum) + @sum.checksum.should == :mychecksum + end + + it "should not calculate the checksum until it is asked for" do + require 'digest/md5' + Digest::MD5.expects(:hexdigest).never + sum = Puppet::Checksum.new(@content, :md5) + end + + it "should remove the old checksum value if the algorithm is changed" do + Digest::MD5.expects(:hexdigest).with(@content).returns(:oldsum) + oldsum = @sum.checksum + @sum.algorithm = :sha1 + Digest::SHA1.expects(:hexdigest).with(@content).returns(:newsum) + @sum.checksum.should == :newsum + end + + it "should default to 'md5' as the checksum algorithm if the algorithm is not in the name" do + @sum.algorithm.should == :md5 + end + + it "should support specifying the algorithm during initialization" do + sum = Puppet::Checksum.new(@content, :sha1) + sum.algorithm.should == :sha1 + end + + it "should fail when an unsupported algorithm is used" do + proc { Puppet::Checksum.new(@content, :nope) }.should raise_error(ArgumentError) + end +end + +describe Puppet::Checksum, " when using back-ends" do + it "should redirect using Puppet::Indirector" do + Puppet::Indirector::Indirection.instance(:checksum).model.should equal(Puppet::Checksum) + end + + it "should have a :save instance method" do + Puppet::Checksum.new("mysum").should respond_to(:save) + end + + it "should respond to :find" do + Puppet::Checksum.should respond_to(:find) + end + + it "should respond to :destroy" do + Puppet::Checksum.should respond_to(:destroy) + end +end diff --git a/spec/unit/other/modules.rb b/spec/unit/other/modules.rb index 0ab37aa9e..f53c43e89 100755 --- a/spec/unit/other/modules.rb +++ b/spec/unit/other/modules.rb @@ -5,10 +5,10 @@ require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Module, " when building its search path" do include PuppetTest - it "should ignore unqualified paths in the search path" do + it "should fully qualify unqualified paths in the search path" do Puppet[:modulepath] = "something:/my/something" File.stubs(:directory?).returns(true) - Puppet::Module.modulepath.should == %w{/my/something} + Puppet::Module.modulepath.should == [File.join(Dir.getwd, 'something'), "/my/something"] end it "should ignore paths that do not exist" do @@ -26,7 +26,7 @@ describe Puppet::Module, " when building its search path" do end it "should use the environment-specific search path when a node environment is provided" do - Puppet.config.expects(:value).with(:modulepath, "myenv").returns("/mone:/mtwo") + Puppet.settings.expects(:value).with(:modulepath, "myenv").returns("/mone:/mtwo") File.stubs(:directory?).returns(true) Puppet::Module.modulepath("myenv").should == %w{/mone /mtwo} end @@ -82,30 +82,30 @@ describe Puppet::Module, " when searching for templates" do end it "should use the main templatedir if no module is found" do - Puppet.config.expects(:value).with(:templatedir, nil).returns("/my/templates") + Puppet.settings.expects(:value).with(:templatedir, nil).returns("/my/templates") Puppet::Module.expects(:find).with("mymod", nil).returns(nil) Puppet::Module.find_template("mymod/mytemplate").should == "/my/templates/mymod/mytemplate" end it "should return unqualified templates directly in the template dir" do - Puppet.config.expects(:value).with(:templatedir, nil).returns("/my/templates") + Puppet.settings.expects(:value).with(:templatedir, nil).returns("/my/templates") Puppet::Module.expects(:find).never Puppet::Module.find_template("mytemplate").should == "/my/templates/mytemplate" end it "should use the environment templatedir if no module is found and an environment is specified" do - Puppet.config.expects(:value).with(:templatedir, "myenv").returns("/myenv/templates") + Puppet.settings.expects(:value).with(:templatedir, "myenv").returns("/myenv/templates") Puppet::Module.expects(:find).with("mymod", "myenv").returns(nil) Puppet::Module.find_template("mymod/mytemplate", "myenv").should == "/myenv/templates/mymod/mytemplate" end it "should use the node environment if specified" do - Puppet.config.expects(:value).with(:modulepath, "myenv").returns("/my/templates") + Puppet.settings.expects(:value).with(:modulepath, "myenv").returns("/my/templates") File.stubs(:directory?).returns(true) Puppet::Module.find_template("mymod/envtemplate", "myenv").should == "/my/templates/mymod/templates/envtemplate" end - after { Puppet.config.clear } + after { Puppet.settings.clear } end describe Puppet::Module, " when searching for manifests" do @@ -125,27 +125,27 @@ describe Puppet::Module, " when searching for manifests" do end it "should use the node environment if specified" do - Puppet.config.expects(:value).with(:modulepath, "myenv").returns("/env/modules") + Puppet.settings.expects(:value).with(:modulepath, "myenv").returns("/env/modules") File.stubs(:directory?).returns(true) Dir.expects(:glob).with("/env/modules/mymod/manifests/envmanifest.pp").returns(%w{/env/modules/mymod/manifests/envmanifest.pp}) Puppet::Module.find_manifests("mymod/envmanifest.pp", :environment => "myenv").should == ["/env/modules/mymod/manifests/envmanifest.pp"] end it "should return all manifests matching the glob pattern" do - Puppet.config.expects(:value).with(:modulepath, nil).returns("/my/modules") + Puppet.settings.expects(:value).with(:modulepath, nil).returns("/my/modules") File.stubs(:directory?).returns(true) Dir.expects(:glob).with("/my/modules/mymod/manifests/yay/*.pp").returns(%w{/one /two}) Puppet::Module.find_manifests("mymod/yay/*.pp").should == %w{/one /two} end it "should default to the 'init.pp' file in the manifests directory" do - Puppet.config.expects(:value).with(:modulepath, nil).returns("/my/modules") + Puppet.settings.expects(:value).with(:modulepath, nil).returns("/my/modules") File.stubs(:directory?).returns(true) Dir.expects(:glob).with("/my/modules/mymod/manifests/init.pp").returns(%w{my manifest}) Puppet::Module.find_manifests("mymod").should == %w{my manifest} end - after { Puppet.config.clear } + after { Puppet.settings.clear } end describe Puppet::Module, " when returning files" do diff --git a/spec/unit/other/pgraph.rb b/spec/unit/other/pgraph.rb new file mode 100755 index 000000000..19809ac1e --- /dev/null +++ b/spec/unit/other/pgraph.rb @@ -0,0 +1,285 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-12. +# Copyright (c) 2006. All rights reserved. + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/util/graph' + +class Container + include Puppet::Util::Graph + include Enumerable + attr_accessor :name + def each + @children.each do |c| yield c end + end + + def initialize(name, ary) + @name = name + @children = ary + end + + def push(*ary) + ary.each { |c| @children.push(c)} + end + + def to_s + @name + end +end + +describe Puppet::PGraph do + before do + @graph = Puppet::PGraph.new + end + + it "should correctly clear vertices and edges when asked" do + @graph.add_edge!("a", "b") + @graph.add_vertex! "c" + @graph.clear + @graph.vertices.should be_empty + @graph.edges.should be_empty + end +end + +describe Puppet::PGraph, " when matching edges" do + before do + @graph = Puppet::PGraph.new + @event = Puppet::Event.new(:source => "a", :event => :yay) + @none = Puppet::Event.new(:source => "a", :event => :NONE) + + @edges = {} + @edges["a/b"] = Puppet::Relationship["a", "b", {:event => :yay, :callback => :refresh}] + @edges["a/c"] = Puppet::Relationship["a", "c", {:event => :yay, :callback => :refresh}] + @graph.add_edge!(@edges["a/b"]) + end + + it "should match edges whose source matches the source of the event" do + @graph.matching_edges([@event]).should == [@edges["a/b"]] + end + + it "should match always match nothing when the event is :NONE" do + @graph.matching_edges([@none]).should be_empty + end + + it "should match multiple edges" do + @graph.add_edge!(@edges["a/c"]) + @graph.matching_edges([@event]).sort.should == [@edges["a/b"], @edges["a/c"]].sort + end +end + +describe Puppet::PGraph, " when determining dependencies" do + before do + @graph = Puppet::PGraph.new + + @graph.add_edge!("a", "b") + @graph.add_edge!("a", "c") + @graph.add_edge!("b", "d") + end + + it "should find all dependents when they are on multiple levels" do + @graph.dependents("a").sort.should == %w{b c d}.sort + end + + it "should find single dependents" do + @graph.dependents("b").sort.should == %w{d}.sort + end + + it "should return an empty array when there are no dependents" do + @graph.dependents("c").sort.should == [].sort + end + + it "should find all dependencies when they are on multiple levels" do + @graph.dependencies("d").sort.should == %w{a b} + end + + it "should find single dependencies" do + @graph.dependencies("c").sort.should == %w{a} + end + + it "should return an empty array when there are no dependencies" do + @graph.dependencies("a").sort.should == [] + end +end + +describe Puppet::PGraph, " when splicing the relationship graph" do + def container_graph + @one = Container.new("one", %w{a b}) + @two = Container.new("two", ["c", "d"]) + @three = Container.new("three", ["i", "j"]) + @middle = Container.new("middle", ["e", "f", @two]) + @top = Container.new("top", ["g", "h", @middle, @one, @three]) + @empty = Container.new("empty", []) + + @contgraph = @top.to_graph + + # We have to add the container to the main graph, else it won't + # be spliced in the dependency graph. + @contgraph.add_vertex!(@empty) + end + + def dependency_graph + @depgraph = Puppet::PGraph.new + @contgraph.vertices.each do |v| + @depgraph.add_vertex(v) + end + + # We have to specify a relationship to our empty container, else it + # never makes it into the dep graph in the first place. + {@one => @two, "f" => "c", "h" => @middle, "c" => @empty}.each do |source, target| + @depgraph.add_edge!(source, target, :callback => :refresh) + end + end + + def splice + @depgraph.splice!(@contgraph, Container) + end + + before do + container_graph + dependency_graph + splice + end + + it "should not create a cyclic graph" do + @depgraph.should_not be_cyclic + end + + # This is the real heart of splicing -- replacing all containers in + # our relationship and exploding their relationships so that each + # relationship to a container gets copied to all of its children. + it "should remove all Container objects from the dependency graph" do + @depgraph.vertices.find_all { |v| v.is_a?(Container) }.should be_empty + end + + it "should add container relationships to contained objects" do + @contgraph.leaves(@middle).each do |leaf| + @depgraph.should be_edge("h", leaf) + end + end + + it "should explode container-to-container relationships, making edges between all respective contained objects" do + @one.each do |oobj| + @two.each do |tobj| + @depgraph.should be_edge(oobj, tobj) + end + end + end + + it "should no longer contain anything but the non-container objects" do + @depgraph.vertices.find_all { |v| ! v.is_a?(String) }.should be_empty + end + + it "should copy labels" do + @depgraph.edges.each do |edge| + edge.label.should == {:callback => :refresh} + end + end + + it "should not add labels to edges that have none" do + @depgraph.add_edge!(@two, @three) + splice + @depgraph.edge_label("c", "i").should == {} + end + + it "should copy labels over edges that have none" do + @depgraph.add_edge!("c", @three, {:callback => :refresh}) + splice + # And make sure the label got copied. + @depgraph.edge_label("c", "i").should == {:callback => :refresh} + end + + it "should not replace a label with a nil label" do + # Lastly, add some new label-less edges and make sure the label stays. + @depgraph.add_edge!(@middle, @three) + @depgraph.add_edge!("c", @three, {:callback => :refresh}) + splice + @depgraph.edge_label("c", "i").should == {:callback => :refresh} + end + + it "should copy labels to all created edges" do + @depgraph.add_edge!(@middle, @three) + @depgraph.add_edge!("c", @three, {:callback => :refresh}) + splice + @three.each do |child| + edge = @depgraph.edge_class.new("c", child) + @depgraph.should be_edge(edge) + @depgraph[edge].should == {:callback => :refresh} + end + end +end + +# Labels in this graph are used for managing relationships, +# including callbacks, so they're quite important. +describe Puppet::PGraph, " when managing labels" do + before do + @graph = Puppet::PGraph.new + @label = {:callback => :yay} + end + + it "should return nil for edges with no label" do + @graph.add_edge!(:a, :b) + @graph.edge_label(:a, :b).should be_nil + end + + it "should just return empty label hashes" do + @graph.add_edge!(:a, :b, {}) + @graph.edge_label(:a, :b).should == {} + end + + it "should consider empty label hashes to be nil when copying" do + @graph.add_edge!(:a, :b) + @graph.copy_label(:a, :b, {}) + @graph.edge_label(:a, :b).should be_nil + end + + it "should return label hashes" do + @graph.add_edge!(:a, :b, @label) + @graph.edge_label(:a, :b).should == @label + end + + it "should replace nil labels with real labels" do + @graph.add_edge!(:a, :b) + @graph.copy_label(:a, :b, @label) + @graph.edge_label(:a, :b).should == @label + end + + it "should not replace labels with nil labels" do + @graph.add_edge!(:a, :b, @label) + @graph.copy_label(:a, :b, {}) + @graph.edge_label(:a, :b).should == @label + end +end + +describe Puppet::PGraph, " when sorting the graph" do + before do + @graph = Puppet::PGraph.new + end + + def add_edges(hash) + hash.each do |a,b| + @graph.add_edge!(a, b) + end + end + + it "should fail on two-vertex loops" do + add_edges :a => :b, :b => :a + proc { @graph.topsort }.should raise_error(Puppet::Error) + end + + it "should fail on multi-vertex loops" do + add_edges :a => :b, :b => :c, :c => :a + proc { @graph.topsort }.should raise_error(Puppet::Error) + end + + it "should fail when a larger tree contains a small cycle" do + add_edges :a => :b, :b => :a, :c => :a, :d => :c + proc { @graph.topsort }.should raise_error(Puppet::Error) + end + + it "should succeed on trees with no cycles" do + add_edges :a => :b, :b => :e, :c => :a, :d => :c + proc { @graph.topsort }.should_not raise_error + end +end diff --git a/spec/unit/other/transaction.rb b/spec/unit/other/transaction.rb new file mode 100755 index 000000000..7990d2eef --- /dev/null +++ b/spec/unit/other/transaction.rb @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::Transaction, " when determining tags" do + before do + @config = Puppet::Node::Configuration.new + @transaction = Puppet::Transaction.new(@config) + end + + it "should default to the tags specified in the :tags setting" do + Puppet.expects(:[]).with(:tags).returns("one") + @transaction.tags.should == %w{one} + end + + it "should split tags based on ','" do + Puppet.expects(:[]).with(:tags).returns("one,two") + @transaction.tags.should == %w{one two} + end + + it "should use any tags set after creation" do + Puppet.expects(:[]).with(:tags).never + @transaction.tags = %w{one two} + @transaction.tags.should == %w{one two} + end +end diff --git a/spec/unit/other/transbucket.rb b/spec/unit/other/transbucket.rb new file mode 100755 index 000000000..8cb9abaa4 --- /dev/null +++ b/spec/unit/other/transbucket.rb @@ -0,0 +1,133 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::TransBucket do + before do + @bucket = Puppet::TransBucket.new + end + + it "should be able to produce a RAL component" do + @bucket.name = "luke" + @bucket.type = "user" + + resource = nil + proc { resource = @bucket.to_type }.should_not raise_error + resource.should be_instance_of(Puppet::Type::Component) + resource.title.should == "user[luke]" + end + + it "should accept TransObjects into its children list" do + object = Puppet::TransObject.new("luke", "user") + proc { @bucket.push(object) }.should_not raise_error + @bucket.each do |o| + o.should equal(object) + end + end + + it "should accept TransBuckets into its children list" do + object = Puppet::TransBucket.new() + proc { @bucket.push(object) }.should_not raise_error + @bucket.each do |o| + o.should equal(object) + end + end + + it "should refuse to accept any children that are not TransObjects or TransBuckets" do + proc { @bucket.push "a test" }.should raise_error + end + + it "should return nil as its reference when type or name is missing" do + @bucket.to_ref.should be_nil + end + + it "should return the title as its reference" do + @bucket.name = "luke" + @bucket.type = "user" + @bucket.to_ref.should == "user[luke]" + end +end + +describe Puppet::TransBucket, " when generating a configuration" do + before do + @bottom = Puppet::TransBucket.new + @bottom.type = "fake" + @bottom.name = "bottom" + @bottomobj = Puppet::TransObject.new("bottom", "user") + @bottom.push @bottomobj + + @middle = Puppet::TransBucket.new + @middle.type = "fake" + @middle.name = "middle" + @middleobj = Puppet::TransObject.new("middle", "user") + @middle.push(@middleobj) + @middle.push(@bottom) + + @top = Puppet::TransBucket.new + @top.type = "fake" + @top.name = "top" + @topobj = Puppet::TransObject.new("top", "user") + @top.push(@topobj) + @top.push(@middle) + + @config = @top.to_configuration + + @users = %w{top middle bottom} + @fakes = %w{fake[bottom] fake[middle] fake[top]} + end + + it "should convert all transportable objects to RAL resources" do + @users.each do |name| + @config.vertices.find { |r| r.class.name == :user and r.title == name }.should be_instance_of(Puppet::Type.type(:user)) + end + end + + it "should convert all transportable buckets to RAL components" do + @fakes.each do |name| + @config.vertices.find { |r| r.class.name == :component and r.title == name }.should be_instance_of(Puppet::Type.type(:component)) + end + end + + it "should add all resources to the graph's resource table" do + @config.resource("fake[top]").should equal(@top) + end + + it "should finalize all resources" do + @config.vertices.each do |vertex| vertex.should be_finalized end + end + + it "should only call to_type on each resource once" do + @topobj.expects(:to_type) + @bottomobj.expects(:to_type) + @top.to_configuration + end + + after do + Puppet::Type.allclear + end +end + +describe Puppet::TransBucket, " when serializing" do + before do + @bucket = Puppet::TransBucket.new(%w{one two}) + @bucket.name = "one" + @bucket.type = "two" + end + + it "should be able to be dumped to yaml" do + proc { YAML.dump(@bucket) }.should_not raise_error + end + + it "should dump YAML that produces an equivalent object" do + result = YAML.dump(@bucket) + + newobj = YAML.load(result) + newobj.name.should == "one" + newobj.type.should == "two" + children = [] + newobj.each do |o| + children << o + end + children.should == %w{one two} + end +end diff --git a/spec/unit/other/transobject.rb b/spec/unit/other/transobject.rb new file mode 100755 index 000000000..07c9dc761 --- /dev/null +++ b/spec/unit/other/transobject.rb @@ -0,0 +1,116 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::TransObject, " when building its search path" do +end + +describe Puppet::TransObject, " when building its search path" do +end +#!/usr/bin/env ruby + +$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ + +require 'puppet' +require 'puppet/transportable' +require 'puppettest' +require 'puppettest/parsertesting' +require 'yaml' + +class TestTransportable < Test::Unit::TestCase + include PuppetTest::ParserTesting + + def test_yamldumpobject + obj = mk_transobject + obj.to_yaml_properties + str = nil + assert_nothing_raised { + str = YAML.dump(obj) + } + + newobj = nil + assert_nothing_raised { + newobj = YAML.load(str) + } + + assert(newobj.name, "Object has no name") + assert(newobj.type, "Object has no type") + end + + def test_yamldumpbucket + objects = %w{/etc/passwd /etc /tmp /var /dev}.collect { |d| + mk_transobject(d) + } + bucket = mk_transbucket(*objects) + str = nil + assert_nothing_raised { + str = YAML.dump(bucket) + } + + newobj = nil + assert_nothing_raised { + newobj = YAML.load(str) + } + + assert(newobj.name, "Bucket has no name") + assert(newobj.type, "Bucket has no type") + end + + # Verify that we correctly strip out collectable objects, since they should + # not be sent to the client. + def test_collectstrip + top = mk_transtree do |object, depth, width| + if width % 2 == 1 + object.collectable = true + end + end + + assert(top.flatten.find_all { |o| o.collectable }.length > 0, + "Could not find any collectable objects") + + # Now strip out the collectable objects + top.collectstrip! + + # And make sure they're actually gone + assert_equal(0, top.flatten.find_all { |o| o.collectable }.length, + "Still found collectable objects") + end + + # Make sure our 'delve' command is working + def test_delve + top = mk_transtree do |object, depth, width| + if width % 2 == 1 + object.collectable = true + end + end + + objects = [] + buckets = [] + collectable = [] + + count = 0 + assert_nothing_raised { + top.delve do |object| + count += 1 + if object.is_a? Puppet::TransBucket + buckets << object + else + objects << object + if object.collectable + collectable << object + end + end + end + } + + top.flatten.each do |obj| + assert(objects.include?(obj), "Missing obj %s[%s]" % [obj.type, obj.name]) + end + + assert_equal(collectable.length, + top.flatten.find_all { |o| o.collectable }.length, + "Found incorrect number of collectable objects") + end +end + +# $Id$ diff --git a/spec/unit/parser/interpreter.rb b/spec/unit/parser/interpreter.rb index c0f9d54b3..a79267b52 100755 --- a/spec/unit/parser/interpreter.rb +++ b/spec/unit/parser/interpreter.rb @@ -77,8 +77,8 @@ describe Puppet::Parser::Interpreter, " when creating parser instances" do file = mock 'file' file.stubs(:changed?).returns(true) file.stubs(:file).returns("/whatever") - Puppet.config.stubs(:read_file).with(file).returns(text) - Puppet.config.parse(file) + Puppet.settings.stubs(:read_file).with(file).returns(text) + Puppet.settings.parse(file) parser1 = mock 'parser1' Puppet::Parser::Parser.expects(:new).with(:environment => :env1).returns(parser1) diff --git a/spec/unit/ral/type.rb b/spec/unit/ral/type.rb new file mode 100755 index 000000000..c8bf8c9b4 --- /dev/null +++ b/spec/unit/ral/type.rb @@ -0,0 +1,25 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::Type, " when in a configuration" do + before do + @configuration = Puppet::Node::Configuration.new + @container = Puppet::Type.type(:component).create(:name => "container") + @one = Puppet::Type.type(:file).create(:path => "/file/one") + @two = Puppet::Type.type(:file).create(:path => "/file/two") + @configuration.add_resource @container + @configuration.add_resource @one + @configuration.add_resource @two + @configuration.add_edge! @container, @one + @configuration.add_edge! @container, @two + end + + it "should have no parent if there is no in edge" do + @container.parent.should be_nil + end + + it "should set its parent to its in edge" do + @one.parent.ref.should equal(@container.ref) + end +end diff --git a/spec/unit/util/config.rb b/spec/unit/util/config.rb deleted file mode 100755 index 348a54893..000000000 --- a/spec/unit/util/config.rb +++ /dev/null @@ -1,408 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../../spec_helper' - -describe Puppet::Util::Config, " when specifying defaults" do - before do - @config = Puppet::Util::Config.new - end - - it "should start with no defined parameters" do - @config.params.length.should == 0 - end - - it "should allow specification of default values associated with a section as an array" do - @config.setdefaults(:section, :myvalue => ["defaultval", "my description"]) - end - - it "should not allow duplicate parameter specifications" do - @config.setdefaults(:section, :myvalue => ["a", "b"]) - lambda { @config.setdefaults(:section, :myvalue => ["c", "d"]) }.should raise_error(ArgumentError) - end - - it "should allow specification of default values associated with a section as a hash" do - @config.setdefaults(:section, :myvalue => {:default => "defaultval", :desc => "my description"}) - end - - it "should consider defined parameters to be valid" do - @config.setdefaults(:section, :myvalue => ["defaultval", "my description"]) - @config.valid?(:myvalue).should be_true - end - - it "should require a description when defaults are specified with an array" do - lambda { @config.setdefaults(:section, :myvalue => ["a value"]) }.should raise_error(ArgumentError) - end - - it "should require a description when defaults are specified with a hash" do - lambda { @config.setdefaults(:section, :myvalue => {:default => "a value"}) }.should raise_error(ArgumentError) - end - - it "should support specifying owner, group, and mode when specifying files" do - @config.setdefaults(:section, :myvalue => {:default => "/some/file", :owner => "blah", :mode => "boo", :group => "yay", :desc => "whatever"}) - end - - it "should support specifying a short name" do - @config.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) - end - - it "should fail when short names conflict" do - @config.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) - lambda { @config.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) }.should raise_error(ArgumentError) - end -end - -describe Puppet::Util::Config, " when setting values" do - before do - @config = Puppet::Util::Config.new - @config.setdefaults :main, :myval => ["val", "desc"] - @config.setdefaults :main, :bool => [true, "desc"] - end - - it "should provide a method for setting values from other objects" do - @config[:myval] = "something else" - @config[:myval].should == "something else" - end - - it "should support a getopt-specific mechanism for setting values" do - @config.handlearg("--myval", "newval") - @config[:myval].should == "newval" - end - - it "should support a getopt-specific mechanism for turning booleans off" do - @config.handlearg("--no-bool") - @config[:bool].should == false - end - - it "should support a getopt-specific mechanism for turning booleans on" do - # Turn it off first - @config[:bool] = false - @config.handlearg("--bool") - @config[:bool].should == true - end - - it "should clear the cache when setting getopt-specific values" do - @config.setdefaults :mysection, :one => ["whah", "yay"], :two => ["$one yay", "bah"] - @config[:two].should == "whah yay" - @config.handlearg("--one", "else") - @config[:two].should == "else yay" - end - - it "should not clear other values when setting getopt-specific values" do - @config[:myval] = "yay" - @config.handlearg("--no-bool") - @config[:myval].should == "yay" - end - - it "should call passed blocks when values are set" do - values = [] - @config.setdefaults(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }}) - values.should == [] - - @config[:hooker] = "something" - values.should == %w{something} - end - - it "should munge values using the element-specific methods" do - @config[:bool] = "false" - @config[:bool].should == false - end - - it "should prefer cli values to values set in Ruby code" do - @config.handlearg("--myval", "cliarg") - @config[:myval] = "memarg" - @config[:myval].should == "cliarg" - end -end - -describe Puppet::Util::Config, " when returning values" do - before do - @config = Puppet::Util::Config.new - @config.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"], :four => ["$two $three FOUR", "d"] - end - - it "should provide a mechanism for returning set values" do - @config[:one] = "other" - @config[:one].should == "other" - end - - it "should interpolate default values for other parameters into returned parameter values" do - @config[:one].should == "ONE" - @config[:two].should == "ONE TWO" - @config[:three].should == "ONE ONE TWO THREE" - end - - it "should interpolate default values that themselves need to be interpolated" do - @config[:four].should == "ONE TWO ONE ONE TWO THREE FOUR" - end - - it "should interpolate set values for other parameters into returned parameter values" do - @config[:one] = "on3" - @config[:two] = "$one tw0" - @config[:three] = "$one $two thr33" - @config[:four] = "$one $two $three f0ur" - @config[:one].should == "on3" - @config[:two].should == "on3 tw0" - @config[:three].should == "on3 on3 tw0 thr33" - @config[:four].should == "on3 on3 tw0 on3 on3 tw0 thr33 f0ur" - end - - it "should not cache interpolated values such that stale information is returned" do - @config[:two].should == "ONE TWO" - @config[:one] = "one" - @config[:two].should == "one TWO" - end - - it "should not cache values such that information from one environment is returned for another environment" do - text = "[env1]\none = oneval\n[env2]\none = twoval\n" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/whatever") - @config.stubs(:read_file).with(file).returns(text) - @config.parse(file) - - @config.value(:one, "env1").should == "oneval" - @config.value(:one, "env2").should == "twoval" - end - - it "should have a name determined by the 'name' parameter" do - @config.setdefaults(:whatever, :name => ["something", "yayness"]) - @config.name.should == :something - @config[:name] = :other - @config.name.should == :other - end -end - -describe Puppet::Util::Config, " when choosing which value to return" do - before do - @config = Puppet::Util::Config.new - @config.setdefaults :section, - :one => ["ONE", "a"], - :name => ["myname", "w"] - end - - it "should return default values if no values have been set" do - @config[:one].should == "ONE" - end - - it "should return values set on the cli before values set in the configuration file" do - text = "[main]\none = fileval\n" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/whatever") - @config.stubs(:parse_file).returns(text) - @config.handlearg("--one", "clival") - @config.parse(file) - - @config[:one].should == "clival" - end - - it "should return values set on the cli before values set in Ruby" do - @config[:one] = "rubyval" - @config.handlearg("--one", "clival") - @config[:one].should == "clival" - end - - it "should return values set in the executable-specific section before values set in the main section" do - text = "[main]\none = mainval\n[myname]\none = nameval\n" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/whatever") - @config.stubs(:read_file).with(file).returns(text) - @config.parse(file) - - @config[:one].should == "nameval" - end - - it "should not return values outside of its search path" do - text = "[other]\none = oval\n" - file = "/some/file" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/whatever") - @config.stubs(:read_file).with(file).returns(text) - @config.parse(file) - @config[:one].should == "ONE" - end - - it "should return values in a specified environment" do - text = "[env]\none = envval\n" - file = "/some/file" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/whatever") - @config.stubs(:read_file).with(file).returns(text) - @config.parse(file) - @config.value(:one, "env").should == "envval" - end - - it "should return values in a specified environment before values in the main or name sections" do - text = "[env]\none = envval\n[main]\none = mainval\n[myname]\none = nameval\n" - file = "/some/file" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/whatever") - @config.stubs(:read_file).with(file).returns(text) - @config.parse(file) - @config.value(:one, "env").should == "envval" - end -end - -describe Puppet::Util::Config, " when parsing its configuration" do - before do - @config = Puppet::Util::Config.new - @config.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"] - end - - it "should return values set in the configuration file" do - text = "[main] - one = fileval - " - file = "/some/file" - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - @config[:one].should == "fileval" - end - - #484 - this should probably be in the regression area - it "should not throw an exception on unknown parameters" do - text = "[main]\nnosuchparam = mval\n" - file = "/some/file" - @config.expects(:read_file).with(file).returns(text) - lambda { @config.parse(file) }.should_not raise_error - end - - it "should support an old parse method when per-executable configuration files still exist" do - # I'm not going to bother testing this method. - @config.should respond_to(:old_parse) - end - - it "should convert booleans in the configuration file into Ruby booleans" do - text = "[main] - one = true - two = false - " - file = "/some/file" - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - @config[:one].should == true - @config[:two].should == false - end - - it "should convert integers in the configuration file into Ruby Integers" do - text = "[main] - one = 65 - " - file = "/some/file" - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - @config[:one].should == 65 - end - - it "should support specifying file all metadata (owner, group, mode) in the configuration file" do - @config.setdefaults :section, :myfile => ["/my/file", "a"] - - text = "[main] - myfile = /other/file {owner = luke, group = luke, mode = 644} - " - file = "/some/file" - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - @config[:myfile].should == "/other/file" - @config.metadata(:myfile).should == {:owner => "luke", :group => "luke", :mode => "644"} - end - - it "should support specifying file a single piece of metadata (owner, group, or mode) in the configuration file" do - @config.setdefaults :section, :myfile => ["/my/file", "a"] - - text = "[main] - myfile = /other/file {owner = luke} - " - file = "/some/file" - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - @config[:myfile].should == "/other/file" - @config.metadata(:myfile).should == {:owner => "luke"} - end -end - -describe Puppet::Util::Config, " when reparsing its configuration" do - before do - @config = Puppet::Util::Config.new - @config.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"] - end - - it "should replace in-memory values with on-file values" do - # Init the value - text = "[main]\none = disk-init\n" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/test/file") - @config[:one] = "init" - @config.file = file - - # Now replace the value - text = "[main]\none = disk-replace\n" - - # This is kinda ridiculous - the reason it parses twice is that - # it goes to parse again when we ask for the value, because the - # mock always says it should get reparsed. - @config.expects(:read_file).with(file).returns(text).times(2) - @config.reparse - @config[:one].should == "disk-replace" - end - - it "should retain parameters set by cli when configuration files are reparsed" do - @config.handlearg("--one", "clival") - - text = "[main]\none = on-disk\n" - file = mock 'file' - file.stubs(:file).returns("/test/file") - @config.stubs(:read_file).with(file).returns(text) - @config.parse(file) - - @config[:one].should == "clival" - end - - it "should remove in-memory values that are no longer set in the file" do - # Init the value - text = "[main]\none = disk-init\n" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/test/file") - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - @config[:one].should == "disk-init" - - # Now replace the value - text = "[main]\ntwo = disk-replace\n" - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - #@config.reparse - - # The originally-overridden value should be replaced with the default - @config[:one].should == "ONE" - - # and we should now have the new value in memory - @config[:two].should == "disk-replace" - end -end - -#describe Puppet::Util::Config, " when being used to manage the host machine" do -# it "should provide a method that writes files with the correct modes" -# -# it "should provide a method that creates directories with the correct modes" -# -# it "should provide a method to declare what directories should exist" -# -# it "should provide a method to trigger enforcing of file modes on existing files and directories" -# -# it "should provide a method to convert the file mode enforcement into a Puppet manifest" -# -# it "should provide an option to create needed users and groups" -# -# it "should provide a method to print out the current configuration" -# -# it "should be able to provide all of its parameters in a format compatible with GetOpt::Long" -# -# it "should not attempt to manage files within /dev" -#end diff --git a/spec/unit/util/settings.rb b/spec/unit/util/settings.rb new file mode 100755 index 000000000..8d11737b3 --- /dev/null +++ b/spec/unit/util/settings.rb @@ -0,0 +1,535 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::Util::Settings, " when specifying defaults" do + before do + @settings = Puppet::Util::Settings.new + end + + it "should start with no defined parameters" do + @settings.params.length.should == 0 + end + + it "should allow specification of default values associated with a section as an array" do + @settings.setdefaults(:section, :myvalue => ["defaultval", "my description"]) + end + + it "should not allow duplicate parameter specifications" do + @settings.setdefaults(:section, :myvalue => ["a", "b"]) + lambda { @settings.setdefaults(:section, :myvalue => ["c", "d"]) }.should raise_error(ArgumentError) + end + + it "should allow specification of default values associated with a section as a hash" do + @settings.setdefaults(:section, :myvalue => {:default => "defaultval", :desc => "my description"}) + end + + it "should consider defined parameters to be valid" do + @settings.setdefaults(:section, :myvalue => ["defaultval", "my description"]) + @settings.valid?(:myvalue).should be_true + end + + it "should require a description when defaults are specified with an array" do + lambda { @settings.setdefaults(:section, :myvalue => ["a value"]) }.should raise_error(ArgumentError) + end + + it "should require a description when defaults are specified with a hash" do + lambda { @settings.setdefaults(:section, :myvalue => {:default => "a value"}) }.should raise_error(ArgumentError) + end + + it "should support specifying owner, group, and mode when specifying files" do + @settings.setdefaults(:section, :myvalue => {:default => "/some/file", :owner => "blah", :mode => "boo", :group => "yay", :desc => "whatever"}) + end + + it "should support specifying a short name" do + @settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) + end + + it "should fail when short names conflict" do + @settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) + lambda { @settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) }.should raise_error(ArgumentError) + end +end + +describe Puppet::Util::Settings, " when setting values" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :main, :myval => ["val", "desc"] + @settings.setdefaults :main, :bool => [true, "desc"] + end + + it "should provide a method for setting values from other objects" do + @settings[:myval] = "something else" + @settings[:myval].should == "something else" + end + + it "should support a getopt-specific mechanism for setting values" do + @settings.handlearg("--myval", "newval") + @settings[:myval].should == "newval" + end + + it "should support a getopt-specific mechanism for turning booleans off" do + @settings.handlearg("--no-bool") + @settings[:bool].should == false + end + + it "should support a getopt-specific mechanism for turning booleans on" do + # Turn it off first + @settings[:bool] = false + @settings.handlearg("--bool") + @settings[:bool].should == true + end + + it "should clear the cache when setting getopt-specific values" do + @settings.setdefaults :mysection, :one => ["whah", "yay"], :two => ["$one yay", "bah"] + @settings[:two].should == "whah yay" + @settings.handlearg("--one", "else") + @settings[:two].should == "else yay" + end + + it "should not clear other values when setting getopt-specific values" do + @settings[:myval] = "yay" + @settings.handlearg("--no-bool") + @settings[:myval].should == "yay" + end + + it "should call passed blocks when values are set" do + values = [] + @settings.setdefaults(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }}) + values.should == [] + + @settings[:hooker] = "something" + values.should == %w{something} + end + + it "should munge values using the element-specific methods" do + @settings[:bool] = "false" + @settings[:bool].should == false + end + + it "should prefer cli values to values set in Ruby code" do + @settings.handlearg("--myval", "cliarg") + @settings[:myval] = "memarg" + @settings[:myval].should == "cliarg" + end +end + +describe Puppet::Util::Settings, " when returning values" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"], :four => ["$two $three FOUR", "d"] + end + + it "should provide a mechanism for returning set values" do + @settings[:one] = "other" + @settings[:one].should == "other" + end + + it "should interpolate default values for other parameters into returned parameter values" do + @settings[:one].should == "ONE" + @settings[:two].should == "ONE TWO" + @settings[:three].should == "ONE ONE TWO THREE" + end + + it "should interpolate default values that themselves need to be interpolated" do + @settings[:four].should == "ONE TWO ONE ONE TWO THREE FOUR" + end + + it "should interpolate set values for other parameters into returned parameter values" do + @settings[:one] = "on3" + @settings[:two] = "$one tw0" + @settings[:three] = "$one $two thr33" + @settings[:four] = "$one $two $three f0ur" + @settings[:one].should == "on3" + @settings[:two].should == "on3 tw0" + @settings[:three].should == "on3 on3 tw0 thr33" + @settings[:four].should == "on3 on3 tw0 on3 on3 tw0 thr33 f0ur" + end + + it "should not cache interpolated values such that stale information is returned" do + @settings[:two].should == "ONE TWO" + @settings[:one] = "one" + @settings[:two].should == "one TWO" + end + + it "should not cache values such that information from one environment is returned for another environment" do + text = "[env1]\none = oneval\n[env2]\none = twoval\n" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/whatever") + @settings.stubs(:read_file).with(file).returns(text) + @settings.parse(file) + + @settings.value(:one, "env1").should == "oneval" + @settings.value(:one, "env2").should == "twoval" + end + + it "should have a name determined by the 'name' parameter" do + @settings.setdefaults(:whatever, :name => ["something", "yayness"]) + @settings.name.should == :something + @settings[:name] = :other + @settings.name.should == :other + end +end + +describe Puppet::Util::Settings, " when choosing which value to return" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :section, + :one => ["ONE", "a"], + :name => ["myname", "w"] + end + + it "should return default values if no values have been set" do + @settings[:one].should == "ONE" + end + + it "should return values set on the cli before values set in the configuration file" do + text = "[main]\none = fileval\n" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/whatever") + @settings.stubs(:parse_file).returns(text) + @settings.handlearg("--one", "clival") + @settings.parse(file) + + @settings[:one].should == "clival" + end + + it "should return values set on the cli before values set in Ruby" do + @settings[:one] = "rubyval" + @settings.handlearg("--one", "clival") + @settings[:one].should == "clival" + end + + it "should return values set in the executable-specific section before values set in the main section" do + text = "[main]\none = mainval\n[myname]\none = nameval\n" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/whatever") + @settings.stubs(:read_file).with(file).returns(text) + @settings.parse(file) + + @settings[:one].should == "nameval" + end + + it "should not return values outside of its search path" do + text = "[other]\none = oval\n" + file = "/some/file" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/whatever") + @settings.stubs(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:one].should == "ONE" + end + + it "should return values in a specified environment" do + text = "[env]\none = envval\n" + file = "/some/file" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/whatever") + @settings.stubs(:read_file).with(file).returns(text) + @settings.parse(file) + @settings.value(:one, "env").should == "envval" + end + + it "should return values in a specified environment before values in the main or name sections" do + text = "[env]\none = envval\n[main]\none = mainval\n[myname]\none = nameval\n" + file = "/some/file" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/whatever") + @settings.stubs(:read_file).with(file).returns(text) + @settings.parse(file) + @settings.value(:one, "env").should == "envval" + end +end + +describe Puppet::Util::Settings, " when parsing its configuration" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"] + end + + it "should return values set in the configuration file" do + text = "[main] + one = fileval + " + file = "/some/file" + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:one].should == "fileval" + end + + #484 - this should probably be in the regression area + it "should not throw an exception on unknown parameters" do + text = "[main]\nnosuchparam = mval\n" + file = "/some/file" + @settings.expects(:read_file).with(file).returns(text) + lambda { @settings.parse(file) }.should_not raise_error + end + + it "should support an old parse method when per-executable configuration files still exist" do + # I'm not going to bother testing this method. + @settings.should respond_to(:old_parse) + end + + it "should convert booleans in the configuration file into Ruby booleans" do + text = "[main] + one = true + two = false + " + file = "/some/file" + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:one].should == true + @settings[:two].should == false + end + + it "should convert integers in the configuration file into Ruby Integers" do + text = "[main] + one = 65 + " + file = "/some/file" + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:one].should == 65 + end + + it "should support specifying file all metadata (owner, group, mode) in the configuration file" do + @settings.setdefaults :section, :myfile => ["/myfile", "a"] + + text = "[main] + myfile = /other/file {owner = luke, group = luke, mode = 644} + " + file = "/some/file" + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:myfile].should == "/other/file" + @settings.metadata(:myfile).should == {:owner => "luke", :group => "luke", :mode => "644"} + end + + it "should support specifying file a single piece of metadata (owner, group, or mode) in the configuration file" do + @settings.setdefaults :section, :myfile => ["/myfile", "a"] + + text = "[main] + myfile = /other/file {owner = luke} + " + file = "/some/file" + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:myfile].should == "/other/file" + @settings.metadata(:myfile).should == {:owner => "luke"} + end +end + +describe Puppet::Util::Settings, " when reparsing its configuration" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"] + end + + it "should replace in-memory values with on-file values" do + # Init the value + text = "[main]\none = disk-init\n" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/test/file") + @settings[:one] = "init" + @settings.file = file + + # Now replace the value + text = "[main]\none = disk-replace\n" + + # This is kinda ridiculous - the reason it parses twice is that + # it goes to parse again when we ask for the value, because the + # mock always says it should get reparsed. + @settings.expects(:read_file).with(file).returns(text).times(2) + @settings.reparse + @settings[:one].should == "disk-replace" + end + + it "should retain parameters set by cli when configuration files are reparsed" do + @settings.handlearg("--one", "clival") + + text = "[main]\none = on-disk\n" + file = mock 'file' + file.stubs(:file).returns("/test/file") + @settings.stubs(:read_file).with(file).returns(text) + @settings.parse(file) + + @settings[:one].should == "clival" + end + + it "should remove in-memory values that are no longer set in the file" do + # Init the value + text = "[main]\none = disk-init\n" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/test/file") + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:one].should == "disk-init" + + # Now replace the value + text = "[main]\ntwo = disk-replace\n" + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + #@settings.reparse + + # The originally-overridden value should be replaced with the default + @settings[:one].should == "ONE" + + # and we should now have the new value in memory + @settings[:two].should == "disk-replace" + end +end + +describe Puppet::Util::Settings, " when being used to manage the host machine" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :main, :maindir => ["/maindir", "a"], :seconddir => ["/seconddir", "a"] + @settings.setdefaults :other, :otherdir => {:default => "/otherdir", :desc => "a", :owner => "luke", :group => "johnny", :mode => 0755} + @settings.setdefaults :files, :myfile => {:default => "/myfile", :desc => "a", :mode => 0755} + end + + it "should provide a method that writes files with the correct modes" do + pending "Not converted from test/unit yet" + end + + it "should provide a method that creates directories with the correct modes" do + Puppet::Util::SUIDManager.expects(:asuser).with("luke", "johnny").yields + Dir.expects(:mkdir).with("/otherdir", 0755) + @settings.mkdir(:otherdir) + end + + it "should be able to create needed directories in a single section" do + Dir.expects(:mkdir).with("/maindir") + Dir.expects(:mkdir).with("/seconddir") + @settings.use(:main) + end + + it "should be able to create needed directories in multiple sections" do + Dir.expects(:mkdir).with("/maindir") + Dir.expects(:mkdir).with("/otherdir", 0755) + Dir.expects(:mkdir).with("/seconddir") + @settings.use(:main, :other) + end + + it "should provide a method to trigger enforcing of file modes on existing files and directories" do + pending "Not converted from test/unit yet" + end + + it "should provide a method to convert the file mode enforcement into a Puppet manifest" do + pending "Not converted from test/unit yet" + end + + it "should create files when configured to do so with the :create parameter" + + it "should provide a method to convert the file mode enforcement into transportable resources" do + # Make it think we're root so it tries to manage user and group. + Puppet.features.stubs(:root?).returns(true) + File.stubs(:exist?).with("/myfile").returns(true) + trans = nil + trans = @settings.to_transportable + resources = [] + trans.delve { |obj| resources << obj if obj.is_a? Puppet::TransObject } + %w{/maindir /seconddir /otherdir /myfile}.each do |path| + obj = resources.find { |r| r.type == "file" and r.name == path } + if path.include?("dir") + obj[:ensure].should == :directory + else + # Do not create the file, just manage mode + obj[:ensure].should be_nil + end + obj.should be_instance_of(Puppet::TransObject) + case path + when "/otherdir": + obj[:owner].should == "luke" + obj[:group].should == "johnny" + obj[:mode].should == 0755 + when "/myfile": + obj[:mode].should == 0755 + end + end + end + + it "should not try to manage user or group when not running as root" do + Puppet.features.stubs(:root?).returns(false) + trans = nil + trans = @settings.to_transportable(:other) + trans.delve do |obj| + next unless obj.is_a?(Puppet::TransObject) + obj[:owner].should be_nil + obj[:group].should be_nil + end + end + + it "should add needed users and groups to the manifest when asked" do + # This is how we enable user/group management + @settings.setdefaults :main, :mkusers => [true, "w"] + Puppet.features.stubs(:root?).returns(false) + trans = nil + trans = @settings.to_transportable(:other) + resources = [] + trans.delve { |obj| resources << obj if obj.is_a? Puppet::TransObject and obj.type != "file" } + + user = resources.find { |r| r.type == "user" } + user.should be_instance_of(Puppet::TransObject) + user.name.should == "luke" + user[:ensure].should == :present + + # This should maybe be a separate test, but... + group = resources.find { |r| r.type == "group" } + group.should be_instance_of(Puppet::TransObject) + group.name.should == "johnny" + group[:ensure].should == :present + end + + it "should ignore tags and schedules when creating files and directories" + + it "should apply all resources in debug mode to reduce logging" + + it "should not try to manage absent files" do + # Make it think we're root so it tries to manage user and group. + Puppet.features.stubs(:root?).returns(true) + trans = nil + trans = @settings.to_transportable + file = nil + trans.delve { |obj| file = obj if obj.name == "/myfile" } + file.should be_nil + end + + it "should be able to turn the current configuration into a parseable manifest" + + it "should convert octal numbers correctly when producing a manifest" + + it "should be able to provide all of its parameters in a format compatible with GetOpt::Long" do + pending "Not converted from test/unit yet" + end + + it "should not attempt to manage files within /dev" do + pending "Not converted from test/unit yet" + end + + it "should not modify the stored state database when managing resources" do + Puppet::Util::Storage.expects(:store).never + Puppet::Util::Storage.expects(:load).never + Dir.expects(:mkdir).with("/maindir") + @settings.use(:main) + end + + it "should convert all relative paths to fully-qualified paths (#795)" do + @settings[:myfile] = "unqualified" + dir = Dir.getwd + @settings[:myfile].should == File.join(dir, "unqualified") + end + + it "should support a method for re-using all currently used sections" do + Dir.expects(:mkdir).with(@settings[:otherdir], 0755).times(2) + @settings.use(:other) + @settings.reuse + end +end |
