diff options
-rw-r--r-- | lib/puppet/provider/confine.rb | 76 | ||||
-rw-r--r-- | lib/puppet/provider/confiner.rb | 41 | ||||
-rwxr-xr-x | spec/unit/provider/confine.rb | 182 | ||||
-rwxr-xr-x | spec/unit/provider/confiner.rb | 106 |
4 files changed, 405 insertions, 0 deletions
diff --git a/lib/puppet/provider/confine.rb b/lib/puppet/provider/confine.rb new file mode 100644 index 000000000..e55ba02ec --- /dev/null +++ b/lib/puppet/provider/confine.rb @@ -0,0 +1,76 @@ +# The class that handles testing whether our providers +# actually work or not. +class Puppet::Provider::Confine + attr_reader :test, :values, :fact + + def exists?(value) + value and FileTest.exist?(value) + end + + # 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 false?(value) + ! value + end + + def initialize(test, 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 + 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 + end + + # Test whether our confine matches. + def valid? + values.each do |value| + unless send(@method, value) + msg = case test + when :false: "false value" + when :true: "true value" + 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 + 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 + end +end diff --git a/lib/puppet/provider/confiner.rb b/lib/puppet/provider/confiner.rb new file mode 100644 index 000000000..b88f1f01b --- /dev/null +++ b/lib/puppet/provider/confiner.rb @@ -0,0 +1,41 @@ +# Manage a collection of confines, returning a boolean or +# helpful information. +require 'puppet/provider/confine' + +class Puppet::Provider::Confiner + def confine(hash) + hash.each do |test, values| + @confines << Puppet::Provider::Confine.new(test, values) + end + end + + def initialize + @confines = [] + end + + # 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 + + missing + end + + def valid? + ! @confines.detect { |c| ! c.valid? } + end +end diff --git a/spec/unit/provider/confine.rb b/spec/unit/provider/confine.rb new file mode 100755 index 000000000..0e87ccdfb --- /dev/null +++ b/spec/unit/provider/confine.rb @@ -0,0 +1,182 @@ +#!/usr/bin/env ruby + +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 + end + + it "should always convert values to an array" do + Puppet::Provider::Confine.new(:exists, "/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) + 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 + 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 + end + + describe "when testing all values" do + before { @confine = Puppet::Provider::Confine.new(:true, %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.should_not be_valid + end + + it "should be valid if all values pass" do + @confine.stubs(:true?).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.valid? + end + end + + describe "when testing the result of the values" do + before { @confine = Puppet::Provider::Confine.new(:true, %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.result.should == [true, false, true, false] + end + end +end diff --git a/spec/unit/provider/confiner.rb b/spec/unit/provider/confiner.rb new file mode 100755 index 000000000..b80255c95 --- /dev/null +++ b/spec/unit/provider/confiner.rb @@ -0,0 +1,106 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/provider/confiner' + +describe Puppet::Provider::Confiner do + it "should be able to add confines" do + Puppet::Provider::Confiner.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::Confiner.new.confine :foo => :bar, :baz => :bee + end + + it "should be valid if no confines are present" do + Puppet::Provider::Confiner.new.should be_valid + end + + it "should be valid if all confines are valid" do + c1 = mock 'c1', :valid? => true + c2 = mock 'c2', :valid? => true + + Puppet::Provider::Confine.expects(:new).times(2).returns(c1).then.returns(c2) + + confiner = Puppet::Provider::Confiner.new + confiner.confine :foo => :bar, :baz => :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 + + Puppet::Provider::Confine.expects(:new).times(2).returns(c1).then.returns(c2) + + confiner = Puppet::Provider::Confiner.new + confiner.confine :foo => :bar, :baz => :bee + + confiner.should_not be_valid + end + + describe "when providing a complete result" do + before do + @confiner = Puppet::Provider::Confiner.new + end + + it "should return a hash" do + @confiner.result.should be_instance_of(Hash) + end + + it "should return an empty hash if the confiner is valid" do + @confiner.result.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::Confiner.new + confiner.confine :foo => :bar, :baz => :bee + + confiner.result[:true].should == 3 + 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 + + Puppet::Provider::Confine.expects(:new).times(2).returns(c1).then.returns(c2) + + confiner = Puppet::Provider::Confiner.new + confiner.confine :foo => :bar, :baz => :bee + + confiner.result[:false].should == 3 + 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::Confiner.new + confiner.confine :exists => %w{/one /two} + confiner.confine :exists => %w{/three /four} + + confiner.result[:exists].should == %w{/two /four} + 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::Confiner.new + confiner.confine :foo => "yes", :bar => "boo" + + result = confiner.result + result[:facter][:foo].should == %w{yes} + result[:facter][:bar].should be_nil + end + end +end |