From 1ba2bed578debd251d2b9514039082eaa3f136df Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 15 May 2008 14:30:29 -0500 Subject: 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. --- lib/facter.rb | 9 +- lib/facter/collection.rb | 84 ------------------- lib/facter/confine.rb | 41 --------- lib/facter/fact.rb | 102 ----------------------- lib/facter/loader.rb | 127 ---------------------------- lib/facter/resolution.rb | 107 ------------------------ lib/facter/util/collection.rb | 84 +++++++++++++++++++ lib/facter/util/confine.rb | 41 +++++++++ lib/facter/util/fact.rb | 102 +++++++++++++++++++++++ lib/facter/util/loader.rb | 127 ++++++++++++++++++++++++++++ lib/facter/util/resolution.rb | 107 ++++++++++++++++++++++++ spec/integration/facter.rb | 9 +- spec/unit/collection.rb | 177 --------------------------------------- spec/unit/confine.rb | 75 ----------------- spec/unit/fact.rb | 129 ---------------------------- spec/unit/loader.rb | 190 ------------------------------------------ spec/unit/resolution.rb | 168 ------------------------------------- spec/unit/util/collection.rb | 177 +++++++++++++++++++++++++++++++++++++++ spec/unit/util/confine.rb | 75 +++++++++++++++++ spec/unit/util/fact.rb | 129 ++++++++++++++++++++++++++++ spec/unit/util/loader.rb | 190 ++++++++++++++++++++++++++++++++++++++++++ spec/unit/util/resolution.rb | 168 +++++++++++++++++++++++++++++++++++++ 22 files changed, 1213 insertions(+), 1205 deletions(-) delete mode 100644 lib/facter/collection.rb delete mode 100644 lib/facter/confine.rb delete mode 100644 lib/facter/fact.rb delete mode 100644 lib/facter/loader.rb delete mode 100644 lib/facter/resolution.rb create mode 100644 lib/facter/util/collection.rb create mode 100644 lib/facter/util/confine.rb create mode 100644 lib/facter/util/fact.rb create mode 100644 lib/facter/util/loader.rb create mode 100644 lib/facter/util/resolution.rb delete mode 100755 spec/unit/collection.rb delete mode 100755 spec/unit/confine.rb delete mode 100755 spec/unit/fact.rb delete mode 100755 spec/unit/loader.rb delete mode 100755 spec/unit/resolution.rb create mode 100755 spec/unit/util/collection.rb create mode 100755 spec/unit/util/confine.rb create mode 100755 spec/unit/util/fact.rb create mode 100755 spec/unit/util/loader.rb create mode 100755 spec/unit/util/resolution.rb diff --git a/lib/facter.rb b/lib/facter.rb index 913060e..0f00f7d 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -18,8 +18,11 @@ #-- module Facter - require 'facter/fact' - require 'facter/collection' + # This is just so the other classes have the constant. + module Util; end + + require 'facter/util/fact' + require 'facter/util/collection' include Comparable include Enumerable @@ -47,7 +50,7 @@ module Facter def self.collection unless defined?(@collection) and @collection - @collection = Facter::Collection.new + @collection = Facter::Util::Collection.new end @collection end diff --git a/lib/facter/collection.rb b/lib/facter/collection.rb deleted file mode 100644 index 5e6845a..0000000 --- a/lib/facter/collection.rb +++ /dev/null @@ -1,84 +0,0 @@ -require 'facter' -require 'facter/resolution' - -# Manage which facts exist and how we access them. Largely just a wrapper -# around a hash of facts. -class Facter::Collection - # Return a fact object by name. If you use this, you still have to call - # 'value' on it to retrieve the actual value. - def [](name) - value(name) - end - - # Add a resolution mechanism for a named fact. This does not distinguish - # between adding a new fact and adding a new way to resolve a fact. - def add(name, options = {}, &block) - name = canonize(name) - - unless fact = @facts[name] - fact = Facter::Fact.new(name, options) - @facts[name] = fact - end - - fact.add(&block) if block - - return fact - end - - include Enumerable - - # Iterate across all of the facts. - def each - @facts.each do |name, fact| - value = fact.value - unless value.nil? - yield name.to_s, value - end - end - end - - # Return a fact by name. - def fact(name) - @facts[canonize(name)] - end - - # Flush all cached values. - def flush - @facts.each { |name, fact| fact.flush } - end - - def initialize - @facts = Hash.new - end - - # Return a list of all of the facts. - def list - return @facts.keys - end - - # Return a hash of all of our facts. - def to_hash - @facts.inject({}) do |h, ary| - value = ary[1].value - if ! value.nil? - # For backwards compatibility, convert the fact name to a string. - h[ary[0].to_s] = value - end - h - end - end - - def value(name) - if fact = @facts[canonize(name)] - fact.value - end - end - - private - - # Provide a consistent means of getting the exact same fact name - # every time. - def canonize(name) - name.to_s.downcase.to_sym - end -end diff --git a/lib/facter/confine.rb b/lib/facter/confine.rb deleted file mode 100644 index 529c968..0000000 --- a/lib/facter/confine.rb +++ /dev/null @@ -1,41 +0,0 @@ -# A restricting tag for fact resolution mechanisms. The tag must be true -# for the resolution mechanism to be suitable. -class Facter::Confine - attr_accessor :fact, :values - - # Add the restriction. Requires the fact name, an operator, and the value - # we're comparing to. - def initialize(fact, *values) - raise ArgumentError, "The fact name must be provided" unless fact - raise ArgumentError, "One or more values must be provided" if values.empty? - fact = fact.to_s if fact.is_a? Symbol - @fact = fact - @values = values.collect do |value| - if value.is_a? String - value - else - value.to_s - end - end - end - - def to_s - return "'%s' '%s'" % [@fact, @values.join(",")] - end - - # Evaluate the fact, returning true or false. - def true? - unless fact = Facter[@fact] - Facter.debug "No fact for %s" % @fact - return false - end - value = fact.value - - return false if value.nil? - - @values.each { |v| - return true if value.downcase == v.downcase - } - return false - end -end diff --git a/lib/facter/fact.rb b/lib/facter/fact.rb deleted file mode 100644 index 9514cd0..0000000 --- a/lib/facter/fact.rb +++ /dev/null @@ -1,102 +0,0 @@ -require 'facter' -require 'facter/resolution' - -class Facter::Fact - attr_accessor :name, :searching, :ldapname - - # Create a new fact, with no resolution mechanisms. - def initialize(name, options = {}) - @name = name.to_s.downcase.intern - - # LAK:NOTE: This is slow for many options, but generally we won't have any and at - # worst we'll have one. If we add more, this should be made more efficient. - options.each do |name, value| - case name - when :ldapname: self.ldapname = value - else - raise ArgumentError, "Invalid fact option '%s'" % name - end - end - - @ldapname ||= @name.to_s - - @resolves = [] - @searching = false - - @value = nil - end - - # Add a new resolution mechanism. This requires a block, which will then - # be evaluated in the context of the new mechanism. - def add(&block) - raise ArgumentError, "You must pass a block to Fact.add" unless block_given? - - resolve = Facter::Resolution.new(@name) - - resolve.instance_eval(&block) - - @resolves << resolve - - # Immediately sort the resolutions, so that we always have - # a sorted list for looking up values. - # We always want to look them up in the order of number of - # confines, so the most restricted resolution always wins. - @resolves.sort! { |a, b| b.length <=> a.length } - - return resolve - end - - # Flush any cached values. - def flush - @value = nil - @suitable = nil - end - - # Return the value for a given fact. Searches through all of the mechanisms - # and returns either the first value or nil. - def value - unless @value - # make sure we don't get stuck in recursive dependency loops - if @searching - Facter.debug "Caught recursion on %s" % @name - - # return a cached value if we've got it - if @value - return @value - else - return nil - end - end - @value = nil - - if @resolves.length == 0 - Facter.debug "No resolves for %s" % @name - return nil - end - - @searching = true - foundsuits = false - @value = @resolves.inject(nil) { |result, resolve| - next unless resolve.suitable? - foundsuits = true - - tmp = resolve.value - - break tmp unless tmp.nil? or tmp == "" - } - @searching = false - - unless foundsuits - Facter.debug "Found no suitable resolves of %s for %s" % [@resolves.length, @name] - end - end - - if @value.nil? - # nothing - Facter.debug("value for %s is still nil" % @name) - return nil - else - return @value - end - end -end diff --git a/lib/facter/loader.rb b/lib/facter/loader.rb deleted file mode 100644 index 3284cc4..0000000 --- a/lib/facter/loader.rb +++ /dev/null @@ -1,127 +0,0 @@ -require 'facter' - -# Load facts on demand. -class Facter::Loader - # Load all resolutions for a single fact. - def load(fact) - # Now load from the search path - shortname = fact.to_s.downcase - load_env(shortname) - - filename = shortname + ".rb" - search_path.each do |dir| - # Load individual files - file = File.join(dir, filename) - - # We have to specify Kernel.load, because we have a load method. - Kernel.load(file) if FileTest.exist?(file) - - # And load any directories matching the name - factdir = File.join(dir, shortname) - load_dir(factdir) if FileTest.directory?(factdir) - end - end - - # Load all facts from all directories. - def load_all - load_env - - search_path.each do |dir| - Dir.entries(dir).each do |file| - path = File.join(dir, file) - if File.directory?(path) - load_dir(path) - elsif file =~ /\.rb$/ - Kernel.load(File.join(dir, file)) - end - end - end - end - - # The list of directories we're going to search through for facts. - def search_path - result = [] - result += $LOAD_PATH.collect { |d| File.join(d, "facter") } - if ENV.include?("FACTERLIB") - result += ENV["FACTERLIB"].split(":") - end - - if defined?(Puppet) - result << Puppet.settings.value(:factdest) - result << File.join(Puppet.settings.value(:libdir), "facter") - end - result - end - - def old_stuff - # See if we can find any other facts in the regular Ruby lib - # paths - $:.each do |dir| - fdir = File.join(dir, "facter") - if FileTest.exists?(fdir) and FileTest.directory?(fdir) - factdirs.push(fdir) - end - end - # Also check anything in 'FACTERLIB' - if ENV['FACTERLIB'] - ENV['FACTERLIB'].split(":").each do |fdir| - factdirs.push(fdir) - end - end - factdirs.each do |fdir| - Dir.glob("#{fdir}/*.rb").each do |file| - # Load here, rather than require, because otherwise - # the facts won't get reloaded if someone calls - # "loadfacts". Really only important in testing, but, - # well, it's important in testing. - begin - load file - rescue => detail - warn "Could not load %s: %s" % - [file, detail] - end - end - end - - - # Now try to get facts from the environment - ENV.each do |name, value| - if name =~ /^facter_?(\w+)$/i - Facter.add($1) do - setcode { value } - end - end - end - end - - private - - def load_dir(dir) - return if dir =~ /\/util$/ - Dir.entries(dir).find_all { |f| f =~ /\.rb$/ }.each do |file| - Kernel.load(File.join(dir, file)) - end - end - - # Load facts from the environment. If no name is provided, - # all will be loaded. - def load_env(fact = nil) - # Load from the environment, if possible - ENV.each do |name, value| - # Skip anything that doesn't match our regex. - next unless name =~ /^facter_?(\w+)$/i - env_name = $1 - - # If a fact name was specified, skip anything that doesn't - # match it. - next if fact and env_name != fact - - Facter.add($1) do - setcode { value } - end - - # Short-cut, if we are only looking for one value. - break if fact - end - end -end diff --git a/lib/facter/resolution.rb b/lib/facter/resolution.rb deleted file mode 100644 index 4df55b7..0000000 --- a/lib/facter/resolution.rb +++ /dev/null @@ -1,107 +0,0 @@ -# An actual fact resolution mechanism. These are largely just chunks of -# code, with optional confinements restricting the mechanisms to only working on -# specific systems. Note that the confinements are always ANDed, so any -# confinements specified must all be true for the resolution to be -# suitable. -require 'facter/confine' - -class Facter::Resolution - attr_accessor :interpreter, :code, :name - - def self.have_which - if @have_which.nil? - %x{which which 2>/dev/null} - @have_which = ($? == 0) - end - @have_which - end - - # Execute a chunk of code. - def self.exec(code, interpreter = "/bin/sh") - raise ArgumentError, "non-sh interpreters are not currently supported" unless interpreter == "/bin/sh" - binary = code.split(/\s+/).shift - - if have_which - path = nil - if binary !~ /^\// - path = %x{which #{binary} 2>/dev/null}.chomp - # we don't have the binary necessary - return nil if path == "" - else - path = binary - end - - return nil unless FileTest.exists?(path) - end - - out = nil - begin - out = %x{#{code}}.chomp - rescue => detail - $stderr.puts detail - return nil - end - if out == "" - return nil - else - return out - end - end - - # Add a new confine to the resolution mechanism. - def confine(confines) - confines.each do |fact, values| - @confines.push Facter::Confine.new(fact, *values) - end - end - - # Create a new resolution mechanism. - def initialize(name) - @name = name - @confines = [] - @value = nil - end - - # Return the number of confines. - def length - @confines.length - end - - # Set our code for returning a value. - def setcode(string = nil, interp = nil, &block) - if string - @code = string - @interpreter = interp || "/bin/sh" - else - unless block_given? - raise ArgumentError, "You must pass either code or a block" - end - @code = block - end - end - - # Is this resolution mechanism suitable on the system in question? - def suitable? - unless defined? @suitable - @suitable = ! @confines.detect { |confine| ! confine.true? } - end - - return @suitable - end - - def to_s - return self.value() - end - - # How we get a value for our resolution mechanism. - def value - if @code.is_a?(Proc) - result = @code.call() - else - result = Facter::Resolution.exec(@code,@interpreter) - end - - return nil if result == "" - return result - end -end diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb new file mode 100644 index 0000000..f76b578 --- /dev/null +++ b/lib/facter/util/collection.rb @@ -0,0 +1,84 @@ +require 'facter' +require 'facter/util/fact' + +# Manage which facts exist and how we access them. Largely just a wrapper +# around a hash of facts. +class Facter::Util::Collection + # Return a fact object by name. If you use this, you still have to call + # 'value' on it to retrieve the actual value. + def [](name) + value(name) + end + + # Add a resolution mechanism for a named fact. This does not distinguish + # between adding a new fact and adding a new way to resolve a fact. + def add(name, options = {}, &block) + name = canonize(name) + + unless fact = @facts[name] + fact = Facter::Util::Fact.new(name, options) + @facts[name] = fact + end + + fact.add(&block) if block + + return fact + end + + include Enumerable + + # Iterate across all of the facts. + def each + @facts.each do |name, fact| + value = fact.value + unless value.nil? + yield name.to_s, value + end + end + end + + # Return a fact by name. + def fact(name) + @facts[canonize(name)] + end + + # Flush all cached values. + def flush + @facts.each { |name, fact| fact.flush } + end + + def initialize + @facts = Hash.new + end + + # Return a list of all of the facts. + def list + return @facts.keys + end + + # Return a hash of all of our facts. + def to_hash + @facts.inject({}) do |h, ary| + value = ary[1].value + if ! value.nil? + # For backwards compatibility, convert the fact name to a string. + h[ary[0].to_s] = value + end + h + end + end + + def value(name) + if fact = @facts[canonize(name)] + fact.value + end + end + + private + + # Provide a consistent means of getting the exact same fact name + # every time. + def canonize(name) + name.to_s.downcase.to_sym + end +end diff --git a/lib/facter/util/confine.rb b/lib/facter/util/confine.rb new file mode 100644 index 0000000..a430bbe --- /dev/null +++ b/lib/facter/util/confine.rb @@ -0,0 +1,41 @@ +# A restricting tag for fact resolution mechanisms. The tag must be true +# for the resolution mechanism to be suitable. +class Facter::Util::Confine + attr_accessor :fact, :values + + # Add the restriction. Requires the fact name, an operator, and the value + # we're comparing to. + def initialize(fact, *values) + raise ArgumentError, "The fact name must be provided" unless fact + raise ArgumentError, "One or more values must be provided" if values.empty? + fact = fact.to_s if fact.is_a? Symbol + @fact = fact + @values = values.collect do |value| + if value.is_a? String + value + else + value.to_s + end + end + end + + def to_s + return "'%s' '%s'" % [@fact, @values.join(",")] + end + + # Evaluate the fact, returning true or false. + def true? + unless fact = Facter[@fact] + Facter.debug "No fact for %s" % @fact + return false + end + value = fact.value + + return false if value.nil? + + @values.each { |v| + return true if value.downcase == v.downcase + } + return false + end +end diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb new file mode 100644 index 0000000..c73dfbf --- /dev/null +++ b/lib/facter/util/fact.rb @@ -0,0 +1,102 @@ +require 'facter' +require 'facter/util/resolution' + +class Facter::Util::Fact + attr_accessor :name, :searching, :ldapname + + # Create a new fact, with no resolution mechanisms. + def initialize(name, options = {}) + @name = name.to_s.downcase.intern + + # LAK:NOTE: This is slow for many options, but generally we won't have any and at + # worst we'll have one. If we add more, this should be made more efficient. + options.each do |name, value| + case name + when :ldapname: self.ldapname = value + else + raise ArgumentError, "Invalid fact option '%s'" % name + end + end + + @ldapname ||= @name.to_s + + @resolves = [] + @searching = false + + @value = nil + end + + # Add a new resolution mechanism. This requires a block, which will then + # be evaluated in the context of the new mechanism. + def add(&block) + raise ArgumentError, "You must pass a block to Fact.add" unless block_given? + + resolve = Facter::Util::Resolution.new(@name) + + resolve.instance_eval(&block) + + @resolves << resolve + + # Immediately sort the resolutions, so that we always have + # a sorted list for looking up values. + # We always want to look them up in the order of number of + # confines, so the most restricted resolution always wins. + @resolves.sort! { |a, b| b.length <=> a.length } + + return resolve + end + + # Flush any cached values. + def flush + @value = nil + @suitable = nil + end + + # Return the value for a given fact. Searches through all of the mechanisms + # and returns either the first value or nil. + def value + unless @value + # make sure we don't get stuck in recursive dependency loops + if @searching + Facter.debug "Caught recursion on %s" % @name + + # return a cached value if we've got it + if @value + return @value + else + return nil + end + end + @value = nil + + if @resolves.length == 0 + Facter.debug "No resolves for %s" % @name + return nil + end + + @searching = true + foundsuits = false + @value = @resolves.inject(nil) { |result, resolve| + next unless resolve.suitable? + foundsuits = true + + tmp = resolve.value + + break tmp unless tmp.nil? or tmp == "" + } + @searching = false + + unless foundsuits + Facter.debug "Found no suitable resolves of %s for %s" % [@resolves.length, @name] + end + end + + if @value.nil? + # nothing + Facter.debug("value for %s is still nil" % @name) + return nil + else + return @value + end + end +end diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb new file mode 100644 index 0000000..7031172 --- /dev/null +++ b/lib/facter/util/loader.rb @@ -0,0 +1,127 @@ +require 'facter' + +# Load facts on demand. +class Facter::Util::Loader + # Load all resolutions for a single fact. + def load(fact) + # Now load from the search path + shortname = fact.to_s.downcase + load_env(shortname) + + filename = shortname + ".rb" + search_path.each do |dir| + # Load individual files + file = File.join(dir, filename) + + # We have to specify Kernel.load, because we have a load method. + Kernel.load(file) if FileTest.exist?(file) + + # And load any directories matching the name + factdir = File.join(dir, shortname) + load_dir(factdir) if FileTest.directory?(factdir) + end + end + + # Load all facts from all directories. + def load_all + load_env + + search_path.each do |dir| + Dir.entries(dir).each do |file| + path = File.join(dir, file) + if File.directory?(path) + load_dir(path) + elsif file =~ /\.rb$/ + Kernel.load(File.join(dir, file)) + end + end + end + end + + # The list of directories we're going to search through for facts. + def search_path + result = [] + result += $LOAD_PATH.collect { |d| File.join(d, "facter") } + if ENV.include?("FACTERLIB") + result += ENV["FACTERLIB"].split(":") + end + + if defined?(Puppet) + result << Puppet.settings.value(:factdest) + result << File.join(Puppet.settings.value(:libdir), "facter") + end + result + end + + def old_stuff + # See if we can find any other facts in the regular Ruby lib + # paths + $:.each do |dir| + fdir = File.join(dir, "facter") + if FileTest.exists?(fdir) and FileTest.directory?(fdir) + factdirs.push(fdir) + end + end + # Also check anything in 'FACTERLIB' + if ENV['FACTERLIB'] + ENV['FACTERLIB'].split(":").each do |fdir| + factdirs.push(fdir) + end + end + factdirs.each do |fdir| + Dir.glob("#{fdir}/*.rb").each do |file| + # Load here, rather than require, because otherwise + # the facts won't get reloaded if someone calls + # "loadfacts". Really only important in testing, but, + # well, it's important in testing. + begin + load file + rescue => detail + warn "Could not load %s: %s" % + [file, detail] + end + end + end + + + # Now try to get facts from the environment + ENV.each do |name, value| + if name =~ /^facter_?(\w+)$/i + Facter.add($1) do + setcode { value } + end + end + end + end + + private + + def load_dir(dir) + return if dir =~ /\/util$/ + Dir.entries(dir).find_all { |f| f =~ /\.rb$/ }.each do |file| + Kernel.load(File.join(dir, file)) + end + end + + # Load facts from the environment. If no name is provided, + # all will be loaded. + def load_env(fact = nil) + # Load from the environment, if possible + ENV.each do |name, value| + # Skip anything that doesn't match our regex. + next unless name =~ /^facter_?(\w+)$/i + env_name = $1 + + # If a fact name was specified, skip anything that doesn't + # match it. + next if fact and env_name != fact + + Facter.add($1) do + setcode { value } + end + + # Short-cut, if we are only looking for one value. + break if fact + end + end +end diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb new file mode 100644 index 0000000..b6aae77 --- /dev/null +++ b/lib/facter/util/resolution.rb @@ -0,0 +1,107 @@ +# An actual fact resolution mechanism. These are largely just chunks of +# code, with optional confinements restricting the mechanisms to only working on +# specific systems. Note that the confinements are always ANDed, so any +# confinements specified must all be true for the resolution to be +# suitable. +require 'facter/util/confine' + +class Facter::Util::Resolution + attr_accessor :interpreter, :code, :name + + def self.have_which + if @have_which.nil? + %x{which which 2>/dev/null} + @have_which = ($? == 0) + end + @have_which + end + + # Execute a chunk of code. + def self.exec(code, interpreter = "/bin/sh") + raise ArgumentError, "non-sh interpreters are not currently supported" unless interpreter == "/bin/sh" + binary = code.split(/\s+/).shift + + if have_which + path = nil + if binary !~ /^\// + path = %x{which #{binary} 2>/dev/null}.chomp + # we don't have the binary necessary + return nil if path == "" + else + path = binary + end + + return nil unless FileTest.exists?(path) + end + + out = nil + begin + out = %x{#{code}}.chomp + rescue => detail + $stderr.puts detail + return nil + end + if out == "" + return nil + else + return out + end + end + + # Add a new confine to the resolution mechanism. + def confine(confines) + confines.each do |fact, values| + @confines.push Facter::Util::Confine.new(fact, *values) + end + end + + # Create a new resolution mechanism. + def initialize(name) + @name = name + @confines = [] + @value = nil + end + + # Return the number of confines. + def length + @confines.length + end + + # Set our code for returning a value. + def setcode(string = nil, interp = nil, &block) + if string + @code = string + @interpreter = interp || "/bin/sh" + else + unless block_given? + raise ArgumentError, "You must pass either code or a block" + end + @code = block + end + end + + # Is this resolution mechanism suitable on the system in question? + def suitable? + unless defined? @suitable + @suitable = ! @confines.detect { |confine| ! confine.true? } + end + + return @suitable + end + + def to_s + return self.value() + end + + # How we get a value for our resolution mechanism. + def value + if @code.is_a?(Proc) + result = @code.call() + else + result = Facter::Util::Resolution.exec(@code,@interpreter) + end + + return nil if result == "" + return result + end +end diff --git a/spec/integration/facter.rb b/spec/integration/facter.rb index b503c9d..e1f2025 100755 --- a/spec/integration/facter.rb +++ b/spec/integration/facter.rb @@ -8,10 +8,15 @@ describe Facter do Facter.loadfacts end + after do + Facter.reset + end + it "should create a new collection if one does not exist" do Facter.reset - Facter::Collection.expects(:new).returns "coll" - Facter.collection.should == "coll" + coll = mock('coll') + Facter::Util::Collection.stubs(:new).returns coll + Facter.collection.should equal(coll) Facter.reset end diff --git a/spec/unit/collection.rb b/spec/unit/collection.rb deleted file mode 100755 index d69888e..0000000 --- a/spec/unit/collection.rb +++ /dev/null @@ -1,177 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../spec_helper' - -require 'facter/collection' - -describe Facter::Collection do - it "should have a method for adding facts" do - Facter::Collection.new.should respond_to(:add) - end - - describe "when adding facts" do - before do - @coll = Facter::Collection.new - end - - it "should create a new fact if no fact with the same name already exists" do - fact = mock 'fact' - Facter::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::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::Collection.new.should respond_to(:fact) - end - - describe "when retrieving facts" do - before do - @coll = Facter::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::Collection.new.should respond_to(:value) - end - - describe "when returning a fact's value" do - before do - @coll = Facter::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::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::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::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::Collection.new.should respond_to(:to_hash) - end - - describe "when returning a hash of values" do - before do - @coll = Facter::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::Collection.new.should respond_to(:each) - end - - it "should include Enumerable" do - Facter::Collection.ancestors.should be_include(Enumerable) - end - - describe "when iterating over facts" do - before do - @coll = Facter::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/confine.rb b/spec/unit/confine.rb deleted file mode 100755 index ae122ea..0000000 --- a/spec/unit/confine.rb +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../spec_helper' - -require 'facter/confine' - -describe Facter::Confine do - it "should require a fact name" do - Facter::Confine.new("yay", true).fact.should == "yay" - end - - it "should accept a value specified individually" do - Facter::Confine.new("yay", "test").values.should == ["test"] - end - - it "should accept multiple values specified at once" do - Facter::Confine.new("yay", "test", "other").values.should == ["test", "other"] - end - - it "should convert all values to strings" do - Facter::Confine.new("yay", :test).values.should == %w{test} - end - - it "should fail if no fact name is provided" do - lambda { Facter::Confine.new(nil, :test) }.should raise_error(ArgumentError) - end - - it "should fail if no values were provided" do - lambda { Facter::Confine.new("yay") }.should raise_error(ArgumentError) - end - - it "should have a method for testing whether it matches" do - Facter::Confine.new("yay", :test).should respond_to(:true?) - end - - describe "when evaluating" do - before do - @confine = Facter::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/fact.rb b/spec/unit/fact.rb deleted file mode 100755 index 3772d0f..0000000 --- a/spec/unit/fact.rb +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../spec_helper' - -require 'facter/fact' - -describe Facter::Fact do - it "should require a name" do - lambda { Facter::Fact.new }.should raise_error(ArgumentError) - end - - it "should always downcase the name and convert it to a symbol" do - Facter::Fact.new("YayNess").name.should == :yayness - end - - it "should default to its name converted to a string as its ldapname" do - Facter::Fact.new("YayNess").ldapname.should == "yayness" - end - - it "should allow specifying the ldap name at initialization" do - Facter::Fact.new("YayNess", :ldapname => "fooness").ldapname.should == "fooness" - end - - it "should fail if an unknown option is provided" do - lambda { Facter::Fact.new('yay', :foo => :bar) }.should raise_error(ArgumentError) - end - - it "should have a method for adding resolution mechanisms" do - Facter::Fact.new("yay").should respond_to(:add) - end - - describe "when adding resolution mechanisms" do - before do - @fact = Facter::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::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::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::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::Fact.new("yay").should respond_to(:value) - end - - describe "when returning a value" do - before do - @fact = Facter::Fact.new("yay") - end - - it "should return nil if there are no resolutions" do - Facter::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::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::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::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::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::Fact.new(:foo).should respond_to(:flush) - end -end diff --git a/spec/unit/loader.rb b/spec/unit/loader.rb deleted file mode 100755 index 75146e1..0000000 --- a/spec/unit/loader.rb +++ /dev/null @@ -1,190 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../spec_helper' - -require 'facter/loader' - -# Make sure we have a Puppet constant, so we can test -# loading Puppet facts. -unless defined?(Puppet) - class Puppet; end -end - -describe Facter::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::Loader.new.should respond_to(:load) - end - - it "should have a method for loading all facts" do - Facter::Loader.new.should respond_to(:load_all) - end - - it "should have a method for returning directories containing facts" do - Facter::Loader.new.should respond_to(:search_path) - end - - describe "when determining the search path" do - before do - @loader = Facter::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::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::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/resolution.rb b/spec/unit/resolution.rb deleted file mode 100755 index a54b872..0000000 --- a/spec/unit/resolution.rb +++ /dev/null @@ -1,168 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../spec_helper' - -require 'facter/resolution' - -describe Facter::Resolution do - it "should require a name" do - lambda { Facter::Resolution.new }.should raise_error(ArgumentError) - end - - it "should have a name" do - Facter::Resolution.new("yay").name.should == "yay" - end - - it "should have a method for setting the code" do - Facter::Resolution.new("yay").should respond_to(:setcode) - end - - describe "when setting the code" do - before do - @resolve = Facter::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::Resolution.new("yay").should respond_to(:value) - end - - describe "when returning the value" do - before do - @resolve = Facter::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::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::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::Resolution.new("yay") - @resolve.expects(:value).returns "myval" - @resolve.to_s.should == "myval" - end - - it "should allow the adding of confines" do - Facter::Resolution.new("yay").should respond_to(:confine) - end - - it "should provide a method for returning the number of confines" do - @resolve = Facter::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::Resolution.new("yay").length.should == 0 - end - - it "should have a method for determining if it is suitable" do - Facter::Resolution.new("yay").should respond_to(:suitable?) - end - - describe "when adding confines" do - before do - @resolve = Facter::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 Confine instance for every argument in the provided hash" do - Facter::Confine.expects(:new).with("one", "foo") - Facter::Confine.expects(:new).with("two", "fee") - - @resolve.confine "one" => "foo", "two" => "fee" - end - - end - - describe "when determining suitability" do - before do - @resolve = Facter::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::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::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::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::Resolution.exec("/something", "/bin/perl") }.should raise_error(ArgumentError) - end - end -end 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 -- cgit