diff options
author | James Turnbull <james@lovedthanlost.net> | 2008-05-20 19:06:45 +1000 |
---|---|---|
committer | James Turnbull <james@lovedthanlost.net> | 2008-05-20 19:06:45 +1000 |
commit | 84a787a2a764a5035f7cbb8d30f94fc601bed154 (patch) | |
tree | ec46287a86ce738fec5774347ea4f9afcf804953 | |
parent | 390db8061ae81174ee9d42e26a6ebfe7182ea529 (diff) | |
parent | ee4be4f78f7c904dbe5873ff7b44993d1440da41 (diff) | |
download | puppet-84a787a2a764a5035f7cbb8d30f94fc601bed154.tar.gz puppet-84a787a2a764a5035f7cbb8d30f94fc601bed154.tar.xz puppet-84a787a2a764a5035f7cbb8d30f94fc601bed154.zip |
Merge branch '0.24.x' of git://github.com/lak/puppet into 0.24.x
Conflicts:
CHANGELOG
25 files changed, 864 insertions, 350 deletions
@@ -2,6 +2,10 @@ Created a Confiner module for the Provider class methods, enhanced the interface between it and the Confine class to make sure binary paths are searched for fresh each time. + + Modified the 'factpath' setting to automatically configure + Facter to load facts there if a new enough version of + Facter is used. Crontab provider: fix a parse error when a line begins with a space character (fixes #1216) diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index df2fb9425..57299b7e7 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -505,9 +505,11 @@ module Puppet # Central fact information. self.setdefaults(:main, - :factpath => ["$vardir/facts", - "Where Puppet should look for facts. Multiple directories should - be colon-separated, like normal PATH variables."], + :factpath => {:default => "$vardir/facts", + :desc => "Where Puppet should look for facts. Multiple directories should + be colon-separated, like normal PATH variables.", + :call_on_define => true, # Call our hook with the default value, so we always get the value added to facter. + :hook => proc { |value| Facter.search(value) if Facter.respond_to?(:search) }}, :factdest => ["$vardir/facts", "Where Puppet should store facts that it pulls down from the central server."], diff --git a/lib/puppet/provider/confine.rb b/lib/puppet/provider/confine.rb index 227c923e6..35b80fdcf 100644 --- a/lib/puppet/provider/confine.rb +++ b/lib/puppet/provider/confine.rb @@ -5,85 +5,73 @@ require 'puppet/util' class Puppet::Provider::Confine include Puppet::Util - attr_reader :test, :values, :fact + @tests = {} - # Mark that this confine is used for testing binary existence. - attr_accessor :for_binary - def for_binary? - for_binary + class << self + attr_accessor :name end - def exists?(value) - if for_binary? - return false unless value = binary(value) - end - value and FileTest.exist?(value) - end + def self.inherited(klass) + name = klass.to_s.split("::").pop.downcase.to_sym + raise "Test %s is already defined" % name if @tests.include?(name) + + klass.name = name - # Are we a facter comparison? - def facter? - defined?(@facter) + @tests[name] = klass end - # Retrieve the value from facter - def facter_value - unless defined?(@facter_value) and @facter_value - @facter_value = Facter.value(@fact).to_s.downcase + def self.test(name) + unless @tests[name] + begin + require "puppet/provider/confine/%s" % name + rescue LoadError => detail + unless detail.to_s.include?("no such file") + warn "Could not load confine test '%s': %s" % [name, detail] + end + # Could not find file + end end - @facter_value + return @tests[name] end - def false?(value) - ! value + attr_reader :values + + # Mark that this confine is used for testing binary existence. + attr_accessor :for_binary + def for_binary? + for_binary end - def initialize(test, values) + def initialize(values) values = [values] unless values.is_a?(Array) @values = values - - if %w{exists false true}.include?(test.to_s) - @test = test - @method = @test.to_s + "?" - else - @fact = test - @test = :facter - @method = "match?" - end end - def match?(value) - facter_value == value.to_s.downcase + # Provide a hook for the message when there's a failure. + def message(value) + "" end # Collect the results of all of them. def result - values.collect { |value| send(@method, value) } - end - - def true?(value) - # Double negate, so we only get true or false. - ! ! value + values.collect { |value| pass?(value) } end # Test whether our confine matches. def valid? values.each do |value| - unless send(@method, value) - msg = case test - when :false: "false value when expecting true" - when :true: "true value when expecting false" - when :exists: "file %s does not exist" % value - when :facter: "facter value '%s' for '%s' not in required list '%s'" % [value, @fact, values.join(",")] - end - Puppet.debug msg + unless pass?(value) + Puppet.debug message(value) return false end end return true ensure - # Reset the cache. We want to cache it during a given - # run, but across runs. - @facter_value = nil + reset + end + + # Provide a hook for subclasses. + def reset end end diff --git a/lib/puppet/provider/confine/exists.rb b/lib/puppet/provider/confine/exists.rb new file mode 100644 index 000000000..1d1ed8c84 --- /dev/null +++ b/lib/puppet/provider/confine/exists.rb @@ -0,0 +1,22 @@ +require 'puppet/provider/confine' + +class Puppet::Provider::Confine::Exists < Puppet::Provider::Confine + def self.summarize(confines) + confines.inject([]) { |total, confine| total + confine.summary } + end + + def pass?(value) + if for_binary? + return false unless value = binary(value) + end + value and FileTest.exist?(value) + end + + def message(value) + "file %s does not exist" % value + end + + def summary + result.zip(values).inject([]) { |array, args| val, f = args; array << f unless val; array } + end +end diff --git a/lib/puppet/provider/confine/facter.rb b/lib/puppet/provider/confine/facter.rb new file mode 100644 index 000000000..9bb66c058 --- /dev/null +++ b/lib/puppet/provider/confine/facter.rb @@ -0,0 +1,37 @@ +require 'puppet/provider/confine' + +class Puppet::Provider::Confine::Facter < Puppet::Provider::Confine + def self.summarize(confines) + result = Hash.new { |hash, key| hash[key] = [] } + confines.inject(result) { |total, confine| total[confine.fact] += confine.values unless confine.valid?; total } + end + + attr_accessor :fact + + # Are we a facter comparison? + def facter? + defined?(@facter) + end + + # Retrieve the value from facter + def facter_value + unless defined?(@facter_value) and @facter_value + @facter_value = ::Facter.value(@fact).to_s.downcase + end + @facter_value + end + + def message(value) + "facter value '%s' for '%s' not in required list '%s'" % [value, self.fact, values.join(",")] + end + + def pass?(value) + facter_value == value.to_s.downcase + end + + def reset + # Reset the cache. We want to cache it during a given + # run, but across runs. + @facter_value = nil + end +end diff --git a/lib/puppet/provider/confine/false.rb b/lib/puppet/provider/confine/false.rb new file mode 100644 index 000000000..b5b2b51c8 --- /dev/null +++ b/lib/puppet/provider/confine/false.rb @@ -0,0 +1,19 @@ +require 'puppet/provider/confine' + +class Puppet::Provider::Confine::False < Puppet::Provider::Confine + def self.summarize(confines) + confines.inject(0) { |count, confine| count + confine.summary } + end + + def pass?(value) + ! value + end + + def message(value) + "true value when expecting false" + end + + def summary + result.find_all { |v| v == false }.length + end +end diff --git a/lib/puppet/provider/confine/feature.rb b/lib/puppet/provider/confine/feature.rb new file mode 100644 index 000000000..1d92b001a --- /dev/null +++ b/lib/puppet/provider/confine/feature.rb @@ -0,0 +1,17 @@ +require 'puppet/provider/confine' + +class Puppet::Provider::Confine::Feature < Puppet::Provider::Confine + def self.summarize(confines) + confines.collect { |c| c.values }.flatten.uniq.find_all { |value| ! confines[0].pass?(value) } + end + + # Is the named feature available? + def pass?(value) + Puppet.features.send(value.to_s + "?") + end + + def message(value) + "feature %s is missing" % value + end +end + diff --git a/lib/puppet/provider/confine/true.rb b/lib/puppet/provider/confine/true.rb new file mode 100644 index 000000000..86b3b144f --- /dev/null +++ b/lib/puppet/provider/confine/true.rb @@ -0,0 +1,20 @@ +require 'puppet/provider/confine' + +class Puppet::Provider::Confine::True < Puppet::Provider::Confine + def self.summarize(confines) + confines.inject(0) { |count, confine| count + confine.summary } + end + + def pass?(value) + # Double negate, so we only get true or false. + ! ! value + end + + def message(value) + "false value when expecting true" + end + + def summary + result.find_all { |v| v == true }.length + end +end diff --git a/lib/puppet/provider/confine_collection.rb b/lib/puppet/provider/confine_collection.rb index f38035521..0c80086c9 100644 --- a/lib/puppet/provider/confine_collection.rb +++ b/lib/puppet/provider/confine_collection.rb @@ -11,8 +11,14 @@ class Puppet::Provider::ConfineCollection for_binary = false end hash.each do |test, values| - @confines << Puppet::Provider::Confine.new(test, values) - @confines[-1].for_binary = true if for_binary + if klass = Puppet::Provider::Confine.test(test) + @confines << klass.new(values) + @confines[-1].for_binary = true if for_binary + else + confine = Puppet::Provider::Confine.test(:facter).new(values) + confine.fact = test + @confines << confine + end end end @@ -22,24 +28,17 @@ class Puppet::Provider::ConfineCollection # Return a hash of the whole confine set, used for the Provider # reference. - def result - defaults = { - :false => 0, - :true => 0, - :exists => [], - :facter => {} - } - missing = Hash.new { |hash, key| hash[key] = defaults[key] } - @confines.each do |confine| - case confine.test - when :false: missing[confine.test] += confine.result.find_all { |v| v == false }.length - when :true: missing[confine.test] += confine.result.find_all { |v| v == true }.length - when :exists: confine.result.zip(confine.values).each { |val, f| missing[:exists] << f unless val } - when :facter: missing[:facter][confine.fact] = confine.values if confine.result.include?(false) - end - end + def summary + confines = Hash.new { |hash, key| hash[key] = [] } + @confines.each { |confine| confines[confine.class] << confine } + result = {} + confines.each do |klass, list| + value = klass.summarize(list) + next if (value.respond_to?(:length) and value.length == 0) or (value == 0) + result[klass.name] = value - missing + end + result end def valid? diff --git a/lib/puppet/provider/confiner.rb b/lib/puppet/provider/confiner.rb index 3e406873b..4605523e8 100644 --- a/lib/puppet/provider/confiner.rb +++ b/lib/puppet/provider/confiner.rb @@ -15,6 +15,6 @@ module Puppet::Provider::Confiner # Check whether this implementation is suitable for our platform. def suitable?(short = true) return confine_collection.valid? if short - return confine_collection.result + return confine_collection.summary end end diff --git a/lib/puppet/reference/providers.rb b/lib/puppet/reference/providers.rb index da815ddf1..610c7550d 100644 --- a/lib/puppet/reference/providers.rb +++ b/lib/puppet/reference/providers.rb @@ -71,6 +71,8 @@ providers = Puppet::Util::Reference.newreference :providers, :title => "Provider details += " - Got %s true tests that should have been false\n" % values when :false: details += " - Got %s false tests that should have been true\n" % values + when :feature: + details += " - Missing features %s\n" % values.collect { |f| f.to_s }.join(",") end end notes << details diff --git a/lib/puppet/util/storage.rb b/lib/puppet/util/storage.rb index 9358a28e9..dc4e9cd71 100644 --- a/lib/puppet/util/storage.rb +++ b/lib/puppet/util/storage.rb @@ -6,6 +6,10 @@ class Puppet::Util::Storage include Singleton include Puppet::Util + def self.state + return @@state + end + def initialize self.class.load end diff --git a/lib/puppet/util/variables.rb b/lib/puppet/util/variables.rb deleted file mode 100644 index 1a78ef5c1..000000000 --- a/lib/puppet/util/variables.rb +++ /dev/null @@ -1,38 +0,0 @@ -module Puppet::Util::Variables - def inithooks - @instance_init_hooks.dup - end - - def initvars - return unless defined? @class_init_hooks - self.inithooks.each do |var, value| - if value.is_a?(Class) - instance_variable_set("@" + var.to_s, value.new) - else - instance_variable_set("@" + var.to_s, value) - end - end - end - - def instancevar(hash) - @instance_init_hooks ||= {} - - unless method_defined?(:initvars) - define_method(:initvars) do - self.class.inithooks.each do |var, value| - if value.is_a?(Class) - instance_variable_set("@" + var.to_s, value.new) - else - instance_variable_set("@" + var.to_s, value) - end - end - end - end - hash.each do |var, value| - raise("Already initializing %s" % var) if @instance_init_hooks[var] - - @instance_init_hooks[var] = value - end - end -end - diff --git a/spec/integration/defaults.rb b/spec/integration/defaults.rb new file mode 100755 index 000000000..b14a141fb --- /dev/null +++ b/spec/integration/defaults.rb @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../spec_helper' + +require 'puppet/defaults' + +describe "Puppet defaults" do + describe "when setting the :factpath" do + after { Puppet.settings.clear } + + it "should add the :factpath to Facter's search paths" do + Facter.expects(:search).with("/my/fact/path") + + Puppet.settings[:factpath] = "/my/fact/path" + end + end +end diff --git a/spec/integration/node/catalog.rb b/spec/integration/node/catalog.rb index ca14c2ea8..941d2cc6c 100755 --- a/spec/integration/node/catalog.rb +++ b/spec/integration/node/catalog.rb @@ -7,6 +7,12 @@ require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Node::Catalog do describe "when using the indirector" do + before do + # This is so the tests work w/out networking. + Facter.stubs(:to_hash).returns({"hostname" => "foo.domain.com"}) + Facter.stubs(:value).returns("eh") + end + after { Puppet::Node::Catalog.indirection.clear_cache } it "should be able to delegate to the :yaml terminus" do diff --git a/spec/unit/provider/confine.rb b/spec/unit/provider/confine.rb index bb2e751d6..6a9214e26 100755 --- a/spec/unit/provider/confine.rb +++ b/spec/unit/provider/confine.rb @@ -5,219 +5,61 @@ require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/provider/confine' describe Puppet::Provider::Confine do - it "should require a test" do - lambda { Puppet::Provider::Confine.new }.should raise_error(ArgumentError) - end - it "should require a value" do - lambda { Puppet::Provider::Confine.new(:exists) }.should raise_error(ArgumentError) - end - - it "should have a test" do - Puppet::Provider::Confine.new(:exists, "/some/file").test.should == :exists + lambda { Puppet::Provider::Confine.new() }.should raise_error(ArgumentError) end it "should always convert values to an array" do - Puppet::Provider::Confine.new(:exists, "/some/file").values.should be_instance_of(Array) + Puppet::Provider::Confine.new("/some/file").values.should be_instance_of(Array) end - it "should have an accessor for its fact" do - Puppet::Provider::Confine.new(:foo, :bar).should respond_to(:fact) + it "should have a 'true' test" do + Puppet::Provider::Confine.test(:true).should be_instance_of(Class) end - it "should be possible to mark the confine as a binary test" do - Puppet::Provider::Confine.new(:foo, :bar).should respond_to(:for_binary=) + it "should have a 'false' test" do + Puppet::Provider::Confine.test(:false).should be_instance_of(Class) end - it "should have a boolean method to indicate it's a binary confine" do - Puppet::Provider::Confine.new(:foo, :bar).should respond_to(:for_binary?) + it "should have a 'feature' test" do + Puppet::Provider::Confine.test(:feature).should be_instance_of(Class) end - it "should indicate it's a boolean confine if it has been marked that way" do - confine = Puppet::Provider::Confine.new(:foo, :bar) - confine.for_binary = true - confine.should be_for_binary + it "should have an 'exists' test" do + Puppet::Provider::Confine.test(:exists).should be_instance_of(Class) end - it "should have a method for returning a binary's path" do - Puppet::Provider::Confine.new(:foo, :bar).private_methods.should be_include("binary") - end - - describe "when testing values" do - before { @confine = Puppet::Provider::Confine.new("eh", "foo") } - - describe "and the test is 'false'" do - it "should use the 'false?' method to test validity" do - @confine = Puppet::Provider::Confine.new(:false, "foo") - @confine.expects(:false?).with("foo") - @confine.valid? - end - - it "should return true if the value is false" do - @confine.false?(false).should be_true - end - - it "should return false if the value is not false" do - @confine.false?("else").should be_false - end - - it "should log that a value is false" do - @confine = Puppet::Provider::Confine.new(:false, "foo") - Puppet.expects(:debug).with { |l| l.include?("false") } - @confine.valid? - end - end - - describe "and the test is 'true'" do - it "should use the 'true?' method to test validity" do - @confine = Puppet::Provider::Confine.new(:true, "foo") - @confine.expects(:true?).with("foo") - @confine.valid? - end - - it "should return true if the value is not false" do - @confine.true?("else").should be_true - end - - it "should return false if the value is false" do - @confine.true?(nil).should be_false - end - end - - describe "and the test is 'exists'" do - it "should use the 'exists?' method to test validity" do - @confine = Puppet::Provider::Confine.new(:exists, "foo") - @confine.expects(:exists?).with("foo") - @confine.valid? - end - - it "should return false if the value is false" do - @confine.exists?(false).should be_false - end - - it "should return false if the value does not point to a file" do - FileTest.expects(:exist?).with("/my/file").returns false - @confine.exists?("/my/file").should be_false - end - - it "should return true if the value points to a file" do - FileTest.expects(:exist?).with("/my/file").returns true - @confine.exists?("/my/file").should be_true - end - - it "should log that a value is true" do - @confine = Puppet::Provider::Confine.new(:true, nil) - Puppet.expects(:debug).with { |l| l.include?("true") } - @confine.valid? - end - - describe "and the confine is for binaries" do - before { @confine.stubs(:for_binary).returns true } - it "should use its 'binary' method to look up the full path of the file" do - @confine.expects(:binary).returns nil - @confine.exists?("/my/file") - end - - it "should return false if no binary can be found" do - @confine.expects(:binary).with("/my/file").returns nil - @confine.exists?("/my/file").should be_false - end - - it "should return true if the binary can be found and the file exists" do - @confine.expects(:binary).with("/my/file").returns "/my/file" - FileTest.expects(:exist?).with("/my/file").returns true - @confine.exists?("/my/file").should be_true - end - - it "should return false if the binary can be found but the file does not exist" do - @confine.expects(:binary).with("/my/file").returns "/my/file" - FileTest.expects(:exist?).with("/my/file").returns true - @confine.exists?("/my/file").should be_true - end - end - end - - describe "and the test is not 'true', 'false', or 'exists'" do - it "should use the 'match?' method to test validity" do - @confine = Puppet::Provider::Confine.new("yay", "foo") - @confine.expects(:match?).with("foo") - @confine.valid? - end - - it "should return true if the value matches the facter value" do - Facter.expects(:value).returns("foo") - - @confine.match?("foo").should be_true - end - - it "should return false if the value does not match the facter value" do - Facter.expects(:value).returns("boo") - - @confine.match?("foo").should be_false - end - - it "should be case insensitive" do - Facter.expects(:value).returns("FOO") - - @confine.match?("foo").should be_true - end - - it "should not care whether the value is a string or symbol" do - Facter.expects(:value).returns("FOO") - - @confine.match?(:foo).should be_true - end - - it "should cache the fact during testing" do - Facter.expects(:value).once.returns("FOO") - - @confine.match?(:foo) - @confine.match?(:foo) - end - - it "should log that the fact value is not correct" do - @confine = Puppet::Provider::Confine.new("foo", ["bar", "bee"]) - Facter.expects(:value).with("foo").returns "yayness" - Puppet.expects(:debug).with { |l| l.include?("facter") and l.include?("bar,bee") } - @confine.valid? - end - end + it "should have a 'facter' test" do + Puppet::Provider::Confine.test(:facter).should be_instance_of(Class) end describe "when testing all values" do - before { @confine = Puppet::Provider::Confine.new(:true, %w{a b c}) } + before { @confine = Puppet::Provider::Confine.new(%w{a b c}) } it "should be invalid if any values fail" do - @confine.stubs(:true?).returns true - @confine.expects(:true?).with("b").returns false + @confine.stubs(:pass?).returns true + @confine.expects(:pass?).with("b").returns false @confine.should_not be_valid end it "should be valid if all values pass" do - @confine.stubs(:true?).returns true + @confine.stubs(:pass?).returns true @confine.should be_valid end it "should short-cut at the first failing value" do - @confine.expects(:true?).once.returns false - @confine.valid? - end - - it "should remove the cached facter value" do - @confine = Puppet::Provider::Confine.new(:foo, :bar) - Facter.expects(:value).with(:foo).times(2).returns "eh" - @confine.valid? + @confine.expects(:pass?).once.returns false @confine.valid? end end describe "when testing the result of the values" do - before { @confine = Puppet::Provider::Confine.new(:true, %w{a b c d}) } + before { @confine = Puppet::Provider::Confine.new(%w{a b c d}) } it "should return an array with the result of the test for each value" do - @confine.stubs(:true?).returns true - @confine.expects(:true?).with("b").returns false - @confine.expects(:true?).with("d").returns false + @confine.stubs(:pass?).returns true + @confine.expects(:pass?).with("b").returns false + @confine.expects(:pass?).with("d").returns false @confine.result.should == [true, false, true, false] end diff --git a/spec/unit/provider/confine/exists.rb b/spec/unit/provider/confine/exists.rb new file mode 100755 index 000000000..1ab1d39f7 --- /dev/null +++ b/spec/unit/provider/confine/exists.rb @@ -0,0 +1,80 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/provider/confine/exists' + +describe Puppet::Provider::Confine::Exists do + before do + @confine = Puppet::Provider::Confine::Exists.new("/my/file") + end + + it "should be named :exists" do + Puppet::Provider::Confine::Exists.name.should == :exists + end + + it "should use the 'pass?' method to test validity" do + @confine.expects(:pass?).with("/my/file") + @confine.valid? + end + + it "should return false if the value is false" do + @confine.pass?(false).should be_false + end + + it "should return false if the value does not point to a file" do + FileTest.expects(:exist?).with("/my/file").returns false + @confine.pass?("/my/file").should be_false + end + + it "should return true if the value points to a file" do + FileTest.expects(:exist?).with("/my/file").returns true + @confine.pass?("/my/file").should be_true + end + + it "should produce a message saying that a file is missing" do + @confine.message("/my/file").should be_include("does not exist") + end + + describe "and the confine is for binaries" do + before { @confine.stubs(:for_binary).returns true } + it "should use its 'binary' method to look up the full path of the file" do + @confine.expects(:binary).returns nil + @confine.pass?("/my/file") + end + + it "should return false if no binary can be found" do + @confine.expects(:binary).with("/my/file").returns nil + @confine.pass?("/my/file").should be_false + end + + it "should return true if the binary can be found and the file exists" do + @confine.expects(:binary).with("/my/file").returns "/my/file" + FileTest.expects(:exist?).with("/my/file").returns true + @confine.pass?("/my/file").should be_true + end + + it "should return false if the binary can be found but the file does not exist" do + @confine.expects(:binary).with("/my/file").returns "/my/file" + FileTest.expects(:exist?).with("/my/file").returns true + @confine.pass?("/my/file").should be_true + end + end + + it "should produce a summary containing all missing files" do + FileTest.stubs(:exist?).returns true + FileTest.expects(:exist?).with("/two").returns false + FileTest.expects(:exist?).with("/four").returns false + + confine = Puppet::Provider::Confine::Exists.new %w{/one /two /three /four} + confine.summary.should == %w{/two /four} + end + + it "should summarize multiple instances by returning a flattened array of their summaries" do + c1 = mock '1', :summary => %w{one} + c2 = mock '2', :summary => %w{two} + c3 = mock '3', :summary => %w{three} + + Puppet::Provider::Confine::Exists.summarize([c1, c2, c3]).should == %w{one two three} + end +end diff --git a/spec/unit/provider/confine/facter.rb b/spec/unit/provider/confine/facter.rb new file mode 100755 index 000000000..560263257 --- /dev/null +++ b/spec/unit/provider/confine/facter.rb @@ -0,0 +1,86 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/provider/confine/facter' + +describe Puppet::Provider::Confine::Facter::Facter do + it "should be named :facter" do + Puppet::Provider::Confine::Facter.name.should == :facter + end + + it "should require a value" do + lambda { Puppet::Provider::Confine::Facter.new() }.should raise_error(ArgumentError) + end + + it "should always convert values to an array" do + Puppet::Provider::Confine::Facter.new("/some/file").values.should be_instance_of(Array) + end + + it "should have an accessor for its fact" do + Puppet::Provider::Confine::Facter.new(:bar).should respond_to(:fact) + end + + describe "when testing values" do + before { @confine = Puppet::Provider::Confine::Facter.new("foo") } + it "should use the 'pass?' method to test validity" do + @confine.expects(:pass?).with("foo") + @confine.valid? + end + + it "should return true if the value matches the facter value" do + Facter.expects(:value).returns("foo") + + @confine.pass?("foo").should be_true + end + + it "should return false if the value does not match the facter value" do + Facter.expects(:value).returns("boo") + + @confine.pass?("foo").should be_false + end + + it "should be case insensitive" do + Facter.expects(:value).returns("FOO") + + @confine.pass?("foo").should be_true + end + + it "should not care whether the value is a string or symbol" do + Facter.expects(:value).returns("FOO") + + @confine.pass?(:foo).should be_true + end + + it "should cache the fact during testing" do + Facter.expects(:value).once.returns("FOO") + + @confine.pass?(:foo) + @confine.pass?(:foo) + end + + it "should produce a message that the fact value is not correct" do + @confine = Puppet::Provider::Confine::Facter.new(%w{bar bee}) + message = @confine.message("value") + message.should be_include("facter") + message.should be_include("bar,bee") + end + end + + describe "when summarizing multiple instances" do + it "should return a hash of failing variables and their values" do + c1 = stub '1', :valid? => false, :values => %w{one}, :fact => "uno" + c2 = stub '2', :valid? => true, :values => %w{two}, :fact => "dos" + c3 = stub '3', :valid? => false, :values => %w{three}, :fact => "tres" + + Puppet::Provider::Confine::Facter.summarize([c1, c2, c3]).should == {"uno" => %w{one}, "tres" => %w{three}} + end + + it "should combine the values of multiple confines with the same fact" do + c1 = stub '1', :valid? => false, :values => %w{one}, :fact => "uno" + c2 = stub '2', :valid? => false, :values => %w{two}, :fact => "uno" + + Puppet::Provider::Confine::Facter.summarize([c1, c2]).should == {"uno" => %w{one two}} + end + end +end diff --git a/spec/unit/provider/confine/false.rb b/spec/unit/provider/confine/false.rb new file mode 100755 index 000000000..c6c43e391 --- /dev/null +++ b/spec/unit/provider/confine/false.rb @@ -0,0 +1,52 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/provider/confine/false' + +describe Puppet::Provider::Confine::False do + it "should be named :false" do + Puppet::Provider::Confine::False.name.should == :false + end + + it "should require a value" do + lambda { Puppet::Provider::Confine.new() }.should raise_error(ArgumentError) + end + + describe "when testing values" do + before { @confine = Puppet::Provider::Confine::False.new("foo") } + + it "should use the 'pass?' method to test validity" do + @confine = Puppet::Provider::Confine::False.new("foo") + @confine.expects(:pass?).with("foo") + @confine.valid? + end + + it "should return true if the value is false" do + @confine.pass?(false).should be_true + end + + it "should return false if the value is not false" do + @confine.pass?("else").should be_false + end + + it "should produce a message that a value is true" do + @confine = Puppet::Provider::Confine::False.new("foo") + @confine.message("eh").should be_include("true") + end + end + + it "should be able to produce a summary with the number of incorrectly true values" do + confine = Puppet::Provider::Confine::False.new %w{one two three four} + confine.expects(:pass?).times(4).returns(true).returns(false).returns(true).returns(false) + confine.summary.should == 2 + end + + it "should summarize multiple instances by summing their summaries" do + c1 = mock '1', :summary => 1 + c2 = mock '2', :summary => 2 + c3 = mock '3', :summary => 3 + + Puppet::Provider::Confine::False.summarize([c1, c2, c3]).should == 6 + end +end diff --git a/spec/unit/provider/confine/feature.rb b/spec/unit/provider/confine/feature.rb new file mode 100755 index 000000000..1845c9a47 --- /dev/null +++ b/spec/unit/provider/confine/feature.rb @@ -0,0 +1,59 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/provider/confine/feature' + +describe Puppet::Provider::Confine::Feature do + it "should be named :feature" do + Puppet::Provider::Confine::Feature.name.should == :feature + end + + it "should require a value" do + lambda { Puppet::Provider::Confine::Feature.new() }.should raise_error(ArgumentError) + end + + it "should always convert values to an array" do + Puppet::Provider::Confine::Feature.new("/some/file").values.should be_instance_of(Array) + end + + describe "when testing values" do + before do + @features = mock 'features' + Puppet.stubs(:features).returns @features + @confine = Puppet::Provider::Confine::Feature.new("myfeature") + end + + it "should use the Puppet features instance to test validity" do + @features.expects(:myfeature?) + @confine.valid? + end + + it "should return true if the feature is present" do + @features.expects(:myfeature?).returns true + @confine.pass?("myfeature").should be_true + end + + it "should return false if the value is false" do + @features.expects(:myfeature?).returns false + @confine.pass?("myfeature").should be_false + end + + it "should log that a feature is missing" do + @confine.message("myfeat").should be_include("missing") + end + end + + it "should summarize multiple instances by returning a flattened array of all missing features" do + confines = [] + confines << Puppet::Provider::Confine::Feature.new(%w{one two}) + confines << Puppet::Provider::Confine::Feature.new(%w{two}) + confines << Puppet::Provider::Confine::Feature.new(%w{three four}) + + features = mock 'feature' + features.stub_everything + Puppet.stubs(:features).returns features + + Puppet::Provider::Confine::Feature.summarize(confines).sort.should == %w{one two three four}.sort + end +end diff --git a/spec/unit/provider/confine/true.rb b/spec/unit/provider/confine/true.rb new file mode 100755 index 000000000..c9cc83c9e --- /dev/null +++ b/spec/unit/provider/confine/true.rb @@ -0,0 +1,50 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/provider/confine/true' + +describe Puppet::Provider::Confine::True do + it "should be named :true" do + Puppet::Provider::Confine::True.name.should == :true + end + + it "should require a value" do + lambda { Puppet::Provider::Confine::True.new() }.should raise_error(ArgumentError) + end + + describe "when testing values" do + before { @confine = Puppet::Provider::Confine::True.new("foo") } + + it "should use the 'pass?' method to test validity" do + @confine.expects(:pass?).with("foo") + @confine.valid? + end + + it "should return true if the value is not false" do + @confine.pass?("else").should be_true + end + + it "should return false if the value is false" do + @confine.pass?(nil).should be_false + end + + it "should produce the message that a value is false" do + @confine.message("eh").should be_include("false") + end + end + + it "should produce the number of false values when asked for a summary" do + @confine = Puppet::Provider::Confine::True.new %w{one two three four} + @confine.expects(:pass?).times(4).returns(true).returns(false).returns(true).returns(false) + @confine.summary.should == 2 + end + + it "should summarize multiple instances by summing their summaries" do + c1 = mock '1', :summary => 1 + c2 = mock '2', :summary => 2 + c3 = mock '3', :summary => 3 + + Puppet::Provider::Confine::True.summarize([c1, c2, c3]).should == 6 + end +end diff --git a/spec/unit/provider/confine_collection.rb b/spec/unit/provider/confine_collection.rb index 3430d604f..da4b3fe72 100755 --- a/spec/unit/provider/confine_collection.rb +++ b/spec/unit/provider/confine_collection.rb @@ -9,105 +9,114 @@ describe Puppet::Provider::ConfineCollection do Puppet::Provider::ConfineCollection.new.should respond_to(:confine) end - it "should create a Confine instance for every confine call" do - Puppet::Provider::Confine.expects(:new).with(:foo, :bar).returns "eh" - Puppet::Provider::Confine.expects(:new).with(:baz, :bee).returns "eh" - Puppet::Provider::ConfineCollection.new.confine :foo => :bar, :baz => :bee - end + describe "when creating confine instances" do + it "should create an instance of the named test with the provided values" do + test_class = mock 'test_class' + test_class.expects(:new).with(%w{my values}) + Puppet::Provider::Confine.expects(:test).with(:foo).returns test_class + + Puppet::Provider::ConfineCollection.new.confine :foo => %w{my values} + end + + describe "and the test cannot be found" do + before do + @facter = mock 'facter_test' + + Puppet::Provider::Confine.expects(:test).with(:foo).returns nil + Puppet::Provider::Confine.expects(:test).with(:facter).returns @facter + end + + it "should create a Facter test with the provided values and set the fact to the test name" do + confine = mock 'confine' + confine.expects(:fact=).with(:foo) + @facter.expects(:new).with(%w{my values}).returns confine + Puppet::Provider::ConfineCollection.new.confine :foo => %w{my values} + end + end - it "should mark each confine as a binary confine if :for_binary => true is included" do - confine = mock 'confine' - confine.expects(:for_binary=).with true - Puppet::Provider::Confine.expects(:new).with(:foo, :bar).returns confine - Puppet::Provider::ConfineCollection.new.confine :foo => :bar, :for_binary => true + describe "and the 'for_binary' option was provided" do + it "should mark the test as a binary confine" do + confine = mock 'confine' + confine.expects(:for_binary=).with true + Puppet::Provider::Confine.test(:exists).expects(:new).with(:bar).returns confine + Puppet::Provider::ConfineCollection.new.confine :exists => :bar, :for_binary => true + end + end end it "should be valid if no confines are present" do Puppet::Provider::ConfineCollection.new.should be_valid end - it "should be valid if all confines are valid" do + it "should be valid if all confines pass" do c1 = mock 'c1', :valid? => true c2 = mock 'c2', :valid? => true - Puppet::Provider::Confine.expects(:new).times(2).returns(c1).then.returns(c2) + Puppet::Provider::Confine.test(:true).expects(:new).returns(c1) + Puppet::Provider::Confine.test(:false).expects(:new).returns(c2) confiner = Puppet::Provider::ConfineCollection.new - confiner.confine :foo => :bar, :baz => :bee + confiner.confine :true => :bar, :false => :bee confiner.should be_valid end - it "should not be valid if any confines are valid" do - c1 = mock 'c1', :valid? => true - c2 = mock 'c2', :valid? => false + it "should not be valid if any confines fail" do + c1 = stub 'c1', :valid? => true + c2 = stub 'c2', :valid? => false - Puppet::Provider::Confine.expects(:new).times(2).returns(c1).then.returns(c2) + Puppet::Provider::Confine.test(:true).expects(:new).returns(c1) + Puppet::Provider::Confine.test(:false).expects(:new).returns(c2) confiner = Puppet::Provider::ConfineCollection.new - confiner.confine :foo => :bar, :baz => :bee + confiner.confine :true => :bar, :false => :bee confiner.should_not be_valid end - describe "when providing a complete result" do + describe "when providing a summary" do before do @confiner = Puppet::Provider::ConfineCollection.new end it "should return a hash" do - @confiner.result.should be_instance_of(Hash) + @confiner.summary.should be_instance_of(Hash) end it "should return an empty hash if the confiner is valid" do - @confiner.result.should == {} + @confiner.summary.should == {} end - it "should contain the number of incorrectly false values" do - c1 = stub 'c1', :result => [true, false, true], :test => :true - c2 = stub 'c2', :result => [false, true, false], :test => :true - - Puppet::Provider::Confine.expects(:new).times(2).returns(c1).then.returns(c2) - - confiner = Puppet::Provider::ConfineCollection.new - confiner.confine :foo => :bar, :baz => :bee + it "should add each test type's summary to the hash" do + @confiner.confine :true => :bar, :false => :bee + Puppet::Provider::Confine.test(:true).expects(:summarize).returns :tsumm + Puppet::Provider::Confine.test(:false).expects(:summarize).returns :fsumm - confiner.result[:true].should == 3 + @confiner.summary.should == {:true => :tsumm, :false => :fsumm} end - it "should contain the number of incorrectly true values" do - c1 = stub 'c1', :result => [true, false, true], :test => :false - c2 = stub 'c2', :result => [false, true, false], :test => :false + it "should not include tests that return 0" do + @confiner.confine :true => :bar, :false => :bee + Puppet::Provider::Confine.test(:true).expects(:summarize).returns 0 + Puppet::Provider::Confine.test(:false).expects(:summarize).returns :fsumm - Puppet::Provider::Confine.expects(:new).times(2).returns(c1).then.returns(c2) - - confiner = Puppet::Provider::ConfineCollection.new - confiner.confine :foo => :bar, :baz => :bee - - confiner.result[:false].should == 3 + @confiner.summary.should == {:false => :fsumm} end - it "should contain the missing files" do - FileTest.stubs(:exist?).returns true - FileTest.expects(:exist?).with("/two").returns false - FileTest.expects(:exist?).with("/four").returns false - - confiner = Puppet::Provider::ConfineCollection.new - confiner.confine :exists => %w{/one /two} - confiner.confine :exists => %w{/three /four} + it "should not include tests that return empty arrays" do + @confiner.confine :true => :bar, :false => :bee + Puppet::Provider::Confine.test(:true).expects(:summarize).returns [] + Puppet::Provider::Confine.test(:false).expects(:summarize).returns :fsumm - confiner.result[:exists].should == %w{/two /four} + @confiner.summary.should == {:false => :fsumm} end - it "should contain a hash of facts and the allowed values" do - Facter.expects(:value).with(:foo).returns "yay" - Facter.expects(:value).with(:bar).returns "boo" - confiner = Puppet::Provider::ConfineCollection.new - confiner.confine :foo => "yes", :bar => "boo" + it "should not include tests that return empty hashes" do + @confiner.confine :true => :bar, :false => :bee + Puppet::Provider::Confine.test(:true).expects(:summarize).returns({}) + Puppet::Provider::Confine.test(:false).expects(:summarize).returns :fsumm - result = confiner.result - result[:facter][:foo].should == %w{yes} - result[:facter][:bar].should be_nil + @confiner.summary.should == {:false => :fsumm} end end end diff --git a/spec/unit/provider/confiner.rb b/spec/unit/provider/confiner.rb index 38fffc102..078fc4420 100755 --- a/spec/unit/provider/confiner.rb +++ b/spec/unit/provider/confiner.rb @@ -54,8 +54,8 @@ describe Puppet::Provider::Confiner do @object.should_not be_suitable end - it "should return the result of the confine collection if a long result is asked for" do - @coll.expects(:result).returns "myresult" + it "should return the summary of the confine collection if a long result is asked for" do + @coll.expects(:summary).returns "myresult" @object.suitable?(false).should == "myresult" end end diff --git a/spec/unit/provider/interface/redhat.rb b/spec/unit/provider/interface/redhat.rb index 5a7a8dfcd..99ac50f01 100755 --- a/spec/unit/provider/interface/redhat.rb +++ b/spec/unit/provider/interface/redhat.rb @@ -9,12 +9,12 @@ provider_class = Puppet::Type.type(:interface).provider(:redhat) describe provider_class do it "should not be functional on systems without a network-scripts directory" do - FileTest.expects(:exists?).with("/etc/sysconfig/network-scripts").returns(false) + FileTest.expects(:exist?).with("/etc/sysconfig/network-scripts").returns(false) provider_class.should_not be_suitable end it "should be functional on systems with a network-scripts directory" do - FileTest.expects(:exists?).with("/etc/sysconfig/network-scripts").returns(true) + FileTest.expects(:exist?).with("/etc/sysconfig/network-scripts").returns(true) provider_class.should be_suitable end end diff --git a/spec/unit/util/storage.rb b/spec/unit/util/storage.rb new file mode 100755 index 000000000..55d1d1f61 --- /dev/null +++ b/spec/unit/util/storage.rb @@ -0,0 +1,237 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'yaml' +require 'tempfile' + +require 'puppet/util/storage' + +describe Puppet::Util::Storage do + before(:all) do + Puppet[:statedir] = Dir.tmpdir() + end + + before(:each) do + Puppet::Util::Storage.clear() + end + + describe "when caching a symbol" do + it "should return an empty hash" do + Puppet::Util::Storage.cache(:yayness).should == {} + Puppet::Util::Storage.cache(:more_yayness).should == {} + end + + it "should add the symbol to its internal state" do + Puppet::Util::Storage.cache(:yayness) + Puppet::Util::Storage.state().should == {:yayness=>{}} + end + + it "should not clobber existing state when caching additional objects" do + Puppet::Util::Storage.cache(:yayness) + Puppet::Util::Storage.state().should == {:yayness=>{}} + Puppet::Util::Storage.cache(:bubblyness) + Puppet::Util::Storage.state().should == {:yayness=>{},:bubblyness=>{}} + end + end + + describe "when caching a Puppet::Type" do + before(:all) do + @file_test = Puppet.type(:file).create(:name => "/yayness", :check => %w{checksum type}) + @exec_test = Puppet.type(:exec).create(:name => "/bin/ls /yayness") + end + + it "should return an empty hash" do + Puppet::Util::Storage.cache(@file_test).should == {} + Puppet::Util::Storage.cache(@exec_test).should == {} + end + + it "should add the resource ref to its internal state" do + Puppet::Util::Storage.state().should == {} + Puppet::Util::Storage.cache(@file_test) + Puppet::Util::Storage.state().should == {"File[/yayness]"=>{}} + Puppet::Util::Storage.cache(@exec_test) + Puppet::Util::Storage.state().should == {"File[/yayness]"=>{}, "Exec[/bin/ls /yayness]"=>{}} + end + end + + describe "when caching invalid objects" do + before(:all) do + @bogus_objects = [ {}, [], "foo", 42, nil, Tempfile.new('storage_test') ] + end + + it "should raise an ArgumentError" do + @bogus_objects.each do |object| + proc { Puppet::Util::Storage.cache(object) }.should raise_error() + end + end + + it "should not add anything to its internal state" do + @bogus_objects.each do |object| + begin + Puppet::Util::Storage.cache(object) + rescue + Puppet::Util::Storage.state().should == {} + end + end + end + end + + it "should clear its internal state when clear() is called" do + Puppet::Util::Storage.cache(:yayness) + Puppet::Util::Storage.state().should == {:yayness=>{}} + Puppet::Util::Storage.clear() + Puppet::Util::Storage.state().should == {} + end + + describe "when loading from the state file" do + describe "when the state file/directory does not exist" do + before(:each) do + transient = Tempfile.new('storage_test') + @path = transient.path() + transient.close!() + end + + it "should not fail to load()" do + FileTest.exists?(@path).should be_false() + Puppet[:statedir] = @path + proc { Puppet::Util::Storage.load() }.should_not raise_error() + Puppet[:statefile] = @path + proc { Puppet::Util::Storage.load() }.should_not raise_error() + end + + it "should not lose its internal state when load() is called" do + FileTest.exists?(@path).should be_false() + + Puppet::Util::Storage.cache(:yayness) + Puppet::Util::Storage.state().should == {:yayness=>{}} + + Puppet[:statefile] = @path + proc { Puppet::Util::Storage.load() }.should_not raise_error() + + Puppet::Util::Storage.state().should == {:yayness=>{}} + end + end + + describe "when the state file/directory exists" do + before(:each) do + @state_file = Tempfile.new('storage_test') + @saved_statefile = Puppet[:statefile] + Puppet[:statefile] = @state_file.path() + end + + it "should overwrite its internal state if load() is called" do + # Should the state be overwritten even if Puppet[:statefile] is not valid YAML? + Puppet::Util::Storage.cache(:yayness) + Puppet::Util::Storage.state().should == {:yayness=>{}} + + proc { Puppet::Util::Storage.load() }.should_not raise_error() + Puppet::Util::Storage.state().should == {} + end + + it "should restore its internal state if the state file contains valid YAML" do + test_yaml = {'File["/yayness"]'=>{"name"=>{:a=>:b,:c=>:d}}} + YAML.expects(:load).returns(test_yaml) + + proc { Puppet::Util::Storage.load() }.should_not raise_error() + Puppet::Util::Storage.state().should == test_yaml + end + + it "should initialize with a clear internal state if the state file does not contain valid YAML" do + @state_file.write(:booness) + + proc { Puppet::Util::Storage.load() }.should_not raise_error() + Puppet::Util::Storage.state().should == {} + end + + it "should raise an error if the state file does not contain valid YAML and cannot be renamed" do + @state_file.write(:booness) + File.chmod(0000, @state_file.path()) + + proc { Puppet::Util::Storage.load() }.should raise_error() + end + + it "should attempt to rename the state file if the file is corrupted" do + # We fake corruption by causing YAML.load to raise an exception + YAML.expects(:load).raises(Puppet::Error) + File.expects(:rename).at_least_once + + proc { Puppet::Util::Storage.load() }.should_not raise_error() + end + + it "should fail gracefully on load() if the state file is not a regular file" do + @state_file.close!() + Dir.mkdir(Puppet[:statefile]) + File.expects(:rename).returns(0) + + proc { Puppet::Util::Storage.load() }.should_not raise_error() + + Dir.rmdir(Puppet[:statefile]) + end + + it "should fail gracefully on load() if it cannot get a read lock on the state file" do + Puppet::Util.expects(:readlock).yields(false) + test_yaml = {'File["/yayness"]'=>{"name"=>{:a=>:b,:c=>:d}}} + YAML.expects(:load).returns(test_yaml) + + proc { Puppet::Util::Storage.load() }.should_not raise_error() + Puppet::Util::Storage.state().should == test_yaml + end + + after(:each) do + @state_file.close!() + Puppet[:statefile] = @saved_statefile + end + end + end + + describe "when storing to the state file" do + before(:each) do + @state_file = Tempfile.new('storage_test') + @saved_statefile = Puppet[:statefile] + Puppet[:statefile] = @state_file.path() + end + + it "should create the state file if it does not exist" do + @state_file.close!() + FileTest.exists?(Puppet[:statefile]).should be_false() + Puppet::Util::Storage.cache(:yayness) + + proc { Puppet::Util::Storage.store() }.should_not raise_error() + FileTest.exists?(Puppet[:statefile]).should be_true() + end + + it "should raise an exception if the state file is not a regular file" do + @state_file.close!() + Dir.mkdir(Puppet[:statefile]) + Puppet::Util::Storage.cache(:yayness) + + proc { Puppet::Util::Storage.store() }.should raise_error() + + Dir.rmdir(Puppet[:statefile]) + end + + it "should raise an exception if it cannot get a write lock on the state file" do + Puppet::Util.expects(:writelock).yields(false) + Puppet::Util::Storage.cache(:yayness) + + proc { Puppet::Util::Storage.store() }.should raise_error() + end + + it "should load() the same information that it store()s" do + Puppet::Util::Storage.cache(:yayness) + + Puppet::Util::Storage.state().should == {:yayness=>{}} + proc { Puppet::Util::Storage.store() }.should_not raise_error() + Puppet::Util::Storage.clear() + Puppet::Util::Storage.state().should == {} + proc { Puppet::Util::Storage.load() }.should_not raise_error() + Puppet::Util::Storage.state().should == {:yayness=>{}} + end + + after(:each) do + @state_file.close!() + Puppet[:statefile] = @saved_statefile + end + end +end |