diff options
| author | Luke Kanies <luke@madstop.com> | 2008-05-15 14:30:29 -0500 |
|---|---|---|
| committer | Luke Kanies <luke@madstop.com> | 2008-05-15 14:30:29 -0500 |
| commit | 1ba2bed578debd251d2b9514039082eaa3f136df (patch) | |
| tree | 907098a846dd7215d59ccfec2d470318bb619fa8 /spec/unit/util | |
| parent | be0a8031fbf8e4a2d608ab600c37c1b01dec16a1 (diff) | |
| download | facter-1ba2bed578debd251d2b9514039082eaa3f136df.tar.gz facter-1ba2bed578debd251d2b9514039082eaa3f136df.tar.xz facter-1ba2bed578debd251d2b9514039082eaa3f136df.zip | |
Moving all of the support classes to util/.
This makes it easier for our loader to distinguish between
code that Facter uses and new facts.
Diffstat (limited to 'spec/unit/util')
| -rwxr-xr-x | spec/unit/util/collection.rb | 177 | ||||
| -rwxr-xr-x | spec/unit/util/confine.rb | 75 | ||||
| -rwxr-xr-x | spec/unit/util/fact.rb | 129 | ||||
| -rwxr-xr-x | spec/unit/util/loader.rb | 190 | ||||
| -rwxr-xr-x | spec/unit/util/resolution.rb | 168 |
5 files changed, 739 insertions, 0 deletions
diff --git a/spec/unit/util/collection.rb b/spec/unit/util/collection.rb new file mode 100755 index 0000000..e88de06 --- /dev/null +++ b/spec/unit/util/collection.rb @@ -0,0 +1,177 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'facter/util/collection' + +describe Facter::Util::Collection do + it "should have a method for adding facts" do + Facter::Util::Collection.new.should respond_to(:add) + end + + describe "when adding facts" do + before do + @coll = Facter::Util::Collection.new + end + + it "should create a new fact if no fact with the same name already exists" do + fact = mock 'fact' + Facter::Util::Fact.expects(:new).with { |name, *args| name == :myname }.returns fact + + @coll.add(:myname) + end + + describe "and a block is provided" do + it "should use the block to add a resolution to the fact" do + fact = mock 'fact' + Facter::Util::Fact.expects(:new).returns fact + + fact.expects(:add) + + @coll.add(:myname) {} + end + end + end + + it "should have a method for retrieving facts by name" do + Facter::Util::Collection.new.should respond_to(:fact) + end + + describe "when retrieving facts" do + before do + @coll = Facter::Util::Collection.new + + @fact = @coll.add("YayNess") + end + + it "should return the fact instance specified by the name" do + @coll.fact("YayNess").should equal(@fact) + end + + it "should be case-insensitive" do + @coll.fact("yayness").should equal(@fact) + end + + it "should treat strings and symbols equivalently" do + @coll.fact(:yayness).should equal(@fact) + end + end + + it "should have a method for returning a fact's value" do + Facter::Util::Collection.new.should respond_to(:value) + end + + describe "when returning a fact's value" do + before do + @coll = Facter::Util::Collection.new + @fact = @coll.add("YayNess") + + @fact.stubs(:value).returns "result" + end + + it "should return the result of calling :value on the fact" do + @fact.expects(:value).returns "result" + + @coll.value("YayNess").should == "result" + end + + it "should be case-insensitive" do + @coll.value("yayness").should_not be_nil + end + + it "should treat strings and symbols equivalently" do + @coll.value(:yayness).should_not be_nil + end + end + + it "should return the fact's value when the array index method is used" do + @coll = Facter::Util::Collection.new + @coll.expects(:value).with("myfact").returns "foo" + @coll["myfact"].should == "foo" + end + + it "should have a method for flushing all facts" do + @coll = Facter::Util::Collection.new + @fact = @coll.add("YayNess") + + @fact.expects(:flush) + + @coll.flush + end + + it "should have a method that returns all fact names" do + @coll = Facter::Util::Collection.new + @coll.add(:one) + @coll.add(:two) + + @coll.list.sort.should == [:one, :two].sort + end + + it "should have a method for returning a hash of fact values" do + Facter::Util::Collection.new.should respond_to(:to_hash) + end + + describe "when returning a hash of values" do + before do + @coll = Facter::Util::Collection.new + @fact = @coll.add(:one) + @fact.stubs(:value).returns "me" + end + + it "should return a hash of fact names and values with the fact names as strings" do + @coll.to_hash.should == {"one" => "me"} + end + + it "should not include facts that did not return a value" do + f = @coll.add(:two) + f.stubs(:value).returns nil + @coll.to_hash.should_not be_include(:two) + end + end + + it "should have a method for iterating over all facts" do + Facter::Util::Collection.new.should respond_to(:each) + end + + it "should include Enumerable" do + Facter::Util::Collection.ancestors.should be_include(Enumerable) + end + + describe "when iterating over facts" do + before do + @coll = Facter::Util::Collection.new + @one = @coll.add(:one) + @two = @coll.add(:two) + end + + it "should yield each fact name and the fact value" do + @one.stubs(:value).returns "ONE" + @two.stubs(:value).returns "TWO" + facts = {} + @coll.each do |fact, value| + facts[fact] = value + end + facts.should == {"one" => "ONE", "two" => "TWO"} + end + + it "should convert the fact name to a string" do + @one.stubs(:value).returns "ONE" + @two.stubs(:value).returns "TWO" + facts = {} + @coll.each do |fact, value| + fact.should be_instance_of(String) + end + end + + it "should only yield facts that have values" do + @one.stubs(:value).returns "ONE" + @two.stubs(:value).returns nil + facts = {} + @coll.each do |fact, value| + facts[fact] = value + end + + facts.should_not be_include("two") + end + end +end diff --git a/spec/unit/util/confine.rb b/spec/unit/util/confine.rb new file mode 100755 index 0000000..5c1ce3b --- /dev/null +++ b/spec/unit/util/confine.rb @@ -0,0 +1,75 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'facter/util/confine' + +describe Facter::Util::Confine do + it "should require a fact name" do + Facter::Util::Confine.new("yay", true).fact.should == "yay" + end + + it "should accept a value specified individually" do + Facter::Util::Confine.new("yay", "test").values.should == ["test"] + end + + it "should accept multiple values specified at once" do + Facter::Util::Confine.new("yay", "test", "other").values.should == ["test", "other"] + end + + it "should convert all values to strings" do + Facter::Util::Confine.new("yay", :test).values.should == %w{test} + end + + it "should fail if no fact name is provided" do + lambda { Facter::Util::Confine.new(nil, :test) }.should raise_error(ArgumentError) + end + + it "should fail if no values were provided" do + lambda { Facter::Util::Confine.new("yay") }.should raise_error(ArgumentError) + end + + it "should have a method for testing whether it matches" do + Facter::Util::Confine.new("yay", :test).should respond_to(:true?) + end + + describe "when evaluating" do + before do + @confine = Facter::Util::Confine.new("yay", "one", "two") + @fact = mock 'fact' + Facter.stubs(:[]).returns @fact + end + + it "should return false if the fact does not exist" do + Facter.expects(:[]).with("yay").returns nil + + @confine.true?.should be_false + end + + it "should use the returned fact to get the value" do + Facter.expects(:[]).with("yay").returns @fact + + @fact.expects(:value).returns nil + + @confine.true? + end + + it "should return false if the fact has no value" do + @fact.stubs(:value).returns nil + + @confine.true?.should be_false + end + + it "should return true if any of the provided values matches the fact's value" do + @fact.stubs(:value).returns "two" + + @confine.true?.should be_true + end + + it "should return false if none of the provided values matches the fact's value" do + @fact.stubs(:value).returns "three" + + @confine.true?.should be_false + end + end +end diff --git a/spec/unit/util/fact.rb b/spec/unit/util/fact.rb new file mode 100755 index 0000000..1652032 --- /dev/null +++ b/spec/unit/util/fact.rb @@ -0,0 +1,129 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'facter/util/fact' + +describe Facter::Util::Fact do + it "should require a name" do + lambda { Facter::Util::Fact.new }.should raise_error(ArgumentError) + end + + it "should always downcase the name and convert it to a symbol" do + Facter::Util::Fact.new("YayNess").name.should == :yayness + end + + it "should default to its name converted to a string as its ldapname" do + Facter::Util::Fact.new("YayNess").ldapname.should == "yayness" + end + + it "should allow specifying the ldap name at initialization" do + Facter::Util::Fact.new("YayNess", :ldapname => "fooness").ldapname.should == "fooness" + end + + it "should fail if an unknown option is provided" do + lambda { Facter::Util::Fact.new('yay', :foo => :bar) }.should raise_error(ArgumentError) + end + + it "should have a method for adding resolution mechanisms" do + Facter::Util::Fact.new("yay").should respond_to(:add) + end + + describe "when adding resolution mechanisms" do + before do + @fact = Facter::Util::Fact.new("yay") + + @resolution = mock 'resolution' + @resolution.stub_everything + + end + + it "should fail if no block is given" do + lambda { @fact.add }.should raise_error(ArgumentError) + end + + it "should create a new resolution instance" do + Facter::Util::Resolution.expects(:new).returns @resolution + + @fact.add { } + end + + it "should instance_eval the passed block on the new resolution" do + @resolution.expects(:instance_eval) + + Facter::Util::Resolution.stubs(:new).returns @resolution + + @fact.add { } + end + + it "should re-sort the resolutions by length, so the most restricted resolutions are first" do + r1 = stub 'r1', :length => 1 + r2 = stub 'r2', :length => 2 + r3 = stub 'r3', :length => 0 + Facter::Util::Resolution.expects(:new).times(3).returns(r1).returns(r2).returns(r3) + @fact.add { } + @fact.add { } + @fact.add { } + + @fact.instance_variable_get("@resolves").should == [r2, r1, r3] + end + end + + it "should be able to return a value" do + Facter::Util::Fact.new("yay").should respond_to(:value) + end + + describe "when returning a value" do + before do + @fact = Facter::Util::Fact.new("yay") + end + + it "should return nil if there are no resolutions" do + Facter::Util::Fact.new("yay").value.should be_nil + end + + it "should return the first value returned by a resolution" do + r1 = stub 'r1', :length => 2, :value => nil, :suitable? => true + r2 = stub 'r2', :length => 1, :value => "yay", :suitable? => true + r3 = stub 'r3', :length => 0, :value => "foo", :suitable? => true + Facter::Util::Resolution.expects(:new).times(3).returns(r1).returns(r2).returns(r3) + @fact.add { } + @fact.add { } + @fact.add { } + + @fact.value.should == "yay" + end + + it "should short-cut returning the value once one is found" do + r1 = stub 'r1', :length => 2, :value => "foo", :suitable? => true + r2 = stub 'r2', :length => 1, :suitable? => true # would fail if 'value' were asked for + Facter::Util::Resolution.expects(:new).times(2).returns(r1).returns(r2) + @fact.add { } + @fact.add { } + + @fact.value + end + + it "should skip unsuitable resolutions" do + r1 = stub 'r1', :length => 2, :suitable? => false # would fail if 'value' were asked for' + r2 = stub 'r2', :length => 1, :value => "yay", :suitable? => true + Facter::Util::Resolution.expects(:new).times(2).returns(r1).returns(r2) + @fact.add { } + @fact.add { } + + @fact.value.should == "yay" + end + + it "should return nil if the value is the empty string" do + r1 = stub 'r1', :suitable? => true, :value => "" + Facter::Util::Resolution.expects(:new).returns r1 + @fact.add { } + + @fact.value.should be_nil + end + end + + it "should have a method for flushing the cached fact" do + Facter::Util::Fact.new(:foo).should respond_to(:flush) + end +end diff --git a/spec/unit/util/loader.rb b/spec/unit/util/loader.rb new file mode 100755 index 0000000..4c0d777 --- /dev/null +++ b/spec/unit/util/loader.rb @@ -0,0 +1,190 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'facter/util/loader' + +# Make sure we have a Puppet constant, so we can test +# loading Puppet facts. +unless defined?(Puppet) + class Puppet; end +end + +describe Facter::Util::Loader do + def with_env(values) + old = {} + values.each do |var, value| + if old_val = ENV[var] + old[var] = old_val + end + ENV[var] = value + end + yield + values.each do |var, value| + if old.include?(var) + ENV[var] = old[var] + else + ENV.delete(var) + end + end + end + + it "should have a method for loading individual facts by name" do + Facter::Util::Loader.new.should respond_to(:load) + end + + it "should have a method for loading all facts" do + Facter::Util::Loader.new.should respond_to(:load_all) + end + + it "should have a method for returning directories containing facts" do + Facter::Util::Loader.new.should respond_to(:search_path) + end + + describe "when determining the search path" do + before do + @loader = Facter::Util::Loader.new + @settings = mock 'settings' + @settings.stubs(:value).returns "/eh" + Puppet.stubs(:settings).returns @settings + end + + it "should include the facter subdirectory of all paths in ruby LOAD_PATH" do + dirs = $LOAD_PATH.collect { |d| File.join(d, "facter") } + paths = @loader.search_path + + dirs.each do |dir| + paths.should be_include(dir) + end + end + + describe "and the FACTERLIB environment variable is set" do + it "should include all paths in FACTERLIB" do + with_env "FACTERLIB" => "/one/path:/two/path" do + paths = @loader.search_path + %w{/one/path /two/path}.each do |dir| + paths.should be_include(dir) + end + end + end + end + + describe "and the Puppet libraries are loaded" do + it "should include the factdest setting" do + @settings.expects(:value).with(:factdest).returns "/my/facts" + @loader.search_path.should be_include("/my/facts") + end + + it "should include the facter subdirectory of the libdir setting" do + @settings.expects(:value).with(:libdir).returns "/lib/dir" + @loader.search_path.should be_include("/lib/dir/facter") + end + end + end + + describe "when loading facts" do + before do + @loader = Facter::Util::Loader.new + @loader.stubs(:search_path).returns [] + end + + it "should load values from the matching environment variable if one is present" do + Facter.expects(:add).with("testing") + + with_env "facter_testing" => "yayness" do + @loader.load(:testing) + end + end + + it "should load any files in the search path with names matching the fact name" do + @loader.expects(:search_path).returns %w{/one/dir /two/dir} + FileTest.stubs(:exist?).returns false + FileTest.expects(:exist?).with("/one/dir/testing.rb").returns true + FileTest.expects(:exist?).with("/two/dir/testing.rb").returns true + + Kernel.expects(:load).with("/one/dir/testing.rb") + Kernel.expects(:load).with("/two/dir/testing.rb") + + @loader.load(:testing) + end + + it "should load any ruby files in directories matching the fact name in the search path" do + @loader.expects(:search_path).returns %w{/one/dir} + FileTest.stubs(:exist?).returns false + FileTest.expects(:directory?).with("/one/dir/testing").returns true + + Dir.expects(:entries).with("/one/dir/testing").returns %w{two.rb} + + Kernel.expects(:load).with("/one/dir/testing/two.rb") + + @loader.load(:testing) + end + + it "should not load files that don't end in '.rb'" do + @loader.expects(:search_path).returns %w{/one/dir} + FileTest.stubs(:exist?).returns false + FileTest.expects(:directory?).with("/one/dir/testing").returns true + + Dir.expects(:entries).with("/one/dir/testing").returns %w{one} + + Kernel.expects(:load).never + + @loader.load(:testing) + end + end + + describe "when loading all facts" do + before do + @loader = Facter::Util::Loader.new + @loader.stubs(:search_path).returns [] + end + + it "should load all files in all search paths" do + @loader.expects(:search_path).returns %w{/one/dir /two/dir} + + Dir.expects(:entries).with("/one/dir").returns %w{a.rb b.rb} + Dir.expects(:entries).with("/two/dir").returns %w{c.rb d.rb} + + %w{/one/dir/a.rb /one/dir/b.rb /two/dir/c.rb /two/dir/d.rb}.each { |f| Kernel.expects(:load).with(f) } + + @loader.load_all + end + + it "should load all files in all subdirectories in all search paths" do + @loader.expects(:search_path).returns %w{/one/dir /two/dir} + + Dir.expects(:entries).with("/one/dir").returns %w{a} + Dir.expects(:entries).with("/two/dir").returns %w{b} + + %w{/one/dir/a /two/dir/b}.each { |f| File.expects(:directory?).with(f).returns true } + + Dir.expects(:entries).with("/one/dir/a").returns %w{c.rb} + Dir.expects(:entries).with("/two/dir/b").returns %w{d.rb} + + %w{/one/dir/a/c.rb /two/dir/b/d.rb}.each { |f| Kernel.expects(:load).with(f) } + + @loader.load_all + end + + it "should not load files in the util subdirectory" do + @loader.expects(:search_path).returns %w{/one/dir} + + Dir.expects(:entries).with("/one/dir").returns %w{util} + + File.expects(:directory?).with("/one/dir/util").returns true + + Dir.expects(:entries).with("/one/dir/util").never + + @loader.load_all + end + + it "should load all facts from the environment" do + Facter.expects(:add).with('one') + Facter.expects(:add).with('two') + + with_env "facter_one" => "yayness", "facter_two" => "boo" do + @loader.load_all + end + end + end +end diff --git a/spec/unit/util/resolution.rb b/spec/unit/util/resolution.rb new file mode 100755 index 0000000..493ee3a --- /dev/null +++ b/spec/unit/util/resolution.rb @@ -0,0 +1,168 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'facter/util/resolution' + +describe Facter::Util::Resolution do + it "should require a name" do + lambda { Facter::Util::Resolution.new }.should raise_error(ArgumentError) + end + + it "should have a name" do + Facter::Util::Resolution.new("yay").name.should == "yay" + end + + it "should have a method for setting the code" do + Facter::Util::Resolution.new("yay").should respond_to(:setcode) + end + + describe "when setting the code" do + before do + @resolve = Facter::Util::Resolution.new("yay") + end + + it "should default to /bin/sh as the interpreter if a string is provided" do + @resolve.setcode "foo" + @resolve.interpreter.should == "/bin/sh" + end + + it "should set the code to any provided string" do + @resolve.setcode "foo" + @resolve.code.should == "foo" + end + + it "should set the code to any provided block" do + block = lambda { } + @resolve.setcode(&block) + @resolve.code.should equal(block) + end + + it "should prefer the string over a block" do + @resolve.setcode("foo") { } + @resolve.code.should == "foo" + end + + it "should fail if neither a string nor block has been provided" do + lambda { @resolve.setcode }.should raise_error(ArgumentError) + end + end + + it "should be able to return a value" do + Facter::Util::Resolution.new("yay").should respond_to(:value) + end + + describe "when returning the value" do + before do + @resolve = Facter::Util::Resolution.new("yay") + end + + describe "and the code is a string" do + it "should return the result of executing the code with the interpreter" do + @resolve.setcode "/bin/foo" + Facter::Util::Resolution.expects(:exec).with("/bin/foo", "/bin/sh").returns "yup" + + @resolve.value.should == "yup" + end + + it "should return nil if the value is an empty string" do + @resolve.setcode "/bin/foo" + Facter::Util::Resolution.stubs(:exec).returns "" + @resolve.value.should be_nil + end + end + + describe "and the code is a block" do + it "should return the value returned by the block" do + @resolve.setcode { "yayness" } + @resolve.value.should == "yayness" + end + + it "should return nil if the value is an empty string" do + @resolve.setcode { "" } + @resolve.value.should be_nil + end + end + end + + it "should return its value when converted to a string" do + @resolve = Facter::Util::Resolution.new("yay") + @resolve.expects(:value).returns "myval" + @resolve.to_s.should == "myval" + end + + it "should allow the adding of confines" do + Facter::Util::Resolution.new("yay").should respond_to(:confine) + end + + it "should provide a method for returning the number of confines" do + @resolve = Facter::Util::Resolution.new("yay") + @resolve.confine "one" => "foo", "two" => "fee" + @resolve.length.should == 2 + end + + it "should return 0 confines when no confines have been added" do + Facter::Util::Resolution.new("yay").length.should == 0 + end + + it "should have a method for determining if it is suitable" do + Facter::Util::Resolution.new("yay").should respond_to(:suitable?) + end + + describe "when adding confines" do + before do + @resolve = Facter::Util::Resolution.new("yay") + end + + it "should accept a hash of fact names and values" do + lambda { @resolve.confine :one => "two" }.should_not raise_error + end + + it "should create a Util::Confine instance for every argument in the provided hash" do + Facter::Util::Confine.expects(:new).with("one", "foo") + Facter::Util::Confine.expects(:new).with("two", "fee") + + @resolve.confine "one" => "foo", "two" => "fee" + end + + end + + describe "when determining suitability" do + before do + @resolve = Facter::Util::Resolution.new("yay") + end + + it "should always be suitable if no confines have been added" do + @resolve.should be_suitable + end + + it "should be unsuitable if any provided confines return false" do + confine1 = mock 'confine1', :true? => true + confine2 = mock 'confine2', :true? => false + Facter::Util::Confine.expects(:new).times(2).returns(confine1).then.returns(confine2) + @resolve.confine :one => :two, :three => :four + + @resolve.should_not be_suitable + end + + it "should be suitable if all provided confines return true" do + confine1 = mock 'confine1', :true? => true + confine2 = mock 'confine2', :true? => true + Facter::Util::Confine.expects(:new).times(2).returns(confine1).then.returns(confine2) + @resolve.confine :one => :two, :three => :four + + @resolve.should be_suitable + end + end + + it "should have a class method for executing code" do + Facter::Util::Resolution.should respond_to(:exec) + end + + # It's not possible, AFAICT, to mock %x{}, so I can't really test this bit. + describe "when executing code" do + it "should fail if any interpreter other than /bin/sh is requested" do + lambda { Facter::Util::Resolution.exec("/something", "/bin/perl") }.should raise_error(ArgumentError) + end + end +end |
