diff --git a/Rakefile b/Rakefile
index 8e21cebd9..728f1e3de 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,54 +1,54 @@
# Rakefile for Puppet -*- ruby -*-
$LOAD_PATH << File.join(File.dirname(__FILE__), 'tasks')
require 'rake'
require 'rake/packagetask'
require 'rake/gempackagetask'
-require 'spec'
-require 'spec/rake/spectask'
+require 'rspec'
+require "rspec/core/rake_task"
module Puppet
PUPPETVERSION = File.read('lib/puppet.rb')[/PUPPETVERSION *= *'(.*)'/,1] or fail "Couldn't find PUPPETVERSION"
end
Dir['tasks/**/*.rake'].each { |t| load t }
FILES = FileList[
'[A-Z]*',
'install.rb',
'bin/**/*',
'sbin/**/*',
'lib/**/*',
'conf/**/*',
'man/**/*',
'examples/**/*',
'ext/**/*',
'tasks/**/*',
'test/**/*',
'spec/**/*'
]
Rake::PackageTask.new("puppet", Puppet::PUPPETVERSION) do |pkg|
pkg.package_dir = 'pkg'
pkg.need_tar_gz = true
pkg.package_files = FILES.to_a
end
task :default do
sh %{rake -T}
end
desc "Create the tarball and the gem - use when releasing"
task :puppetpackages => [:create_gem, :package]
-Spec::Rake::SpecTask.new do |t|
- t.spec_opts = ['--format','s', '--loadby','mtime','--color']
+RSpec::Core::RakeTask.new do |t|
+ t.rspec_opts = ['--format','s', '--color']
t.pattern ='spec/{unit,integration}/**/*.rb'
t.fail_on_error = false
end
desc "Run the unit tests"
task :unit do
sh "cd test; rake"
end
diff --git a/lib/puppet/application/apply.rb b/lib/puppet/application/apply.rb
index 59a95d35a..33a70ce8a 100644
--- a/lib/puppet/application/apply.rb
+++ b/lib/puppet/application/apply.rb
@@ -1,173 +1,157 @@
require 'puppet/application'
class Puppet::Application::Apply < Puppet::Application
should_parse_config
option("--debug","-d")
option("--execute EXECUTE","-e") do |arg|
options[:code] = arg
end
option("--loadclasses","-L")
option("--verbose","-v")
option("--use-nodes")
option("--detailed-exitcodes")
option("--apply catalog", "-a catalog") do |arg|
options[:catalog] = arg
end
option("--logdest LOGDEST", "-l") do |arg|
begin
Puppet::Util::Log.newdestination(arg)
options[:logset] = true
rescue => detail
$stderr.puts detail.to_s
end
end
def run_command
if options[:catalog]
apply
elsif Puppet[:parseonly]
parseonly
else
main
end
end
def apply
if options[:catalog] == "-"
text = $stdin.read
else
text = File.read(options[:catalog])
end
begin
catalog = Puppet::Resource::Catalog.convert_from(Puppet::Resource::Catalog.default_format,text)
catalog = Puppet::Resource::Catalog.pson_create(catalog) unless catalog.is_a?(Puppet::Resource::Catalog)
rescue => detail
raise Puppet::Error, "Could not deserialize catalog from pson: #{detail}"
end
catalog = catalog.to_ral
require 'puppet/configurer'
configurer = Puppet::Configurer.new
configurer.run :catalog => catalog
end
def parseonly
# Set our code or file to use.
if options[:code] or command_line.args.length == 0
Puppet[:code] = options[:code] || STDIN.read
else
Puppet[:manifest] = command_line.args.shift
end
begin
Puppet::Node::Environment.new(Puppet[:environment]).known_resource_types
rescue => detail
Puppet.err detail
exit 1
end
exit 0
end
def main
# Set our code or file to use.
if options[:code] or command_line.args.length == 0
Puppet[:code] = options[:code] || STDIN.read
else
manifest = command_line.args.shift
raise "Could not find file #{manifest}" unless File.exist?(manifest)
Puppet.warning("Only one file can be applied per run. Skipping #{command_line.args.join(', ')}") if command_line.args.size > 0
Puppet[:manifest] = manifest
end
# Collect our facts.
unless facts = Puppet::Node::Facts.find(Puppet[:certname])
raise "Could not find facts for #{Puppet[:certname]}"
end
# Find our Node
unless node = Puppet::Node.find(Puppet[:certname])
raise "Could not find node #{Puppet[:certname]}"
end
# Merge in the facts.
node.merge(facts.values)
# Allow users to load the classes that puppet agent creates.
if options[:loadclasses]
file = Puppet[:classfile]
if FileTest.exists?(file)
unless FileTest.readable?(file)
$stderr.puts "#{file} is not readable"
exit(63)
end
node.classes = File.read(file).split(/[\s\n]+/)
end
end
begin
# Compile our catalog
starttime = Time.now
catalog = Puppet::Resource::Catalog.find(node.name, :use_node => node)
# Translate it to a RAL catalog
catalog = catalog.to_ral
catalog.finalize
catalog.retrieval_duration = Time.now - starttime
require 'puppet/configurer'
configurer = Puppet::Configurer.new
- configurer.execute_prerun_command
+ report = configurer.run(:skip_plugin_download => true, :catalog => catalog)
- # And apply it
- if Puppet[:report]
- report = configurer.initialize_report
- Puppet::Util::Log.newdestination(report)
- end
- transaction = catalog.apply
-
- configurer.execute_postrun_command
-
- if Puppet[:report]
- Puppet::Util::Log.close(report)
- configurer.send_report(report, transaction)
- else
- transaction.generate_report
- end
-
- exit( Puppet[:noop] ? 0 : options[:detailed_exitcodes] ? transaction.report.exit_status : 0 )
+ exit( Puppet[:noop] ? 0 : options[:detailed_exitcodes] ? report.exit_status : 0 )
rescue => detail
puts detail.backtrace if Puppet[:trace]
$stderr.puts detail.message
exit(1)
end
end
def setup
exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs?
# If noop is set, then also enable diffs
Puppet[:show_diff] = true if Puppet[:noop]
Puppet::Util::Log.newdestination(:console) unless options[:logset]
client = nil
server = nil
trap(:INT) do
$stderr.puts "Exiting"
exit(1)
end
if options[:debug]
Puppet::Util::Log.level = :debug
elsif options[:verbose]
Puppet::Util::Log.level = :info
end
end
end
diff --git a/lib/puppet/application/inspect.rb b/lib/puppet/application/inspect.rb
index c28fef326..c7be893c7 100644
--- a/lib/puppet/application/inspect.rb
+++ b/lib/puppet/application/inspect.rb
@@ -1,80 +1,86 @@
require 'puppet/application'
class Puppet::Application::Inspect < Puppet::Application
should_parse_config
run_mode :agent
option("--debug","-d")
option("--verbose","-v")
option("--logdest LOGDEST", "-l") do |arg|
begin
Puppet::Util::Log.newdestination(arg)
options[:logset] = true
rescue => detail
$stderr.puts detail.to_s
end
end
def setup
exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs?
raise "Inspect requires reporting to be enabled. Set report=true in puppet.conf to enable reporting." unless Puppet[:report]
@report = Puppet::Transaction::Report.new("inspect")
Puppet::Util::Log.newdestination(@report)
Puppet::Util::Log.newdestination(:console) unless options[:logset]
trap(:INT) do
$stderr.puts "Exiting"
exit(1)
end
if options[:debug]
Puppet::Util::Log.level = :debug
elsif options[:verbose]
Puppet::Util::Log.level = :info
end
Puppet::Transaction::Report.terminus_class = :rest
Puppet::Resource::Catalog.terminus_class = :yaml
end
def run_command
retrieval_starttime = Time.now
unless catalog = Puppet::Resource::Catalog.find(Puppet[:certname])
raise "Could not find catalog for #{Puppet[:certname]}"
end
- retrieval_time = Time.now - retrieval_starttime
- @report.add_times("config_retrieval", retrieval_time)
+ @report.configuration_version = catalog.version
- starttime = Time.now
+ inspect_starttime = Time.now
+ @report.add_times("config_retrieval", inspect_starttime - retrieval_starttime)
catalog.to_ral.resources.each do |ral_resource|
audited_attributes = ral_resource[:audit]
next unless audited_attributes
audited_resource = ral_resource.to_resource
status = Puppet::Resource::Status.new(ral_resource)
audited_attributes.each do |name|
- event = ral_resource.event(:previous_value => audited_resource[name], :property => name, :status => "audit", :message => "inspected value is #{audited_resource[name].inspect}")
- status.add_event(event)
+ next if audited_resource[name].nil?
+ # Skip :absent properties of :absent resources. Really, it would be nicer if the RAL returned nil for those, but it doesn't. ~JW
+ if name == :ensure or audited_resource[:ensure] != :absent or audited_resource[name] != :absent
+ event = ral_resource.event(:previous_value => audited_resource[name], :property => name, :status => "audit", :message => "inspected value is #{audited_resource[name].inspect}")
+ status.add_event(event)
+ end
end
@report.add_resource_status(status)
end
- @report.add_metric(:time, {"config_retrieval" => retrieval_time, "inspect" => Time.now - starttime})
+ finishtime = Time.now
+ @report.add_times("inspect", finishtime - inspect_starttime)
+ @report.finalize_report
begin
@report.save
rescue => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Could not send report: #{detail}"
end
end
end
diff --git a/lib/puppet/configurer.rb b/lib/puppet/configurer.rb
index 31d31c2d2..d3c902576 100644
--- a/lib/puppet/configurer.rb
+++ b/lib/puppet/configurer.rb
@@ -1,239 +1,237 @@
# The client for interacting with the puppetmaster config server.
require 'sync'
require 'timeout'
require 'puppet/network/http_pool'
require 'puppet/util'
class Puppet::Configurer
class CommandHookError < RuntimeError; end
require 'puppet/configurer/fact_handler'
require 'puppet/configurer/plugin_handler'
include Puppet::Configurer::FactHandler
include Puppet::Configurer::PluginHandler
# For benchmarking
include Puppet::Util
attr_reader :compile_time
# Provide more helpful strings to the logging that the Agent does
def self.to_s
"Puppet configuration client"
end
class << self
# Puppetd should only have one instance running, and we need a way
# to retrieve it.
attr_accessor :instance
include Puppet::Util
end
# How to lock instances of this class.
def self.lockfile_path
Puppet[:puppetdlockfile]
end
def clear
@catalog.clear(true) if @catalog
@catalog = nil
end
def execute_postrun_command
execute_from_setting(:postrun_command)
end
def execute_prerun_command
execute_from_setting(:prerun_command)
end
# Initialize and load storage
def dostorage
Puppet::Util::Storage.load
@compile_time ||= Puppet::Util::Storage.cache(:configuration)[:compile_time]
rescue => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Corrupt state file #{Puppet[:statefile]}: #{detail}"
begin
::File.unlink(Puppet[:statefile])
retry
rescue => detail
raise Puppet::Error.new("Cannot remove #{Puppet[:statefile]}: #{detail}")
end
end
# Just so we can specify that we are "the" instance.
def initialize
Puppet.settings.use(:main, :ssl, :agent)
self.class.instance = self
@running = false
@splayed = false
end
- def initialize_report
- Puppet::Transaction::Report.new
- end
-
# Prepare for catalog retrieval. Downloads everything necessary, etc.
- def prepare
+ def prepare(options)
dostorage
- download_plugins
+ download_plugins unless options[:skip_plugin_download]
- download_fact_plugins
+ download_fact_plugins unless options[:skip_plugin_download]
execute_prerun_command
end
# Get the remote catalog, yo. Returns nil if no catalog can be found.
def retrieve_catalog
if Puppet::Resource::Catalog.indirection.terminus_class == :rest
# This is a bit complicated. We need the serialized and escaped facts,
# and we need to know which format they're encoded in. Thus, we
# get a hash with both of these pieces of information.
fact_options = facts_for_uploading
else
fact_options = {}
end
# First try it with no cache, then with the cache.
unless (Puppet[:use_cached_catalog] and result = retrieve_catalog_from_cache(fact_options)) or result = retrieve_new_catalog(fact_options)
if ! Puppet[:usecacheonfailure]
Puppet.warning "Not using cache on failed catalog"
return nil
end
result = retrieve_catalog_from_cache(fact_options)
end
return nil unless result
convert_catalog(result, @duration)
end
# Convert a plain resource catalog into our full host catalog.
def convert_catalog(result, duration)
catalog = result.to_ral
catalog.finalize
catalog.retrieval_duration = duration
catalog.write_class_file
catalog
end
# The code that actually runs the catalog.
# This just passes any options on to the catalog,
# which accepts :tags and :ignoreschedules.
def run(options = {})
begin
- prepare
+ prepare(options)
rescue SystemExit,NoMemoryError
raise
rescue Exception => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Failed to prepare catalog: #{detail}"
end
- options[:report] ||= initialize_report
+ options[:report] ||= Puppet::Transaction::Report.new("apply")
report = options[:report]
Puppet::Util::Log.newdestination(report)
if catalog = options[:catalog]
options.delete(:catalog)
elsif ! catalog = retrieve_catalog
Puppet.err "Could not retrieve catalog; skipping run"
return
end
+ report.configuration_version = catalog.version
+
transaction = nil
begin
benchmark(:notice, "Finished catalog run") do
transaction = catalog.apply(options)
end
report
rescue => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Failed to apply catalog: #{detail}"
return
end
ensure
# Make sure we forget the retained module_directories of any autoload
# we might have used.
Thread.current[:env_module_directories] = nil
# Now close all of our existing http connections, since there's no
# reason to leave them lying open.
Puppet::Network::HttpPool.clear_http_instances
execute_postrun_command
Puppet::Util::Log.close(report)
send_report(report, transaction)
end
- def send_report(report, trans = nil)
- trans.generate_report if trans
+ def send_report(report, trans)
+ report.finalize_report if trans
puts report.summary if Puppet[:summarize]
report.save if Puppet[:report]
rescue => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Could not send report: #{detail}"
end
private
def self.timeout
timeout = Puppet[:configtimeout]
case timeout
when String
if timeout =~ /^\d+$/
timeout = Integer(timeout)
else
raise ArgumentError, "Configuration timeout must be an integer"
end
when Integer # nothing
else
raise ArgumentError, "Configuration timeout must be an integer"
end
timeout
end
def execute_from_setting(setting)
return if (command = Puppet[setting]) == ""
begin
Puppet::Util.execute([command])
rescue => detail
raise CommandHookError, "Could not run command from #{setting}: #{detail}"
end
end
def retrieve_catalog_from_cache(fact_options)
result = nil
@duration = thinmark do
result = Puppet::Resource::Catalog.find(Puppet[:certname], fact_options.merge(:ignore_terminus => true))
end
Puppet.notice "Using cached catalog"
result
rescue => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Could not retrieve catalog from cache: #{detail}"
return nil
end
def retrieve_new_catalog(fact_options)
result = nil
@duration = thinmark do
result = Puppet::Resource::Catalog.find(Puppet[:certname], fact_options.merge(:ignore_cache => true))
end
result
rescue SystemExit,NoMemoryError
raise
rescue Exception => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Could not retrieve catalog from remote server: #{detail}"
return nil
end
end
diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb
index 54e034acb..a5aaeddc4 100644
--- a/lib/puppet/parser/ast.rb
+++ b/lib/puppet/parser/ast.rb
@@ -1,134 +1,138 @@
# the parent class for all of our syntactical objects
require 'puppet'
require 'puppet/util/autoload'
require 'puppet/file_collection/lookup'
# The base class for all of the objects that make up the parse trees.
# Handles things like file name, line #, and also does the initialization
# for all of the parameters of all of the child objects.
class Puppet::Parser::AST
# Do this so I don't have to type the full path in all of the subclasses
AST = Puppet::Parser::AST
include Puppet::FileCollection::Lookup
include Puppet::Util::Errors
include Puppet::Util::MethodHelper
include Puppet::Util::Docs
attr_accessor :parent, :scope
+ def inspect
+ "( #{self.class} #{self.to_s} #{@children.inspect} )"
+ end
+
# don't fetch lexer comment by default
def use_docs
self.class.use_docs
end
# allow our subclass to specify they want documentation
class << self
attr_accessor :use_docs
def associates_doc
self.use_docs = true
end
end
# Does this ast object set something? If so, it gets evaluated first.
def self.settor?
if defined?(@settor)
@settor
else
false
end
end
# Evaluate the current object. Just a stub method, since the subclass
# should override this method.
# of the contained children and evaluates them in turn, returning a
# list of all of the collected values, rejecting nil values
def evaluate(*options)
raise Puppet::DevError, "Did not override #evaluate in #{self.class}"
end
# Throw a parse error.
def parsefail(message)
self.fail(Puppet::ParseError, message)
end
# Wrap a statemp in a reusable way so we always throw a parse error.
def parsewrap
exceptwrap :type => Puppet::ParseError do
yield
end
end
# The version of the evaluate method that should be called, because it
# correctly handles errors. It is critical to use this method because
# it can enable you to catch the error where it happens, rather than
# much higher up the stack.
def safeevaluate(*options)
# We duplicate code here, rather than using exceptwrap, because this
# is called so many times during parsing.
begin
return self.evaluate(*options)
rescue Puppet::Error => detail
raise adderrorcontext(detail)
rescue => detail
error = Puppet::Error.new(detail.to_s)
# We can't use self.fail here because it always expects strings,
# not exceptions.
raise adderrorcontext(error, detail)
end
end
# Initialize the object. Requires a hash as the argument, and
# takes each of the parameters of the hash and calls the settor
# method for them. This is probably pretty inefficient and should
# likely be changed at some point.
def initialize(args)
set_options(args)
end
# evaluate ourselves, and match
def evaluate_match(value, scope)
obj = self.safeevaluate(scope)
obj = obj.downcase if obj.respond_to?(:downcase)
value = value.downcase if value.respond_to?(:downcase)
obj = Puppet::Parser::Scope.number?(obj) || obj
value = Puppet::Parser::Scope.number?(value) || value
# "" == undef for case/selector/if
obj == value or (obj == "" and value == :undef)
end
end
# And include all of the AST subclasses.
require 'puppet/parser/ast/arithmetic_operator'
require 'puppet/parser/ast/astarray'
require 'puppet/parser/ast/asthash'
require 'puppet/parser/ast/branch'
require 'puppet/parser/ast/boolean_operator'
require 'puppet/parser/ast/caseopt'
require 'puppet/parser/ast/casestatement'
require 'puppet/parser/ast/collection'
require 'puppet/parser/ast/collexpr'
require 'puppet/parser/ast/comparison_operator'
require 'puppet/parser/ast/else'
require 'puppet/parser/ast/function'
require 'puppet/parser/ast/ifstatement'
require 'puppet/parser/ast/in_operator'
require 'puppet/parser/ast/leaf'
require 'puppet/parser/ast/match_operator'
require 'puppet/parser/ast/minus'
require 'puppet/parser/ast/nop'
require 'puppet/parser/ast/not'
require 'puppet/parser/ast/resource'
require 'puppet/parser/ast/resource_defaults'
require 'puppet/parser/ast/resource_override'
require 'puppet/parser/ast/resource_reference'
require 'puppet/parser/ast/resourceparam'
require 'puppet/parser/ast/selector'
require 'puppet/parser/ast/tag'
require 'puppet/parser/ast/vardef'
require 'puppet/parser/ast/relationship'
diff --git a/lib/puppet/relationship.rb b/lib/puppet/relationship.rb
index 7079fb44b..08d7d042b 100644
--- a/lib/puppet/relationship.rb
+++ b/lib/puppet/relationship.rb
@@ -1,94 +1,98 @@
#!/usr/bin/env ruby
#
# Created by Luke A. Kanies on 2006-11-24.
# Copyright (c) 2006. All rights reserved.
# subscriptions are permanent associations determining how different
# objects react to an event
require 'puppet/util/pson'
# This is Puppet's class for modeling edges in its configuration graph.
# It used to be a subclass of GRATR::Edge, but that class has weird hash
# overrides that dramatically slow down the graphing.
class Puppet::Relationship
extend Puppet::Util::Pson
attr_accessor :source, :target, :callback
attr_reader :event
def self.from_pson(pson)
source = pson["source"]
target = pson["target"]
args = {}
if event = pson["event"]
args[:event] = event
end
if callback = pson["callback"]
args[:callback] = callback
end
new(source, target, args)
end
def event=(event)
raise ArgumentError, "You must pass a callback for non-NONE events" if event != :NONE and ! callback
@event = event
end
def initialize(source, target, options = {})
@source, @target = source, target
options = (options || {}).inject({}) { |h,a| h[a[0].to_sym] = a[1]; h }
[:callback, :event].each do |option|
if value = options[option]
send(option.to_s + "=", value)
end
end
end
# Does the passed event match our event? This is where the meaning
# of :NONE comes from.
def match?(event)
if self.event.nil? or event == :NONE or self.event == :NONE
return false
elsif self.event == :ALL_EVENTS or event == self.event
return true
else
return false
end
end
def label
result = {}
result[:callback] = callback if callback
result[:event] = event if event
result
end
def ref
"#{source} => #{target}"
end
+ def inspect
+ "{ #{source} => #{target} }"
+ end
+
def to_pson_data_hash
data = {
'source' => source.to_s,
'target' => target.to_s
}
["event", "callback"].each do |attr|
next unless value = send(attr)
data[attr] = value
end
data
end
def to_pson(*args)
to_pson_data_hash.to_pson(*args)
end
def to_s
ref
end
end
diff --git a/lib/puppet/resource.rb b/lib/puppet/resource.rb
index 4f0d50750..b0a3ecee6 100644
--- a/lib/puppet/resource.rb
+++ b/lib/puppet/resource.rb
@@ -1,416 +1,420 @@
require 'puppet'
require 'puppet/util/tagging'
require 'puppet/util/pson'
# The simplest resource class. Eventually it will function as the
# base class for all resource-like behaviour.
class Puppet::Resource
include Puppet::Util::Tagging
require 'puppet/resource/type_collection_helper'
include Puppet::Resource::TypeCollectionHelper
extend Puppet::Util::Pson
include Enumerable
attr_accessor :file, :line, :catalog, :exported, :virtual, :validate_parameters, :strict
attr_reader :type, :title
require 'puppet/indirector'
extend Puppet::Indirector
indirects :resource, :terminus_class => :ral
ATTRIBUTES = [:file, :line, :exported]
def self.from_pson(pson)
raise ArgumentError, "No resource type provided in pson data" unless type = pson['type']
raise ArgumentError, "No resource title provided in pson data" unless title = pson['title']
resource = new(type, title)
if params = pson['parameters']
params.each { |param, value| resource[param] = value }
end
if tags = pson['tags']
tags.each { |tag| resource.tag(tag) }
end
ATTRIBUTES.each do |a|
if value = pson[a.to_s]
resource.send(a.to_s + "=", value)
end
end
resource.exported ||= false
resource
end
+ def inspect
+ "#{@type}[#{@title}]#{to_hash.inspect}"
+ end
+
def to_pson_data_hash
data = ([:type, :title, :tags] + ATTRIBUTES).inject({}) do |hash, param|
next hash unless value = self.send(param)
hash[param.to_s] = value
hash
end
data["exported"] ||= false
params = self.to_hash.inject({}) do |hash, ary|
param, value = ary
# Don't duplicate the title as the namevar
next hash if param == namevar and value == title
hash[param] = Puppet::Resource.value_to_pson_data(value)
hash
end
data["parameters"] = params unless params.empty?
data
end
def self.value_to_pson_data(value)
if value.is_a? Array
value.map{|v| value_to_pson_data(v) }
elsif value.is_a? Puppet::Resource
value.to_s
else
value
end
end
def yaml_property_munge(x)
case x
when Hash
x.inject({}) { |h,kv|
k,v = kv
h[k] = self.class.value_to_pson_data(v)
h
}
else self.class.value_to_pson_data(x)
end
end
def to_pson(*args)
to_pson_data_hash.to_pson(*args)
end
# Proxy these methods to the parameters hash. It's likely they'll
# be overridden at some point, but this works for now.
%w{has_key? keys length delete empty? <<}.each do |method|
define_method(method) do |*args|
@parameters.send(method, *args)
end
end
# Set a given parameter. Converts all passed names
# to lower-case symbols.
def []=(param, value)
validate_parameter(param) if validate_parameters
@parameters[parameter_name(param)] = value
end
# Return a given parameter's value. Converts all passed names
# to lower-case symbols.
def [](param)
@parameters[parameter_name(param)]
end
def ==(other)
return false unless other.respond_to?(:title) and self.type == other.type and self.title == other.title
return false unless to_hash == other.to_hash
true
end
# Compatibility method.
def builtin?
builtin_type?
end
# Is this a builtin resource type?
def builtin_type?
resource_type.is_a?(Class)
end
# Iterate over each param/value pair, as required for Enumerable.
def each
@parameters.each { |p,v| yield p, v }
end
def include?(parameter)
super || @parameters.keys.include?( parameter_name(parameter) )
end
# These two methods are extracted into a Helper
# module, but file load order prevents me
# from including them in the class, and I had weird
# behaviour (i.e., sometimes it didn't work) when
# I directly extended each resource with the helper.
def environment
Puppet::Node::Environment.new(@environment)
end
def environment=(env)
if env.is_a?(String) or env.is_a?(Symbol)
@environment = env
else
@environment = env.name
end
end
%w{exported virtual strict}.each do |m|
define_method(m+"?") do
self.send(m)
end
end
# This stub class is only needed for serialization compatibility with 0.25.x
class Reference
attr_accessor :type,:title
def initialize(type,title)
@type,@title = type,title
end
end
# Create our resource.
def initialize(type, title = nil, attributes = {})
@parameters = {}
# Set things like strictness first.
attributes.each do |attr, value|
next if attr == :parameters
send(attr.to_s + "=", value)
end
@type, @title = extract_type_and_title(type, title)
@type = munge_type_name(@type)
if @type == "Class"
@title = :main if @title == ""
@title = munge_type_name(@title)
end
if params = attributes[:parameters]
extract_parameters(params)
end
tag(self.type)
tag(self.title) if valid_tag?(self.title)
@reference = Reference.new(@type,@title) # for serialization compatibility with 0.25.x
raise ArgumentError, "Invalid resource type #{type}" if strict? and ! resource_type
end
def ref
to_s
end
# Find our resource.
def resolve
return(catalog ? catalog.resource(to_s) : nil)
end
def resource_type
case type
when "Class"; known_resource_types.hostclass(title == :main ? "" : title)
when "Node"; known_resource_types.node(title)
else
Puppet::Type.type(type.to_s.downcase.to_sym) || known_resource_types.definition(type)
end
end
# Produce a simple hash of our parameters.
def to_hash
parse_title.merge @parameters
end
def to_s
"#{type}[#{title}]"
end
def uniqueness_key
# Temporary kludge to deal with inconsistant use patters
h = self.to_hash
h[namevar] ||= h[:name]
h[:name] ||= h[namevar]
h.values_at(*key_attributes.sort_by { |k| k.to_s })
end
def key_attributes
return(resource_type.respond_to? :key_attributes) ? resource_type.key_attributes : [:name]
end
# Convert our resource to Puppet code.
def to_manifest
"%s { '%s':\n%s\n}" % [self.type.to_s.downcase, self.title,
@parameters.collect { |p, v|
if v.is_a? Array
" #{p} => [\'#{v.join("','")}\']"
else
" #{p} => \'#{v}\'"
end
}.join(",\n")
]
end
def to_ref
ref
end
# Convert our resource to a RAL resource instance. Creates component
# instances for resource types that don't exist.
def to_ral
if typeklass = Puppet::Type.type(self.type)
return typeklass.new(self)
else
return Puppet::Type::Component.new(self)
end
end
# Translate our object to a backward-compatible transportable object.
def to_trans
if builtin_type? and type.downcase.to_s != "stage"
result = to_transobject
else
result = to_transbucket
end
result.file = self.file
result.line = self.line
result
end
def to_trans_ref
[type.to_s, title.to_s]
end
# Create an old-style TransObject instance, for builtin resource types.
def to_transobject
# Now convert to a transobject
result = Puppet::TransObject.new(title, type)
to_hash.each do |p, v|
if v.is_a?(Puppet::Resource)
v = v.to_trans_ref
elsif v.is_a?(Array)
v = v.collect { |av|
av = av.to_trans_ref if av.is_a?(Puppet::Resource)
av
}
end
# If the value is an array with only one value, then
# convert it to a single value. This is largely so that
# the database interaction doesn't have to worry about
# whether it returns an array or a string.
result[p.to_s] = if v.is_a?(Array) and v.length == 1
v[0]
else
v
end
end
result.tags = self.tags
result
end
def name
# this is potential namespace conflict
# between the notion of an "indirector name"
# and a "resource name"
[ type, title ].join('/')
end
def to_resource
self
end
def valid_parameter?(name)
resource_type.valid_parameter?(name)
end
def validate_parameter(name)
raise ArgumentError, "Invalid parameter #{name}" unless valid_parameter?(name)
end
private
# Produce a canonical method name.
def parameter_name(param)
param = param.to_s.downcase.to_sym
if param == :name and n = namevar
param = namevar
end
param
end
# The namevar for our resource type. If the type doesn't exist,
# always use :name.
def namevar
if builtin_type? and t = resource_type and t.key_attributes.length == 1
t.key_attributes.first
else
:name
end
end
# Create an old-style TransBucket instance, for non-builtin resource types.
def to_transbucket
bucket = Puppet::TransBucket.new([])
bucket.type = self.type
bucket.name = self.title
# TransBuckets don't support parameters, which is why they're being deprecated.
bucket
end
def extract_parameters(params)
params.each do |param, value|
validate_parameter(param) if strict?
self[param] = value
end
end
def extract_type_and_title(argtype, argtitle)
if (argtitle || argtype) =~ /^([^\[\]]+)\[(.+)\]$/m then [ $1, $2 ]
elsif argtitle then [ argtype, argtitle ]
elsif argtype.is_a?(Puppet::Type) then [ argtype.class.name, argtype.title ]
elsif argtype.is_a?(Hash) then
raise ArgumentError, "Puppet::Resource.new does not take a hash as the first argument. "+
"Did you mean (#{(argtype[:type] || argtype["type"]).inspect}, #{(argtype[:title] || argtype["title"]).inspect }) ?"
else raise ArgumentError, "No title provided and #{argtype.inspect} is not a valid resource reference"
end
end
def munge_type_name(value)
return :main if value == :main
return "Class" if value == "" or value.nil? or value.to_s.downcase == "component"
value.to_s.split("::").collect { |s| s.capitalize }.join("::")
end
def parse_title
h = {}
type = resource_type
if type.respond_to? :title_patterns
type.title_patterns.each { |regexp, symbols_and_lambdas|
if captures = regexp.match(title.to_s)
symbols_and_lambdas.zip(captures[1..-1]).each { |symbol_and_lambda,capture|
sym, lam = symbol_and_lambda
#self[sym] = lam.call(capture)
h[sym] = lam.call(capture)
}
return h
end
}
else
return { :name => title.to_s }
end
end
end
diff --git a/lib/puppet/resource/status.rb b/lib/puppet/resource/status.rb
index 2bdfbbfef..ee83004bb 100644
--- a/lib/puppet/resource/status.rb
+++ b/lib/puppet/resource/status.rb
@@ -1,58 +1,77 @@
module Puppet
class Resource
class Status
include Puppet::Util::Tagging
include Puppet::Util::Logging
- ATTRIBUTES = [:resource, :node, :version, :file, :line, :current_values, :skipped_reason, :status, :evaluation_time, :change_count]
- attr_accessor *ATTRIBUTES
+ attr_accessor :resource, :node, :file, :line, :current_values, :status, :evaluation_time
STATES = [:skipped, :failed, :failed_to_restart, :restarted, :changed, :out_of_sync, :scheduled]
attr_accessor *STATES
attr_reader :source_description, :default_log_level, :time, :resource
+ attr_reader :change_count, :out_of_sync_count, :resource_type, :title
+
+ YAML_ATTRIBUTES = %w{@resource @file @line @evaluation_time @change_count @out_of_sync_count @tags @time @events @out_of_sync @changed @resource_type @title}
# Provide a boolean method for each of the states.
STATES.each do |attr|
define_method("#{attr}?") do
!! send(attr)
end
end
def <<(event)
add_event(event)
self
end
def add_event(event)
@events << event
if event.status == 'failure'
self.failed = true
+ elsif event.status == 'success'
+ @change_count += 1
+ @changed = true
+ end
+ if event.status != 'audit'
+ @out_of_sync_count += 1
+ @out_of_sync = true
end
end
def events
@events
end
def initialize(resource)
@source_description = resource.path
@resource = resource.to_s
+ @change_count = 0
+ @out_of_sync_count = 0
+ @changed = false
+ @out_of_sync = false
- [:file, :line, :version].each do |attr|
+ [:file, :line].each do |attr|
send(attr.to_s + "=", resource.send(attr))
end
tag(*resource.tags)
@time = Time.now
@events = []
+ @resource_type = resource.type.to_s.capitalize
+ @title = resource.title
+ end
+
+ def to_yaml_properties
+ (YAML_ATTRIBUTES & instance_variables).sort
end
private
def log_source
source_description
end
end
end
end
diff --git a/lib/puppet/resource/type_collection.rb b/lib/puppet/resource/type_collection.rb
index 6a03458b3..277d37b18 100644
--- a/lib/puppet/resource/type_collection.rb
+++ b/lib/puppet/resource/type_collection.rb
@@ -1,213 +1,217 @@
class Puppet::Resource::TypeCollection
attr_reader :environment
def clear
@hostclasses.clear
@definitions.clear
@nodes.clear
end
def initialize(env)
@environment = env.is_a?(String) ? Puppet::Node::Environment.new(env) : env
@hostclasses = {}
@definitions = {}
@nodes = {}
# So we can keep a list and match the first-defined regex
@node_list = []
@watched_files = {}
end
+ def inspect
+ "TypeCollection" + { :hostclasses => @hostclasses.keys, :definitions => @definitions.keys, :nodes => @nodes.keys }.inspect
+ end
+
def <<(thing)
add(thing)
self
end
def add(instance)
if instance.type == :hostclass and other = @hostclasses[instance.name] and other.type == :hostclass
other.merge(instance)
return other
end
method = "add_#{instance.type}"
send(method, instance)
instance.resource_type_collection = self
instance
end
def add_hostclass(instance)
dupe_check(instance, @hostclasses) { |dupe| "Class '#{instance.name}' is already defined#{dupe.error_context}; cannot redefine" }
dupe_check(instance, @definitions) { |dupe| "Definition '#{instance.name}' is already defined#{dupe.error_context}; cannot be redefined as a class" }
@hostclasses[instance.name] = instance
instance
end
def hostclass(name)
@hostclasses[munge_name(name)]
end
def add_node(instance)
dupe_check(instance, @nodes) { |dupe| "Node '#{instance.name}' is already defined#{dupe.error_context}; cannot redefine" }
@node_list << instance
@nodes[instance.name] = instance
instance
end
def loader
require 'puppet/parser/type_loader'
@loader ||= Puppet::Parser::TypeLoader.new(environment)
end
def node(name)
name = munge_name(name)
if node = @nodes[name]
return node
end
@node_list.each do |node|
next unless node.name_is_regex?
return node if node.match(name)
end
nil
end
def node_exists?(name)
@nodes[munge_name(name)]
end
def nodes?
@nodes.length > 0
end
def add_definition(instance)
dupe_check(instance, @hostclasses) { |dupe| "'#{instance.name}' is already defined#{dupe.error_context} as a class; cannot redefine as a definition" }
dupe_check(instance, @definitions) { |dupe| "Definition '#{instance.name}' is already defined#{dupe.error_context}; cannot be redefined" }
@definitions[instance.name] = instance
end
def definition(name)
@definitions[munge_name(name)]
end
def find(namespaces, name, type)
#Array("") == [] for some reason
namespaces = [namespaces] unless namespaces.is_a?(Array)
if name =~ /^::/
return send(type, name.sub(/^::/, ''))
end
namespaces.each do |namespace|
ary = namespace.split("::")
while ary.length > 0
tmp_namespace = ary.join("::")
if r = find_partially_qualified(tmp_namespace, name, type)
return r
end
# Delete the second to last object, which reduces our namespace by one.
ary.pop
end
if result = send(type, name)
return result
end
end
nil
end
def find_or_load(namespaces, name, type)
name = name.downcase
namespaces = [namespaces] unless namespaces.is_a?(Array)
namespaces = namespaces.collect { |ns| ns.downcase }
# This could be done in the load_until, but the knowledge seems to
# belong here.
if r = find(namespaces, name, type)
return r
end
loader.load_until(namespaces, name) { find(namespaces, name, type) }
end
def find_node(namespaces, name)
find("", name, :node)
end
def find_hostclass(namespaces, name)
find_or_load(namespaces, name, :hostclass)
end
def find_definition(namespaces, name)
find_or_load(namespaces, name, :definition)
end
[:hostclasses, :nodes, :definitions].each do |m|
define_method(m) do
instance_variable_get("@#{m}").dup
end
end
def perform_initial_import
parser = Puppet::Parser::Parser.new(environment)
if code = Puppet.settings.uninterpolated_value(:code, environment.to_s) and code != ""
parser.string = code
else
file = Puppet.settings.value(:manifest, environment.to_s)
return unless File.exist?(file)
parser.file = file
end
parser.parse
rescue => detail
msg = "Could not parse for environment #{environment}: #{detail}"
error = Puppet::Error.new(msg)
error.set_backtrace(detail.backtrace)
raise error
end
def stale?
@watched_files.values.detect { |file| file.changed? }
end
def version
return @version if defined?(@version)
if environment[:config_version] == ""
@version = Time.now.to_i
return @version
end
@version = Puppet::Util.execute([environment[:config_version]]).strip
rescue Puppet::ExecutionFailure => e
raise Puppet::ParseError, "Unable to set config_version: #{e.message}"
end
def watch_file(file)
@watched_files[file] = Puppet::Util::LoadedFile.new(file)
end
def watching_file?(file)
@watched_files.include?(file)
end
private
def find_partially_qualified(namespace, name, type)
send(type, [namespace, name].join("::"))
end
def munge_name(name)
name.to_s.downcase
end
def dupe_check(instance, hash)
return unless dupe = hash[instance.name]
message = yield dupe
instance.fail Puppet::ParseError, message
end
end
diff --git a/lib/puppet/simple_graph.rb b/lib/puppet/simple_graph.rb
index 55b39fadf..c5dac0f6c 100644
--- a/lib/puppet/simple_graph.rb
+++ b/lib/puppet/simple_graph.rb
@@ -1,448 +1,452 @@
# Created by Luke A. Kanies on 2007-11-07.
# Copyright (c) 2007. All rights reserved.
require 'puppet/external/dot'
require 'puppet/relationship'
require 'set'
# A hopefully-faster graph class to replace the use of GRATR.
class Puppet::SimpleGraph
# An internal class for handling a vertex's edges.
class VertexWrapper
attr_accessor :in, :out, :vertex
# Remove all references to everything.
def clear
@adjacencies[:in].clear
@adjacencies[:out].clear
@vertex = nil
end
def initialize(vertex)
@vertex = vertex
@adjacencies = {:in => {}, :out => {}}
end
# Find adjacent vertices or edges.
def adjacent(options)
direction = options[:direction] || :out
options[:type] ||= :vertices
return send(direction.to_s + "_edges") if options[:type] == :edges
@adjacencies[direction].keys.reject { |vertex| @adjacencies[direction][vertex].empty? }
end
# Add an edge to our list.
def add_edge(direction, edge)
opposite_adjacencies(direction, edge) << edge
end
# Return all known edges.
def edges
in_edges + out_edges
end
# Test whether we share an edge with a given vertex.
def has_edge?(direction, vertex)
return(vertex_adjacencies(direction, vertex).length > 0 ? true : false)
end
# Create methods for returning the degree and edges.
[:in, :out].each do |direction|
# LAK:NOTE If you decide to create methods for directly
# testing the degree, you'll have to get the values and flatten
# the results -- you might have duplicate edges, which can give
# a false impression of what the degree is. That's just
# as expensive as just getting the edge list, so I've decided
# to only add this method.
define_method("#{direction}_edges") do
@adjacencies[direction].values.inject([]) { |total, adjacent| total += adjacent.to_a; total }
end
end
# The other vertex in the edge.
def other_vertex(direction, edge)
case direction
when :in; edge.source
else
edge.target
end
end
# Remove an edge from our list. Assumes that we've already checked
# that the edge is valid.
def remove_edge(direction, edge)
opposite_adjacencies(direction, edge).delete(edge)
end
def to_s
vertex.to_s
end
+ def inspect
+ { :@adjacencies => @adjacencies, :@vertex => @vertex.to_s }.inspect
+ end
+
private
# These methods exist so we don't need a Hash with a default proc.
# Look up the adjacencies for a vertex at the other end of an
# edge.
def opposite_adjacencies(direction, edge)
opposite_vertex = other_vertex(direction, edge)
vertex_adjacencies(direction, opposite_vertex)
end
# Look up the adjacencies for a given vertex.
def vertex_adjacencies(direction, vertex)
@adjacencies[direction][vertex] ||= Set.new
@adjacencies[direction][vertex]
end
end
def initialize
@vertices = {}
@edges = []
end
# Clear our graph.
def clear
@vertices.each { |vertex, wrapper| wrapper.clear }
@vertices.clear
@edges.clear
end
# Which resources a given resource depends upon.
def dependents(resource)
tree_from_vertex(resource).keys
end
# Which resources depend upon the given resource.
def dependencies(resource)
# Cache the reversal graph, because it's somewhat expensive
# to create.
@reversal ||= reversal
# Strangely, it's significantly faster to search a reversed
# tree in the :out direction than to search a normal tree
# in the :in direction.
@reversal.tree_from_vertex(resource, :out).keys
end
# Whether our graph is directed. Always true. Used to produce dot files.
def directed?
true
end
# Determine all of the leaf nodes below a given vertex.
def leaves(vertex, direction = :out)
tree = tree_from_vertex(vertex, direction)
l = tree.keys.find_all { |c| adjacent(c, :direction => direction).empty? }
end
# Collect all of the edges that the passed events match. Returns
# an array of edges.
def matching_edges(event, base = nil)
source = base || event.resource
unless vertex?(source)
Puppet.warning "Got an event from invalid vertex #{source.ref}"
return []
end
# Get all of the edges that this vertex should forward events
# to, which is the same thing as saying all edges directly below
# This vertex in the graph.
adjacent(source, :direction => :out, :type => :edges).find_all do |edge|
edge.match?(event.name)
end
end
# Return a reversed version of this graph.
def reversal
result = self.class.new
vertices.each { |vertex| result.add_vertex(vertex) }
edges.each do |edge|
newedge = edge.class.new(edge.target, edge.source, edge.label)
result.add_edge(newedge)
end
result
end
# Return the size of the graph.
def size
@vertices.length
end
# Return the graph as an array.
def to_a
@vertices.keys
end
# Provide a topological sort.
def topsort
degree = {}
zeros = []
result = []
# Collect each of our vertices, with the number of in-edges each has.
@vertices.each do |name, wrapper|
edges = wrapper.in_edges
zeros << wrapper if edges.length == 0
degree[wrapper.vertex] = edges
end
# Iterate over each 0-degree vertex, decrementing the degree of
# each of its out-edges.
while wrapper = zeros.pop
result << wrapper.vertex
wrapper.out_edges.each do |edge|
degree[edge.target].delete(edge)
zeros << @vertices[edge.target] if degree[edge.target].length == 0
end
end
# If we have any vertices left with non-zero in-degrees, then we've found a cycle.
if cycles = degree.find_all { |vertex, edges| edges.length > 0 } and cycles.length > 0
message = cycles.collect { |vertex, edges| edges.collect { |e| e.to_s }.join(", ") }.join(", ")
raise Puppet::Error, "Found dependency cycles in the following relationships: #{message}; try using the '--graph' option and open the '.dot' files in OmniGraffle or GraphViz"
end
result
end
# Add a new vertex to the graph.
def add_vertex(vertex)
@reversal = nil
return false if vertex?(vertex)
setup_vertex(vertex)
true # don't return the VertexWrapper instance.
end
# Remove a vertex from the graph.
def remove_vertex!(vertex)
return nil unless vertex?(vertex)
@vertices[vertex].edges.each { |edge| remove_edge!(edge) }
@edges -= @vertices[vertex].edges
@vertices[vertex].clear
@vertices.delete(vertex)
end
# Test whether a given vertex is in the graph.
def vertex?(vertex)
@vertices.include?(vertex)
end
# Return a list of all vertices.
def vertices
@vertices.keys
end
# Add a new edge. The graph user has to create the edge instance,
# since they have to specify what kind of edge it is.
def add_edge(source, target = nil, label = nil)
@reversal = nil
if target
edge = Puppet::Relationship.new(source, target, label)
else
edge = source
end
[edge.source, edge.target].each { |vertex| setup_vertex(vertex) unless vertex?(vertex) }
@vertices[edge.source].add_edge :out, edge
@vertices[edge.target].add_edge :in, edge
@edges << edge
true
end
# Find a matching edge. Note that this only finds the first edge,
# not all of them or whatever.
def edge(source, target)
@edges.each_with_index { |test_edge, index| return test_edge if test_edge.source == source and test_edge.target == target }
end
def edge_label(source, target)
return nil unless edge = edge(source, target)
edge.label
end
# Is there an edge between the two vertices?
def edge?(source, target)
return false unless vertex?(source) and vertex?(target)
@vertices[source].has_edge?(:out, target)
end
def edges
@edges.dup
end
# Remove an edge from our graph.
def remove_edge!(edge)
@vertices[edge.source].remove_edge(:out, edge)
@vertices[edge.target].remove_edge(:in, edge)
@edges.delete(edge)
nil
end
# Find adjacent edges.
def adjacent(vertex, options = {})
return [] unless wrapper = @vertices[vertex]
wrapper.adjacent(options)
end
private
# An internal method that skips the validation, so we don't have
# duplicate validation calls.
def setup_vertex(vertex)
@vertices[vertex] = VertexWrapper.new(vertex)
end
public
# # For some reason, unconnected vertices do not show up in
# # this graph.
# def to_jpg(path, name)
# gv = vertices
# Dir.chdir(path) do
# induced_subgraph(gv).write_to_graphic_file('jpg', name)
# end
# end
# Take container information from another graph and use it
# to replace any container vertices with their respective leaves.
# This creates direct relationships where there were previously
# indirect relationships through the containers.
def splice!(other, type)
# We have to get the container list via a topological sort on the
# configuration graph, because otherwise containers that contain
# other containers will add those containers back into the
# graph. We could get a similar affect by only setting relationships
# to container leaves, but that would result in many more
# relationships.
stage_class = Puppet::Type.type(:stage)
whit_class = Puppet::Type.type(:whit)
containers = other.topsort.find_all { |v| (v.is_a?(type) or v.is_a?(stage_class)) and vertex?(v) }
containers.each do |container|
# Get the list of children from the other graph.
children = other.adjacent(container, :direction => :out)
# MQR TODO: Luke suggests that it should be possible to refactor the system so that
# container nodes are retained, thus obviating the need for the whit.
children = [whit_class.new(:name => container.name, :catalog => other)] if children.empty?
# First create new edges for each of the :in edges
[:in, :out].each do |dir|
edges = adjacent(container, :direction => dir, :type => :edges)
edges.each do |edge|
children.each do |child|
if dir == :in
s = edge.source
t = child
else
s = child
t = edge.target
end
add_edge(s, t, edge.label)
end
# Now get rid of the edge, so remove_vertex! works correctly.
remove_edge!(edge)
end
end
remove_vertex!(container)
end
end
# Just walk the tree and pass each edge.
def walk(source, direction)
# Use an iterative, breadth-first traversal of the graph. One could do
# this recursively, but Ruby's slow function calls and even slower
# recursion make the shorter, recursive algorithm cost-prohibitive.
stack = [source]
seen = Set.new
until stack.empty?
node = stack.shift
next if seen.member? node
connected = adjacent(node, :direction => direction)
connected.each do |target|
yield node, target
end
stack.concat(connected)
seen << node
end
end
# A different way of walking a tree, and a much faster way than the
# one that comes with GRATR.
def tree_from_vertex(start, direction = :out)
predecessor={}
walk(start, direction) do |parent, child|
predecessor[child] = parent
end
predecessor
end
# LAK:FIXME This is just a paste of the GRATR code with slight modifications.
# Return a DOT::DOTDigraph for directed graphs or a DOT::DOTSubgraph for an
# undirected Graph. _params_ can contain any graph property specified in
# rdot.rb. If an edge or vertex label is a kind of Hash then the keys
# which match +dot+ properties will be used as well.
def to_dot_graph (params = {})
params['name'] ||= self.class.name.gsub(/:/,'_')
fontsize = params['fontsize'] ? params['fontsize'] : '8'
graph = (directed? ? DOT::DOTDigraph : DOT::DOTSubgraph).new(params)
edge_klass = directed? ? DOT::DOTDirectedEdge : DOT::DOTEdge
vertices.each do |v|
name = v.to_s
params = {'name' => '"'+name+'"',
'fontsize' => fontsize,
'label' => name}
v_label = v.to_s
params.merge!(v_label) if v_label and v_label.kind_of? Hash
graph << DOT::DOTNode.new(params)
end
edges.each do |e|
params = {'from' => '"'+ e.source.to_s + '"',
'to' => '"'+ e.target.to_s + '"',
'fontsize' => fontsize }
e_label = e.to_s
params.merge!(e_label) if e_label and e_label.kind_of? Hash
graph << edge_klass.new(params)
end
graph
end
# Output the dot format as a string
def to_dot (params={}) to_dot_graph(params).to_s; end
# Call +dotty+ for the graph which is written to the file 'graph.dot'
# in the # current directory.
def dotty (params = {}, dotfile = 'graph.dot')
File.open(dotfile, 'w') {|f| f << to_dot(params) }
system('dotty', dotfile)
end
# Use +dot+ to create a graphical representation of the graph. Returns the
# filename of the graphics file.
def write_to_graphic_file (fmt='png', dotfile='graph')
src = dotfile + '.dot'
dot = dotfile + '.' + fmt
File.open(src, 'w') {|f| f << self.to_dot << "\n"}
system( "dot -T#{fmt} #{src} -o #{dot}" )
dot
end
# Produce the graph files if requested.
def write_graph(name)
return unless Puppet[:graph]
Puppet.settings.use(:graphing)
file = File.join(Puppet[:graphdir], "#{name}.dot")
File.open(file, "w") { |f|
f.puts to_dot("name" => name.to_s.capitalize)
}
end
end
diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb
index dcd9aad0a..aa650eea1 100644
--- a/lib/puppet/transaction.rb
+++ b/lib/puppet/transaction.rb
@@ -1,361 +1,334 @@
# the class that actually walks our resource/property tree, collects the changes,
# and performs them
require 'puppet'
require 'puppet/util/tagging'
require 'puppet/application'
class Puppet::Transaction
- require 'puppet/transaction/change'
require 'puppet/transaction/event'
require 'puppet/transaction/event_manager'
require 'puppet/transaction/resource_harness'
require 'puppet/resource/status'
attr_accessor :component, :catalog, :ignoreschedules
attr_accessor :sorted_resources, :configurator
# The report, once generated.
attr_accessor :report
# Routes and stores any events and subscriptions.
attr_reader :event_manager
# Handles most of the actual interacting with resources
attr_reader :resource_harness
include Puppet::Util
include Puppet::Util::Tagging
# Wraps application run state check to flag need to interrupt processing
def stop_processing?
Puppet::Application.stop_requested?
end
# Add some additional times for reporting
def add_times(hash)
hash.each do |name, num|
report.add_times(name, num)
end
end
# Are there any failed resources in this transaction?
def any_failed?
report.resource_statuses.values.detect { |status| status.failed? }
end
# Apply all changes for a resource
def apply(resource, ancestor = nil)
status = resource_harness.evaluate(resource)
add_resource_status(status)
event_manager.queue_events(ancestor || resource, status.events)
rescue => detail
resource.err "Could not evaluate: #{detail}"
end
# Find all of the changed resources.
def changed?
report.resource_statuses.values.find_all { |status| status.changed }.collect { |status| catalog.resource(status.resource) }
end
# Copy an important relationships from the parent to the newly-generated
# child resource.
def make_parent_child_relationship(resource, children)
depthfirst = resource.depthfirst?
children.each do |gen_child|
if depthfirst
edge = [gen_child, resource]
else
edge = [resource, gen_child]
end
relationship_graph.add_vertex(gen_child)
unless relationship_graph.edge?(edge[1], edge[0])
relationship_graph.add_edge(*edge)
else
resource.debug "Skipping automatic relationship to #{gen_child}"
end
end
end
# See if the resource generates new resources at evaluation time.
def eval_generate(resource)
generate_additional_resources(resource, :eval_generate)
end
# Evaluate a single resource.
def eval_resource(resource, ancestor = nil)
if skip?(resource)
resource_status(resource).skipped = true
else
eval_children_and_apply_resource(resource, ancestor)
end
# Check to see if there are any events queued for this resource
event_manager.process_events(resource)
end
def eval_children_and_apply_resource(resource, ancestor = nil)
resource_status(resource).scheduled = true
# We need to generate first regardless, because the recursive
# actions sometimes change how the top resource is applied.
children = eval_generate(resource)
if ! children.empty? and resource.depthfirst?
children.each do |child|
# The child will never be skipped when the parent isn't
eval_resource(child, ancestor || resource)
end
end
# Perform the actual changes
apply(resource, ancestor)
if ! children.empty? and ! resource.depthfirst?
children.each do |child|
eval_resource(child, ancestor || resource)
end
end
end
# This method does all the actual work of running a transaction. It
# collects all of the changes, executes them, and responds to any
# necessary events.
def evaluate
# Start logging.
Puppet::Util::Log.newdestination(@report)
prepare
Puppet.info "Applying configuration version '#{catalog.version}'" if catalog.version
begin
@sorted_resources.each do |resource|
next if stop_processing?
if resource.is_a?(Puppet::Type::Component)
Puppet.warning "Somehow left a component in the relationship graph"
next
end
ret = nil
seconds = thinmark do
ret = eval_resource(resource)
end
resource.info "Evaluated in %0.2f seconds" % seconds if Puppet[:evaltrace] and @catalog.host_config?
ret
end
ensure
# And then close the transaction log.
Puppet::Util::Log.close(@report)
end
Puppet.debug "Finishing transaction #{object_id}"
end
def events
event_manager.events
end
def failed?(resource)
s = resource_status(resource) and s.failed?
end
# Does this resource have any failed dependencies?
def failed_dependencies?(resource)
# First make sure there are no failed dependencies. To do this,
# we check for failures in any of the vertexes above us. It's not
# enough to check the immediate dependencies, which is why we use
# a tree from the reversed graph.
found_failed = false
relationship_graph.dependencies(resource).each do |dep|
next unless failed?(dep)
resource.notice "Dependency #{dep} has failures: #{resource_status(dep).failed}"
found_failed = true
end
found_failed
end
# A general method for recursively generating new resources from a
# resource.
def generate_additional_resources(resource, method)
return [] unless resource.respond_to?(method)
begin
made = resource.send(method)
rescue => detail
puts detail.backtrace if Puppet[:trace]
resource.err "Failed to generate additional resources using '#{method}': #{detail}"
end
return [] unless made
made = [made] unless made.is_a?(Array)
made.uniq.find_all do |res|
begin
res.tag(*resource.tags)
@catalog.add_resource(res) do |r|
r.finish
make_parent_child_relationship(resource, [r])
# Call 'generate' recursively
generate_additional_resources(r, method)
end
true
rescue Puppet::Resource::Catalog::DuplicateResourceError
res.info "Duplicate generated resource; skipping"
false
end
end
end
# Collect any dynamically generated resources. This method is called
# before the transaction starts.
def generate
list = @catalog.vertices
newlist = []
while ! list.empty?
list.each do |resource|
newlist += generate_additional_resources(resource, :generate)
end
list = newlist
newlist = []
end
end
- # Generate a transaction report.
- def generate_report
- @report.calculate_metrics
- @report
- end
-
# Should we ignore tags?
def ignore_tags?
! (@catalog.host_config? or Puppet[:name] == "puppet")
end
# this should only be called by a Puppet::Type::Component resource now
# and it should only receive an array
def initialize(catalog)
@catalog = catalog
- @report = Report.new
+ @report = Report.new("apply", catalog.version)
@event_manager = Puppet::Transaction::EventManager.new(self)
@resource_harness = Puppet::Transaction::ResourceHarness.new(self)
end
# Prefetch any providers that support it. We don't support prefetching
# types, just providers.
def prefetch
prefetchers = {}
@catalog.vertices.each do |resource|
if provider = resource.provider and provider.class.respond_to?(:prefetch)
prefetchers[provider.class] ||= {}
prefetchers[provider.class][resource.name] = resource
end
end
# Now call prefetch, passing in the resources so that the provider instances can be replaced.
prefetchers.each do |provider, resources|
Puppet.debug "Prefetching #{provider.name} resources for #{provider.resource_type.name}"
begin
provider.prefetch(resources)
rescue => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Could not prefetch #{provider.resource_type.name} provider '#{provider.name}': #{detail}"
end
end
end
# Prepare to evaluate the resources in a transaction.
def prepare
# Now add any dynamically generated resources
generate
# Then prefetch. It's important that we generate and then prefetch,
# so that any generated resources also get prefetched.
prefetch
# This will throw an error if there are cycles in the graph.
@sorted_resources = relationship_graph.topsort
end
def relationship_graph
catalog.relationship_graph
end
- # Send off the transaction report.
- def send_report
- begin
- report = generate_report
- rescue => detail
- Puppet.err "Could not generate report: #{detail}"
- return
- end
-
- puts report.summary if Puppet[:summarize]
-
- if Puppet[:report]
- begin
- report.save
- rescue => detail
- Puppet.err "Reporting failed: #{detail}"
- end
- end
- end
-
def add_resource_status(status)
report.add_resource_status status
end
def resource_status(resource)
report.resource_statuses[resource.to_s] || add_resource_status(Puppet::Resource::Status.new(resource))
end
# Is the resource currently scheduled?
def scheduled?(resource)
self.ignoreschedules or resource_harness.scheduled?(resource_status(resource), resource)
end
# Should this resource be skipped?
def skip?(resource)
if missing_tags?(resource)
resource.debug "Not tagged with #{tags.join(", ")}"
elsif ! scheduled?(resource)
resource.debug "Not scheduled"
elsif failed_dependencies?(resource)
resource.warning "Skipping because of failed dependencies"
elsif resource.virtual?
resource.debug "Skipping because virtual"
else
return false
end
true
end
# The tags we should be checking.
def tags
self.tags = Puppet[:tags] unless defined?(@tags)
super
end
def handle_qualified_tags( qualified )
# The default behavior of Puppet::Util::Tagging is
# to split qualified tags into parts. That would cause
# qualified tags to match too broadly here.
return
end
# Is this resource tagged appropriately?
def missing_tags?(resource)
return false if ignore_tags?
return false if tags.empty?
not resource.tagged?(*tags)
end
end
require 'puppet/transaction/report'
diff --git a/lib/puppet/transaction/change.rb b/lib/puppet/transaction/change.rb
deleted file mode 100644
index d57ac1917..000000000
--- a/lib/puppet/transaction/change.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-require 'puppet/transaction'
-require 'puppet/transaction/event'
-
-# Handle all of the work around performing an actual change,
-# including calling 'sync' on the properties and producing events.
-class Puppet::Transaction::Change
- attr_accessor :is, :should, :property, :proxy, :auditing, :old_audit_value
-
- def auditing?
- auditing
- end
-
- def initialize(property, currentvalue)
- @property = property
- @is = currentvalue
-
- @should = property.should
-
- @changed = false
- end
-
- def apply
- event = property.event
- event.previous_value = is
- event.desired_value = should
- event.historical_value = old_audit_value
-
- if auditing? and old_audit_value != is
- event.message = "audit change: previously recorded value #{property.is_to_s(old_audit_value)} has been changed to #{property.is_to_s(is)}"
- event.status = "audit"
- event.audited = true
- brief_audit_message = " (previously recorded value was #{property.is_to_s(old_audit_value)})"
- else
- brief_audit_message = ""
- end
-
- if property.insync?(is)
- # nothing happens
- elsif noop?
- event.message = "is #{property.is_to_s(is)}, should be #{property.should_to_s(should)} (noop)#{brief_audit_message}"
- event.status = "noop"
- else
- property.sync
- event.message = [ property.change_to_s(is, should), brief_audit_message ].join
- event.status = "success"
- end
- event
- rescue => detail
- puts detail.backtrace if Puppet[:trace]
- event.status = "failure"
-
- event.message = "change from #{property.is_to_s(is)} to #{property.should_to_s(should)} failed: #{detail}"
- event
- ensure
- event.send_log
- end
-
- # Is our property noop? This is used for generating special events.
- def noop?
- @property.noop
- end
-
- # The resource that generated this change. This is used for handling events,
- # and the proxy resource is used for generated resources, since we can't
- # send an event to a resource we don't have a direct relationship with. If we
- # have a proxy resource, then the events will be considered to be from
- # that resource, rather than us, so the graph resolution will still work.
- def resource
- self.proxy || @property.resource
- end
-
- def to_s
- "change #{@property.change_to_s(@is, @should)}"
- end
-end
diff --git a/lib/puppet/transaction/event.rb b/lib/puppet/transaction/event.rb
index da5b14727..cd695cff8 100644
--- a/lib/puppet/transaction/event.rb
+++ b/lib/puppet/transaction/event.rb
@@ -1,61 +1,66 @@
require 'puppet/transaction'
require 'puppet/util/tagging'
require 'puppet/util/logging'
# A simple struct for storing what happens on the system.
class Puppet::Transaction::Event
include Puppet::Util::Tagging
include Puppet::Util::Logging
- ATTRIBUTES = [:name, :resource, :property, :previous_value, :desired_value, :historical_value, :status, :message, :node, :version, :file, :line, :source_description, :audited]
+ ATTRIBUTES = [:name, :resource, :property, :previous_value, :desired_value, :historical_value, :status, :message, :file, :line, :source_description, :audited]
+ YAML_ATTRIBUTES = %w{@audited @property @previous_value @desired_value @historical_value @message @name @status @time}
attr_accessor *ATTRIBUTES
attr_writer :tags
attr_accessor :time
attr_reader :default_log_level
EVENT_STATUSES = %w{noop success failure audit}
- def initialize(*args)
- options = args.last.is_a?(Hash) ? args.pop : ATTRIBUTES.inject({}) { |hash, attr| hash[attr] = args.pop; hash }
- options.each { |attr, value| send(attr.to_s + "=", value) unless value.nil? }
+ def initialize(options = {})
+ @audited = false
+ options.each { |attr, value| send(attr.to_s + "=", value) }
@time = Time.now
end
def property=(prop)
@property = prop.to_s
end
def resource=(res)
if res.respond_to?(:[]) and level = res[:loglevel]
@default_log_level = level
end
@resource = res.to_s
end
def send_log
super(log_level, message)
end
def status=(value)
raise ArgumentError, "Event status can only be #{EVENT_STATUSES.join(', ')}" unless EVENT_STATUSES.include?(value)
@status = value
end
def to_s
message
end
+ def to_yaml_properties
+ (YAML_ATTRIBUTES & instance_variables).sort
+ end
+
private
# If it's a failure, use 'err', else use either the resource's log level (if available)
# or 'notice'.
def log_level
status == "failure" ? :err : (@default_log_level || :notice)
end
# Used by the Logging module
def log_source
source_description || property || resource
end
end
diff --git a/lib/puppet/transaction/report.rb b/lib/puppet/transaction/report.rb
index 75c08fc7a..8e04759ad 100644
--- a/lib/puppet/transaction/report.rb
+++ b/lib/puppet/transaction/report.rb
@@ -1,150 +1,169 @@
require 'puppet'
require 'puppet/indirector'
# A class for reporting what happens on each client. Reports consist of
# two types of data: Logs and Metrics. Logs are the output that each
# change produces, and Metrics are all of the numerical data involved
# in the transaction.
class Puppet::Transaction::Report
extend Puppet::Indirector
indirects :report, :terminus_class => :processor
- attr_reader :resource_statuses, :logs, :metrics, :host, :time, :kind
+ attr_accessor :configuration_version
+ attr_reader :resource_statuses, :logs, :metrics, :host, :time, :kind, :status
# This is necessary since Marshall doesn't know how to
# dump hash with default proc (see below @records)
def self.default_format
:yaml
end
def <<(msg)
@logs << msg
self
end
def add_times(name, value)
@external_times[name] = value
end
def add_metric(name, hash)
metric = Puppet::Util::Metric.new(name)
hash.each do |name, value|
metric.newvalue(name, value)
end
@metrics[metric.name] = metric
metric
end
def add_resource_status(status)
@resource_statuses[status.resource] = status
end
- def calculate_metrics
- calculate_resource_metrics
- calculate_time_metrics
- calculate_change_metrics
- calculate_event_metrics
+ def compute_status(resource_metrics, change_metric)
+ if (resource_metrics["failed"] || 0) > 0
+ 'failed'
+ elsif change_metric > 0
+ 'changed'
+ else
+ 'unchanged'
+ end
+ end
+
+ def finalize_report
+ resource_metrics = add_metric(:resources, calculate_resource_metrics)
+ add_metric(:time, calculate_time_metrics)
+ change_metric = calculate_change_metric
+ add_metric(:changes, {"total" => change_metric})
+ add_metric(:events, calculate_event_metrics)
+ @status = compute_status(resource_metrics, change_metric)
end
- def initialize(kind = "apply")
+ def initialize(kind, configuration_version=nil)
@metrics = {}
@logs = []
@resource_statuses = {}
@external_times ||= {}
@host = Puppet[:certname]
@time = Time.now
@kind = kind
+ @report_format = 2
+ @puppet_version = Puppet.version
+ @configuration_version = configuration_version
+ @status = 'failed' # assume failed until the report is finalized
end
def name
host
end
# Provide a summary of this report.
def summary
ret = ""
@metrics.sort { |a,b| a[1].label <=> b[1].label }.each do |name, metric|
ret += "#{metric.label}:\n"
metric.values.sort { |a,b|
# sort by label
if a[0] == :total
1
elsif b[0] == :total
-1
else
a[1] <=> b[1]
end
}.each do |name, label, value|
next if value == 0
value = "%0.2f" % value if value.is_a?(Float)
ret += " %15s %s\n" % [label + ":", value]
end
end
ret
end
# Based on the contents of this report's metrics, compute a single number
# that represents the report. The resulting number is a bitmask where
# individual bits represent the presence of different metrics.
def exit_status
status = 0
- status |= 2 if @metrics["changes"][:total] > 0
- status |= 4 if @metrics["resources"][:failed] > 0
+ status |= 2 if @metrics["changes"]["total"] > 0
+ status |= 4 if @metrics["resources"]["failed"] > 0
status
end
- private
+ def to_yaml_properties
+ (instance_variables - ["@external_times"]).sort
+ end
- def calculate_change_metrics
- metrics = Hash.new(0)
- resource_statuses.each do |name, status|
- metrics[:total] += status.change_count if status.change_count
- end
+ private
- add_metric(:changes, metrics)
+ def calculate_change_metric
+ resource_statuses.map { |name, status| status.change_count || 0 }.inject(0) { |a,b| a+b }
end
def calculate_event_metrics
metrics = Hash.new(0)
+ metrics["total"] = 0
resource_statuses.each do |name, status|
- metrics[:total] += status.events.length
+ metrics["total"] += status.events.length
status.events.each do |event|
metrics[event.status] += 1
end
end
- add_metric(:events, metrics)
+ metrics
end
def calculate_resource_metrics
metrics = Hash.new(0)
- metrics[:total] = resource_statuses.length
+ metrics["total"] = resource_statuses.length
resource_statuses.each do |name, status|
Puppet::Resource::Status::STATES.each do |state|
- metrics[state] += 1 if status.send(state)
+ metrics[state.to_s] += 1 if status.send(state)
end
end
- add_metric(:resources, metrics)
+ metrics
end
def calculate_time_metrics
metrics = Hash.new(0)
resource_statuses.each do |name, status|
type = Puppet::Resource.new(name).type
metrics[type.to_s.downcase] += status.evaluation_time if status.evaluation_time
end
@external_times.each do |name, value|
metrics[name.to_s.downcase] = value
end
- add_metric(:time, metrics)
+ metrics["total"] = metrics.values.inject(0) { |a,b| a+b }
+
+ metrics
end
end
diff --git a/lib/puppet/transaction/resource_harness.rb b/lib/puppet/transaction/resource_harness.rb
index c978e5545..c259d3e05 100644
--- a/lib/puppet/transaction/resource_harness.rb
+++ b/lib/puppet/transaction/resource_harness.rb
@@ -1,152 +1,193 @@
require 'puppet/resource/status'
class Puppet::Transaction::ResourceHarness
extend Forwardable
def_delegators :@transaction, :relationship_graph
attr_reader :transaction
def allow_changes?(resource)
- return true unless resource.purging? and resource.deleting?
- return true unless deps = relationship_graph.dependents(resource) and ! deps.empty? and deps.detect { |d| ! d.deleting? }
-
- deplabel = deps.collect { |r| r.ref }.join(",")
- plurality = deps.length > 1 ? "":"s"
- resource.warning "#{deplabel} still depend#{plurality} on me -- not purging"
- false
- end
-
- def apply_changes(status, changes)
- changes.each do |change|
- status << change.apply
-
- cache(change.property.resource, change.property.name, change.is) if change.auditing?
+ if resource.purging? and resource.deleting? and deps = relationship_graph.dependents(resource) \
+ and ! deps.empty? and deps.detect { |d| ! d.deleting? }
+ deplabel = deps.collect { |r| r.ref }.join(",")
+ plurality = deps.length > 1 ? "":"s"
+ resource.warning "#{deplabel} still depend#{plurality} on me -- not purging"
+ false
+ else
+ true
end
- status.changed = true
end
# Used mostly for scheduling and auditing at this point.
def cached(resource, name)
Puppet::Util::Storage.cache(resource)[name]
end
# Used mostly for scheduling and auditing at this point.
def cache(resource, name, value)
Puppet::Util::Storage.cache(resource)[name] = value
end
- def changes_to_perform(status, resource)
+ def perform_changes(resource)
current = resource.retrieve_resource
cache resource, :checked, Time.now
return [] if ! allow_changes?(resource)
- audited = copy_audited_parameters(resource, current)
+ current_values = current.to_hash
+ historical_values = Puppet::Util::Storage.cache(resource).dup
+ desired_values = resource.to_resource.to_hash
+ audited_params = (resource[:audit] || []).map { |p| p.to_sym }
+ synced_params = []
- if param = resource.parameter(:ensure)
- return [] if absent_and_not_being_created?(current, param)
- unless ensure_is_insync?(current, param)
- audited.keys.reject{|name| name == :ensure}.each do |name|
- resource.parameter(name).notice "audit change: previously recorded value #{audited[name]} has been changed to #{current[param]}"
- cache(resource, name, current[param])
+ # Record the current state in state.yml.
+ audited_params.each do |param|
+ cache(resource, param, current_values[param])
+ end
+
+ # Update the machine state & create logs/events
+ events = []
+ ensure_param = resource.parameter(:ensure)
+ if desired_values[:ensure] && !ensure_param.insync?(current_values[:ensure])
+ events << apply_parameter(ensure_param, current_values[:ensure], audited_params.include?(:ensure), historical_values[:ensure])
+ synced_params << :ensure
+ elsif current_values[:ensure] != :absent
+ work_order = resource.properties # Note: only the resource knows what order to apply changes in
+ work_order.each do |param|
+ if !param.insync?(current_values[param.name])
+ events << apply_parameter(param, current_values[param.name], audited_params.include?(param.name), historical_values[param.name])
+ synced_params << param.name
end
- return [Puppet::Transaction::Change.new(param, current[:ensure])]
end
- return [] if ensure_should_be_absent?(current, param)
end
- resource.properties.reject { |param| param.name == :ensure }.select do |param|
- (audited.include?(param.name) && audited[param.name] != current[param.name]) || (param.should != nil && !param_is_insync?(current, param))
- end.collect do |param|
- change = Puppet::Transaction::Change.new(param, current[param.name])
- change.auditing = true if audited.include?(param.name)
- change.old_audit_value = audited[param.name]
- change
+ # Add more events to capture audit results
+ audited_params.each do |param_name|
+ if historical_values.include?(param_name)
+ if historical_values[param_name] != current_values[param_name] && !synced_params.include?(param_name)
+ event = create_change_event(resource.parameter(param_name), current_values[param_name], true, historical_values[param_name])
+ event.send_log
+ events << event
+ end
+ else
+ resource.property(param_name).notice "audit change: newly-recorded value #{current_values[param_name]}"
+ end
end
+
+ events
end
- def copy_audited_parameters(resource, current)
- return {} unless audit = resource[:audit]
- audit = Array(audit).collect { |p| p.to_sym }
- audited = {}
- audit.find_all do |param|
- if value = cached(resource, param)
- audited[param] = value
- else
- resource.property(param).notice "audit change: newly-recorded recorded value #{current[param]}"
- cache(resource, param, current[param])
+ def create_change_event(property, current_value, do_audit, historical_value)
+ event = property.event
+ event.previous_value = current_value
+ event.desired_value = property.should
+ event.historical_value = historical_value
+
+ if do_audit
+ event.audited = true
+ event.status = "audit"
+ if historical_value != current_value
+ event.message = "audit change: previously recorded value #{property.is_to_s(historical_value)} has been changed to #{property.is_to_s(current_value)}"
end
end
- audited
+ event
+ end
+
+ def apply_parameter(property, current_value, do_audit, historical_value)
+ event = create_change_event(property, current_value, do_audit, historical_value)
+
+ if do_audit && historical_value && historical_value != current_value
+ brief_audit_message = " (previously recorded value was #{property.is_to_s(historical_value)})"
+ else
+ brief_audit_message = ""
+ end
+
+ if property.noop
+ event.message = "current_value #{property.is_to_s(current_value)}, should be #{property.should_to_s(property.should)} (noop)#{brief_audit_message}"
+ event.status = "noop"
+ else
+ property.sync
+ event.message = [ property.change_to_s(current_value, property.should), brief_audit_message ].join
+ event.status = "success"
+ end
+ event
+ rescue => detail
+ puts detail.backtrace if Puppet[:trace]
+ event.status = "failure"
+
+ event.message = "change from #{property.is_to_s(current_value)} to #{property.should_to_s(property.should)} failed: #{detail}"
+ event
+ ensure
+ event.send_log
end
def evaluate(resource)
start = Time.now
status = Puppet::Resource::Status.new(resource)
- if changes = changes_to_perform(status, resource) and ! changes.empty?
- status.out_of_sync = true
- status.change_count = changes.length
- apply_changes(status, changes)
- if ! resource.noop?
- cache(resource, :synced, Time.now)
- resource.flush if resource.respond_to?(:flush)
- end
+ perform_changes(resource).each do |event|
+ status << event
end
+
+ if status.changed? && ! resource.noop?
+ cache(resource, :synced, Time.now)
+ resource.flush if resource.respond_to?(:flush)
+ end
+
return status
rescue => detail
resource.fail "Could not create resource status: #{detail}" unless status
puts detail.backtrace if Puppet[:trace]
resource.err "Could not evaluate: #{detail}"
status.failed = true
return status
ensure
(status.evaluation_time = Time.now - start) if status
end
def initialize(transaction)
@transaction = transaction
end
def scheduled?(status, resource)
return true if Puppet[:ignoreschedules]
return true unless schedule = schedule(resource)
# We use 'checked' here instead of 'synced' because otherwise we'll
# end up checking most resources most times, because they will generally
# have been synced a long time ago (e.g., a file only gets updated
# once a month on the server and its schedule is daily; the last sync time
# will have been a month ago, so we'd end up checking every run).
schedule.match?(cached(resource, :checked).to_i)
end
def schedule(resource)
unless resource.catalog
resource.warning "Cannot schedule without a schedule-containing catalog"
return nil
end
return nil unless name = resource[:schedule]
resource.catalog.resource(:schedule, name) || resource.fail("Could not find schedule #{name}")
end
private
def absent_and_not_being_created?(current, param)
current[:ensure] == :absent and param.should.nil?
end
def ensure_is_insync?(current, param)
param.insync?(current[:ensure])
end
def ensure_should_be_absent?(current, param)
param.should == :absent
end
def param_is_insync?(current, param)
param.insync?(current[param.name])
end
end
diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb
index 1b6e7dcd7..ea3944b4e 100644
--- a/lib/puppet/type.rb
+++ b/lib/puppet/type.rb
@@ -1,1897 +1,1897 @@
require 'puppet'
require 'puppet/util/log'
require 'puppet/util/metric'
require 'puppet/property'
require 'puppet/parameter'
require 'puppet/util'
require 'puppet/util/autoload'
require 'puppet/metatype/manager'
require 'puppet/util/errors'
require 'puppet/util/log_paths'
require 'puppet/util/logging'
require 'puppet/util/cacher'
require 'puppet/file_collection/lookup'
require 'puppet/util/tagging'
# see the bottom of the file for the rest of the inclusions
module Puppet
class Type
include Puppet::Util
include Puppet::Util::Errors
include Puppet::Util::LogPaths
include Puppet::Util::Logging
include Puppet::Util::Cacher
include Puppet::FileCollection::Lookup
include Puppet::Util::Tagging
###############################
# Code related to resource type attributes.
class << self
include Puppet::Util::ClassGen
include Puppet::Util::Warnings
attr_reader :properties
end
def self.states
warnonce "The states method is deprecated; use properties"
properties
end
# All parameters, in the appropriate order. The key_attributes come first, then
# the provider, then the properties, and finally the params and metaparams
# in the order they were specified in the files.
def self.allattrs
key_attributes | (parameters & [:provider]) | properties.collect { |property| property.name } | parameters | metaparams
end
# Retrieve an attribute alias, if there is one.
def self.attr_alias(param)
@attr_aliases[symbolize(param)]
end
# Create an alias to an existing attribute. This will cause the aliased
# attribute to be valid when setting and retrieving values on the instance.
def self.set_attr_alias(hash)
hash.each do |new, old|
@attr_aliases[symbolize(new)] = symbolize(old)
end
end
# Find the class associated with any given attribute.
def self.attrclass(name)
@attrclasses ||= {}
# We cache the value, since this method gets called such a huge number
# of times (as in, hundreds of thousands in a given run).
unless @attrclasses.include?(name)
@attrclasses[name] = case self.attrtype(name)
when :property; @validproperties[name]
when :meta; @@metaparamhash[name]
when :param; @paramhash[name]
end
end
@attrclasses[name]
end
# What type of parameter are we dealing with? Cache the results, because
# this method gets called so many times.
def self.attrtype(attr)
@attrtypes ||= {}
unless @attrtypes.include?(attr)
@attrtypes[attr] = case
when @validproperties.include?(attr); :property
when @paramhash.include?(attr); :param
when @@metaparamhash.include?(attr); :meta
end
end
@attrtypes[attr]
end
def self.eachmetaparam
@@metaparams.each { |p| yield p.name }
end
# Create the 'ensure' class. This is a separate method so other types
# can easily call it and create their own 'ensure' values.
def self.ensurable(&block)
if block_given?
self.newproperty(:ensure, :parent => Puppet::Property::Ensure, &block)
else
self.newproperty(:ensure, :parent => Puppet::Property::Ensure) do
self.defaultvalues
end
end
end
# Should we add the 'ensure' property to this class?
def self.ensurable?
# If the class has all three of these methods defined, then it's
# ensurable.
ens = [:exists?, :create, :destroy].inject { |set, method|
set &&= self.public_method_defined?(method)
}
ens
end
# Deal with any options passed into parameters.
def self.handle_param_options(name, options)
# If it's a boolean parameter, create a method to test the value easily
if options[:boolean]
define_method(name.to_s + "?") do
val = self[name]
if val == :true or val == true
return true
end
end
end
end
# Is the parameter in question a meta-parameter?
def self.metaparam?(param)
@@metaparamhash.include?(symbolize(param))
end
# Find the metaparameter class associated with a given metaparameter name.
def self.metaparamclass(name)
@@metaparamhash[symbolize(name)]
end
def self.metaparams
@@metaparams.collect { |param| param.name }
end
def self.metaparamdoc(metaparam)
@@metaparamhash[metaparam].doc
end
# Create a new metaparam. Requires a block and a name, stores it in the
# @parameters array, and does some basic checking on it.
def self.newmetaparam(name, options = {}, &block)
@@metaparams ||= []
@@metaparamhash ||= {}
name = symbolize(name)
param = genclass(
name,
:parent => options[:parent] || Puppet::Parameter,
:prefix => "MetaParam",
:hash => @@metaparamhash,
:array => @@metaparams,
:attributes => options[:attributes],
&block
)
# Grr.
param.required_features = options[:required_features] if options[:required_features]
handle_param_options(name, options)
param.metaparam = true
param
end
def self.key_attribute_parameters
@key_attribute_parameters ||= (
params = @parameters.find_all { |param|
param.isnamevar? or param.name == :name
}
)
end
def self.key_attributes
key_attribute_parameters.collect { |p| p.name }
end
def self.title_patterns
case key_attributes.length
when 0; []
when 1;
identity = lambda {|x| x}
[ [ /(.*)/m, [ [key_attributes.first, identity ] ] ] ]
else
raise Puppet::DevError,"you must specify title patterns when there are two or more key attributes"
end
end
def uniqueness_key
to_resource.uniqueness_key
end
# Create a new parameter. Requires a block and a name, stores it in the
# @parameters array, and does some basic checking on it.
def self.newparam(name, options = {}, &block)
options[:attributes] ||= {}
param = genclass(
name,
:parent => options[:parent] || Puppet::Parameter,
:attributes => options[:attributes],
:block => block,
:prefix => "Parameter",
:array => @parameters,
:hash => @paramhash
)
handle_param_options(name, options)
# Grr.
param.required_features = options[:required_features] if options[:required_features]
param.isnamevar if options[:namevar]
param
end
def self.newstate(name, options = {}, &block)
Puppet.warning "newstate() has been deprecrated; use newproperty(#{name})"
newproperty(name, options, &block)
end
# Create a new property. The first parameter must be the name of the property;
# this is how users will refer to the property when creating new instances.
# The second parameter is a hash of options; the options are:
# * :parent: The parent class for the property. Defaults to Puppet::Property.
# * :retrieve: The method to call on the provider or @parent object (if
# the provider is not set) to retrieve the current value.
def self.newproperty(name, options = {}, &block)
name = symbolize(name)
# This is here for types that might still have the old method of defining
# a parent class.
unless options.is_a? Hash
raise Puppet::DevError,
"Options must be a hash, not #{options.inspect}"
end
raise Puppet::DevError, "Class #{self.name} already has a property named #{name}" if @validproperties.include?(name)
if parent = options[:parent]
options.delete(:parent)
else
parent = Puppet::Property
end
# We have to create our own, new block here because we want to define
# an initial :retrieve method, if told to, and then eval the passed
# block if available.
prop = genclass(name, :parent => parent, :hash => @validproperties, :attributes => options) do
# If they've passed a retrieve method, then override the retrieve
# method on the class.
if options[:retrieve]
define_method(:retrieve) do
provider.send(options[:retrieve])
end
end
class_eval(&block) if block
end
# If it's the 'ensure' property, always put it first.
if name == :ensure
@properties.unshift prop
else
@properties << prop
end
prop
end
def self.paramdoc(param)
@paramhash[param].doc
end
# Return the parameter names
def self.parameters
return [] unless defined?(@parameters)
@parameters.collect { |klass| klass.name }
end
# Find the parameter class associated with a given parameter name.
def self.paramclass(name)
@paramhash[name]
end
# Return the property class associated with a name
def self.propertybyname(name)
@validproperties[name]
end
def self.validattr?(name)
name = symbolize(name)
return true if name == :name
@validattrs ||= {}
unless @validattrs.include?(name)
@validattrs[name] = !!(self.validproperty?(name) or self.validparameter?(name) or self.metaparam?(name))
end
@validattrs[name]
end
# does the name reflect a valid property?
def self.validproperty?(name)
name = symbolize(name)
@validproperties.include?(name) && @validproperties[name]
end
# Return the list of validproperties
def self.validproperties
return {} unless defined?(@parameters)
@validproperties.keys
end
# does the name reflect a valid parameter?
def self.validparameter?(name)
raise Puppet::DevError, "Class #{self} has not defined parameters" unless defined?(@parameters)
!!(@paramhash.include?(name) or @@metaparamhash.include?(name))
end
# This is a forward-compatibility method - it's the validity interface we'll use in Puppet::Resource.
def self.valid_parameter?(name)
validattr?(name)
end
# Return either the attribute alias or the attribute.
def attr_alias(name)
name = symbolize(name)
if synonym = self.class.attr_alias(name)
return synonym
else
return name
end
end
# Are we deleting this resource?
def deleting?
obj = @parameters[:ensure] and obj.should == :absent
end
# Create a new property if it is valid but doesn't exist
# Returns: true if a new parameter was added, false otherwise
def add_property_parameter(prop_name)
if self.class.validproperty?(prop_name) && !@parameters[prop_name]
self.newattr(prop_name)
return true
end
false
end
#
# The name_var is the key_attribute in the case that there is only one.
#
def name_var
key_attributes = self.class.key_attributes
(key_attributes.length == 1) && key_attributes.first
end
# abstract accessing parameters and properties, and normalize
# access to always be symbols, not strings
# This returns a value, not an object. It returns the 'is'
# value, but you can also specifically return 'is' and 'should'
# values using 'object.is(:property)' or 'object.should(:property)'.
def [](name)
name = attr_alias(name)
fail("Invalid parameter #{name}(#{name.inspect})") unless self.class.validattr?(name)
if name == :name
name = name_var
end
if obj = @parameters[name]
# Note that if this is a property, then the value is the "should" value,
# not the current value.
obj.value
else
return nil
end
end
# Abstract setting parameters and properties, and normalize
# access to always be symbols, not strings. This sets the 'should'
# value on properties, and otherwise just sets the appropriate parameter.
def []=(name,value)
name = attr_alias(name)
fail("Invalid parameter #{name}") unless self.class.validattr?(name)
if name == :name
name = name_var
end
raise Puppet::Error.new("Got nil value for #{name}") if value.nil?
property = self.newattr(name)
if property
begin
# make sure the parameter doesn't have any errors
property.value = value
rescue => detail
error = Puppet::Error.new("Parameter #{name} failed: #{detail}")
error.set_backtrace(detail.backtrace)
raise error
end
end
nil
end
# remove a property from the object; useful in testing or in cleanup
# when an error has been encountered
def delete(attr)
attr = symbolize(attr)
if @parameters.has_key?(attr)
@parameters.delete(attr)
else
raise Puppet::DevError.new("Undefined attribute '#{attr}' in #{self}")
end
end
# iterate across the existing properties
def eachproperty
# properties is a private method
properties.each { |property|
yield property
}
end
# Create a transaction event. Called by Transaction or by
# a property.
def event(options = {})
- Puppet::Transaction::Event.new({:resource => self, :file => file, :line => line, :tags => tags, :version => version}.merge(options))
+ Puppet::Transaction::Event.new({:resource => self, :file => file, :line => line, :tags => tags}.merge(options))
end
# Let the catalog determine whether a given cached value is
# still valid or has expired.
def expirer
catalog
end
# retrieve the 'should' value for a specified property
def should(name)
name = attr_alias(name)
(prop = @parameters[name] and prop.is_a?(Puppet::Property)) ? prop.should : nil
end
# Create the actual attribute instance. Requires either the attribute
# name or class as the first argument, then an optional hash of
# attributes to set during initialization.
def newattr(name)
if name.is_a?(Class)
klass = name
name = klass.name
end
unless klass = self.class.attrclass(name)
raise Puppet::Error, "Resource type #{self.class.name} does not support parameter #{name}"
end
if provider and ! provider.class.supports_parameter?(klass)
missing = klass.required_features.find_all { |f| ! provider.class.feature?(f) }
info "Provider %s does not support features %s; not managing attribute %s" % [provider.class.name, missing.join(", "), name]
return nil
end
return @parameters[name] if @parameters.include?(name)
@parameters[name] = klass.new(:resource => self)
end
# return the value of a parameter
def parameter(name)
@parameters[name.to_sym]
end
def parameters
@parameters.dup
end
# Is the named property defined?
def propertydefined?(name)
name = name.intern unless name.is_a? Symbol
@parameters.include?(name)
end
# Return an actual property instance by name; to return the value, use 'resource[param]'
# LAK:NOTE(20081028) Since the 'parameter' method is now a superset of this method,
# this one should probably go away at some point.
def property(name)
(obj = @parameters[symbolize(name)] and obj.is_a?(Puppet::Property)) ? obj : nil
end
# For any parameters or properties that have defaults and have not yet been
# set, set them now. This method can be handed a list of attributes,
# and if so it will only set defaults for those attributes.
def set_default(attr)
return unless klass = self.class.attrclass(attr)
return unless klass.method_defined?(:default)
return if @parameters.include?(klass.name)
return unless parameter = newattr(klass.name)
if value = parameter.default and ! value.nil?
parameter.value = value
else
@parameters.delete(parameter.name)
end
end
# Convert our object to a hash. This just includes properties.
def to_hash
rethash = {}
@parameters.each do |name, obj|
rethash[name] = obj.value
end
rethash
end
def type
self.class.name
end
# Return a specific value for an attribute.
def value(name)
name = attr_alias(name)
(obj = @parameters[name] and obj.respond_to?(:value)) ? obj.value : nil
end
def version
return 0 unless catalog
catalog.version
end
# Return all of the property objects, in the order specified in the
# class.
def properties
self.class.properties.collect { |prop| @parameters[prop.name] }.compact
end
# Is this type's name isomorphic with the object? That is, if the
# name conflicts, does it necessarily mean that the objects conflict?
# Defaults to true.
def self.isomorphic?
if defined?(@isomorphic)
return @isomorphic
else
return true
end
end
def isomorphic?
self.class.isomorphic?
end
# is the instance a managed instance? A 'yes' here means that
# the instance was created from the language, vs. being created
# in order resolve other questions, such as finding a package
# in a list
def managed?
# Once an object is managed, it always stays managed; but an object
# that is listed as unmanaged might become managed later in the process,
# so we have to check that every time
if @managed
return @managed
else
@managed = false
properties.each { |property|
s = property.should
if s and ! property.class.unmanaged
@managed = true
break
end
}
return @managed
end
end
###############################
# Code related to the container behaviour.
# this is a retarded hack method to get around the difference between
# component children and file children
def self.depthfirst?
@depthfirst
end
def depthfirst?
self.class.depthfirst?
end
# Remove an object. The argument determines whether the object's
# subscriptions get eliminated, too.
def remove(rmdeps = true)
# This is hackish (mmm, cut and paste), but it works for now, and it's
# better than warnings.
@parameters.each do |name, obj|
obj.remove
end
@parameters.clear
@parent = nil
# Remove the reference to the provider.
if self.provider
@provider.clear
@provider = nil
end
end
###############################
# Code related to evaluating the resources.
# Flush the provider, if it supports it. This is called by the
# transaction.
def flush
self.provider.flush if self.provider and self.provider.respond_to?(:flush)
end
# if all contained objects are in sync, then we're in sync
# FIXME I don't think this is used on the type instances any more,
# it's really only used for testing
def insync?(is)
insync = true
if property = @parameters[:ensure]
unless is.include? property
raise Puppet::DevError,
"The is value is not in the is array for '#{property.name}'"
end
ensureis = is[property]
if property.insync?(ensureis) and property.should == :absent
return true
end
end
properties.each { |property|
unless is.include? property
raise Puppet::DevError,
"The is value is not in the is array for '#{property.name}'"
end
propis = is[property]
unless property.insync?(propis)
property.debug("Not in sync: #{propis.inspect} vs #{property.should.inspect}")
insync = false
#else
# property.debug("In sync")
end
}
#self.debug("#{self} sync status is #{insync}")
insync
end
# retrieve the current value of all contained properties
def retrieve
fail "Provider #{provider.class.name} is not functional on this host" if self.provider.is_a?(Puppet::Provider) and ! provider.class.suitable?
result = Puppet::Resource.new(type, title)
# Provide the name, so we know we'll always refer to a real thing
result[:name] = self[:name] unless self[:name] == title
if ensure_prop = property(:ensure) or (self.class.validattr?(:ensure) and ensure_prop = newattr(:ensure))
result[:ensure] = ensure_state = ensure_prop.retrieve
else
ensure_state = nil
end
properties.each do |property|
next if property.name == :ensure
if ensure_state == :absent
result[property] = :absent
else
result[property] = property.retrieve
end
end
result
end
def retrieve_resource
resource = retrieve
resource = Resource.new(type, title, :parameters => resource) if resource.is_a? Hash
resource
end
# Get a hash of the current properties. Returns a hash with
# the actual property instance as the key and the current value
# as the, um, value.
def currentpropvalues
# It's important to use the 'properties' method here, as it follows the order
# in which they're defined in the class. It also guarantees that 'ensure'
# is the first property, which is important for skipping 'retrieve' on
# all the properties if the resource is absent.
ensure_state = false
return properties.inject({}) do | prophash, property|
if property.name == :ensure
ensure_state = property.retrieve
prophash[property] = ensure_state
else
if ensure_state == :absent
prophash[property] = :absent
else
prophash[property] = property.retrieve
end
end
prophash
end
end
# Are we running in noop mode?
def noop?
# If we're not a host_config, we're almost certainly part of
# Settings, and we want to ignore 'noop'
return false if catalog and ! catalog.host_config?
if defined?(@noop)
@noop
else
Puppet[:noop]
end
end
def noop
noop?
end
###############################
# Code related to managing resource instances.
require 'puppet/transportable'
# retrieve a named instance of the current type
def self.[](name)
raise "Global resource access is deprecated"
@objects[name] || @aliases[name]
end
# add an instance by name to the class list of instances
def self.[]=(name,object)
raise "Global resource storage is deprecated"
newobj = nil
if object.is_a?(Puppet::Type)
newobj = object
else
raise Puppet::DevError, "must pass a Puppet::Type object"
end
if exobj = @objects[name] and self.isomorphic?
msg = "Object '#{newobj.class.name}[#{name}]' already exists"
msg += ("in file #{object.file} at line #{object.line}") if exobj.file and exobj.line
msg += ("and cannot be redefined in file #{object.file} at line #{object.line}") if object.file and object.line
error = Puppet::Error.new(msg)
raise error
else
#Puppet.info("adding %s of type %s to class list" %
# [name,object.class])
@objects[name] = newobj
end
end
# Create an alias. We keep these in a separate hash so that we don't encounter
# the objects multiple times when iterating over them.
def self.alias(name, obj)
raise "Global resource aliasing is deprecated"
if @objects.include?(name)
unless @objects[name] == obj
raise Puppet::Error.new(
"Cannot create alias #{name}: object already exists"
)
end
end
if @aliases.include?(name)
unless @aliases[name] == obj
raise Puppet::Error.new(
"Object #{@aliases[name].name} already has alias #{name}"
)
end
end
@aliases[name] = obj
end
# remove all of the instances of a single type
def self.clear
raise "Global resource removal is deprecated"
if defined?(@objects)
@objects.each do |name, obj|
obj.remove(true)
end
@objects.clear
end
@aliases.clear if defined?(@aliases)
end
# Force users to call this, so that we can merge objects if
# necessary.
def self.create(args)
# LAK:DEP Deprecation notice added 12/17/2008
Puppet.warning "Puppet::Type.create is deprecated; use Puppet::Type.new"
new(args)
end
# remove a specified object
def self.delete(resource)
raise "Global resource removal is deprecated"
return unless defined?(@objects)
@objects.delete(resource.title) if @objects.include?(resource.title)
@aliases.delete(resource.title) if @aliases.include?(resource.title)
if @aliases.has_value?(resource)
names = []
@aliases.each do |name, otherres|
if otherres == resource
names << name
end
end
names.each { |name| @aliases.delete(name) }
end
end
# iterate across each of the type's instances
def self.each
raise "Global resource iteration is deprecated"
return unless defined?(@objects)
@objects.each { |name,instance|
yield instance
}
end
# does the type have an object with the given name?
def self.has_key?(name)
raise "Global resource access is deprecated"
@objects.has_key?(name)
end
# Retrieve all known instances. Either requires providers or must be overridden.
def self.instances
raise Puppet::DevError, "#{self.name} has no providers and has not overridden 'instances'" if provider_hash.empty?
# Put the default provider first, then the rest of the suitable providers.
provider_instances = {}
providers_by_source.collect do |provider|
provider.instances.collect do |instance|
# We always want to use the "first" provider instance we find, unless the resource
# is already managed and has a different provider set
if other = provider_instances[instance.name]
Puppet.warning "%s %s found in both %s and %s; skipping the %s version" %
[self.name.to_s.capitalize, instance.name, other.class.name, instance.class.name, instance.class.name]
next
end
provider_instances[instance.name] = instance
new(:name => instance.name, :provider => instance, :audit => :all)
end
end.flatten.compact
end
# Return a list of one suitable provider per source, with the default provider first.
def self.providers_by_source
# Put the default provider first, then the rest of the suitable providers.
sources = []
[defaultprovider, suitableprovider].flatten.uniq.collect do |provider|
next if sources.include?(provider.source)
sources << provider.source
provider
end.compact
end
# Convert a simple hash into a Resource instance.
def self.hash2resource(hash)
hash = hash.inject({}) { |result, ary| result[ary[0].to_sym] = ary[1]; result }
title = hash.delete(:title)
title ||= hash[:name]
title ||= hash[key_attributes.first] if key_attributes.length == 1
raise Puppet::Error, "Title or name must be provided" unless title
# Now create our resource.
resource = Puppet::Resource.new(self.name, title)
[:catalog].each do |attribute|
if value = hash[attribute]
hash.delete(attribute)
resource.send(attribute.to_s + "=", value)
end
end
hash.each do |param, value|
resource[param] = value
end
resource
end
# Create the path for logging and such.
def pathbuilder
if p = parent
[p.pathbuilder, self.ref].flatten
else
[self.ref]
end
end
###############################
# Add all of the meta parameters.
newmetaparam(:noop) do
desc "Boolean flag indicating whether work should actually
be done."
newvalues(:true, :false)
munge do |value|
case value
when true, :true, "true"; @resource.noop = true
when false, :false, "false"; @resource.noop = false
end
end
end
newmetaparam(:schedule) do
desc "On what schedule the object should be managed. You must create a
schedule object, and then reference the name of that object to use
that for your schedule:
schedule { daily:
period => daily,
range => \"2-4\"
}
exec { \"/usr/bin/apt-get update\":
schedule => daily
}
The creation of the schedule object does not need to appear in the
configuration before objects that use it."
end
newmetaparam(:audit) do
desc "Audit specified attributes of resources over time, and report if any have changed.
This attribute can be used to track changes to any resource over time, and can
provide an audit trail of every change that happens on any given machine.
Note that you cannot both audit and manage an attribute - managing it guarantees
the value, and any changes already get logged."
validate do |list|
list = Array(list).collect {|p| p.to_sym}
unless list == [:all]
list.each do |param|
next if @resource.class.validattr?(param)
fail "Cannot audit #{param}: not a valid attribute for #{resource}"
end
end
end
munge do |args|
properties_to_audit(args).each do |param|
next unless resource.class.validproperty?(param)
resource.newattr(param)
end
end
def all_properties
resource.class.properties.find_all do |property|
resource.provider.nil? or resource.provider.class.supports_parameter?(property)
end.collect do |property|
property.name
end
end
def properties_to_audit(list)
if !list.kind_of?(Array) && list.to_sym == :all
list = all_properties
else
list = Array(list).collect { |p| p.to_sym }
end
end
end
newmetaparam(:check) do
desc "Audit specified attributes of resources over time, and report if any have changed.
This parameter has been deprecated in favor of 'audit'."
munge do |args|
resource.warning "'check' attribute is deprecated; use 'audit' instead"
resource[:audit] = args
end
end
newmetaparam(:loglevel) do
desc "Sets the level that information will be logged.
The log levels have the biggest impact when logs are sent to
syslog (which is currently the default)."
defaultto :notice
newvalues(*Puppet::Util::Log.levels)
newvalues(:verbose)
munge do |loglevel|
val = super(loglevel)
if val == :verbose
val = :info
end
val
end
end
newmetaparam(:alias) do
desc "Creates an alias for the object. Puppet uses this internally when you
provide a symbolic name:
file { sshdconfig:
path => $operatingsystem ? {
solaris => \"/usr/local/etc/ssh/sshd_config\",
default => \"/etc/ssh/sshd_config\"
},
source => \"...\"
}
service { sshd:
subscribe => File[sshdconfig]
}
When you use this feature, the parser sets `sshdconfig` as the name,
and the library sets that as an alias for the file so the dependency
lookup for `sshd` works. You can use this parameter yourself,
but note that only the library can use these aliases; for instance,
the following code will not work:
file { \"/etc/ssh/sshd_config\":
owner => root,
group => root,
alias => sshdconfig
}
file { sshdconfig:
mode => 644
}
There's no way here for the Puppet parser to know that these two stanzas
should be affecting the same file.
See the [Language Tutorial](http://docs.puppetlabs.com/guides/language_tutorial.html) for more information.
"
munge do |aliases|
aliases = [aliases] unless aliases.is_a?(Array)
raise(ArgumentError, "Cannot add aliases without a catalog") unless @resource.catalog
aliases.each do |other|
if obj = @resource.catalog.resource(@resource.class.name, other)
unless obj.object_id == @resource.object_id
self.fail("#{@resource.title} can not create alias #{other}: object already exists")
end
next
end
# Newschool, add it to the catalog.
@resource.catalog.alias(@resource, other)
end
end
end
newmetaparam(:tag) do
desc "Add the specified tags to the associated resource. While all resources
are automatically tagged with as much information as possible
(e.g., each class and definition containing the resource), it can
be useful to add your own tags to a given resource.
Tags are currently useful for things like applying a subset of a
host's configuration:
puppet agent --test --tags mytag
This way, when you're testing a configuration you can run just the
portion you're testing."
munge do |tags|
tags = [tags] unless tags.is_a? Array
tags.each do |tag|
@resource.tag(tag)
end
end
end
class RelationshipMetaparam < Puppet::Parameter
class << self
attr_accessor :direction, :events, :callback, :subclasses
end
@subclasses = []
def self.inherited(sub)
@subclasses << sub
end
def munge(references)
references = [references] unless references.is_a?(Array)
references.collect do |ref|
if ref.is_a?(Puppet::Resource)
ref
else
Puppet::Resource.new(ref)
end
end
end
def validate_relationship
@value.each do |ref|
unless @resource.catalog.resource(ref.to_s)
description = self.class.direction == :in ? "dependency" : "dependent"
fail "Could not find #{description} #{ref} for #{resource.ref}"
end
end
end
# Create edges from each of our relationships. :in
# relationships are specified by the event-receivers, and :out
# relationships are specified by the event generator. This
# way 'source' and 'target' are consistent terms in both edges
# and events -- that is, an event targets edges whose source matches
# the event's source. The direction of the relationship determines
# which resource is applied first and which resource is considered
# to be the event generator.
def to_edges
@value.collect do |reference|
reference.catalog = resource.catalog
# Either of the two retrieval attempts could have returned
# nil.
unless related_resource = reference.resolve
self.fail "Could not retrieve dependency '#{reference}' of #{@resource.ref}"
end
# Are we requiring them, or vice versa? See the method docs
# for futher info on this.
if self.class.direction == :in
source = related_resource
target = @resource
else
source = @resource
target = related_resource
end
if method = self.class.callback
subargs = {
:event => self.class.events,
:callback => method
}
self.debug("subscribes to #{related_resource.ref}")
else
# If there's no callback, there's no point in even adding
# a label.
subargs = nil
self.debug("requires #{related_resource.ref}")
end
rel = Puppet::Relationship.new(source, target, subargs)
end
end
end
def self.relationship_params
RelationshipMetaparam.subclasses
end
# Note that the order in which the relationships params is defined
# matters. The labelled params (notify and subcribe) must be later,
# so that if both params are used, those ones win. It's a hackish
# solution, but it works.
newmetaparam(:require, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :NONE}) do
desc "One or more objects that this object depends on.
This is used purely for guaranteeing that changes to required objects
happen before the dependent object. For instance:
# Create the destination directory before you copy things down
file { \"/usr/local/scripts\":
ensure => directory
}
file { \"/usr/local/scripts/myscript\":
source => \"puppet://server/module/myscript\",
mode => 755,
require => File[\"/usr/local/scripts\"]
}
Multiple dependencies can be specified by providing a comma-seperated list
of resources, enclosed in square brackets:
require => [ File[\"/usr/local\"], File[\"/usr/local/scripts\"] ]
Note that Puppet will autorequire everything that it can, and
there are hooks in place so that it's easy for resources to add new
ways to autorequire objects, so if you think Puppet could be
smarter here, let us know.
In fact, the above code was redundant -- Puppet will autorequire
any parent directories that are being managed; it will
automatically realize that the parent directory should be created
before the script is pulled down.
Currently, exec resources will autorequire their CWD (if it is
specified) plus any fully qualified paths that appear in the
command. For instance, if you had an `exec` command that ran
the `myscript` mentioned above, the above code that pulls the
file down would be automatically listed as a requirement to the
`exec` code, so that you would always be running againts the
most recent version.
"
end
newmetaparam(:subscribe, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :ALL_EVENTS, :callback => :refresh}) do
desc "One or more objects that this object depends on. Changes in the
subscribed to objects result in the dependent objects being
refreshed (e.g., a service will get restarted). For instance:
class nagios {
file { \"/etc/nagios/nagios.conf\":
source => \"puppet://server/module/nagios.conf\",
alias => nagconf # just to make things easier for me
}
service { nagios:
ensure => running,
subscribe => File[nagconf]
}
}
Currently the `exec`, `mount` and `service` type support
refreshing.
"
end
newmetaparam(:before, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :NONE}) do
desc %{This parameter is the opposite of **require** -- it guarantees
that the specified object is applied later than the specifying
object:
file { "/var/nagios/configuration":
source => "...",
recurse => true,
before => Exec["nagios-rebuid"]
}
exec { "nagios-rebuild":
command => "/usr/bin/make",
cwd => "/var/nagios/configuration"
}
This will make sure all of the files are up to date before the
make command is run.}
end
newmetaparam(:notify, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :ALL_EVENTS, :callback => :refresh}) do
desc %{This parameter is the opposite of **subscribe** -- it sends events
to the specified object:
file { "/etc/sshd_config":
source => "....",
notify => Service[sshd]
}
service { sshd:
ensure => running
}
This will restart the sshd service if the sshd config file changes.}
end
newmetaparam(:stage) do
desc %{Which run stage a given resource should reside in. This just creates
a dependency on or from the named milestone. For instance, saying that
this is in the 'bootstrap' stage creates a dependency on the 'bootstrap'
milestone.
By default, all classes get directly added to the
'main' stage. You can create new stages as resources:
stage { [pre, post]: }
To order stages, use standard relationships:
stage { pre: before => Stage[main] }
Or use the new relationship syntax:
Stage[pre] -> Stage[main] -> Stage[post]
Then use the new class parameters to specify a stage:
class { foo: stage => pre }
Stages can only be set on classes, not individual resources. This will
fail:
file { '/foo': stage => pre, ensure => file }
}
end
###############################
# All of the provider plumbing for the resource types.
require 'puppet/provider'
require 'puppet/util/provider_features'
# Add the feature handling module.
extend Puppet::Util::ProviderFeatures
attr_reader :provider
# the Type class attribute accessors
class << self
attr_accessor :providerloader
attr_writer :defaultprovider
end
# Find the default provider.
def self.defaultprovider
unless @defaultprovider
suitable = suitableprovider
# Find which providers are a default for this system.
defaults = suitable.find_all { |provider| provider.default? }
# If we don't have any default we use suitable providers
defaults = suitable if defaults.empty?
max = defaults.collect { |provider| provider.specificity }.max
defaults = defaults.find_all { |provider| provider.specificity == max }
retval = nil
if defaults.length > 1
Puppet.warning(
"Found multiple default providers for #{self.name}: #{defaults.collect { |i| i.name.to_s }.join(", ")}; using #{defaults[0].name}"
)
retval = defaults.shift
elsif defaults.length == 1
retval = defaults.shift
else
raise Puppet::DevError, "Could not find a default provider for #{self.name}"
end
@defaultprovider = retval
end
@defaultprovider
end
def self.provider_hash_by_type(type)
@provider_hashes ||= {}
@provider_hashes[type] ||= {}
end
def self.provider_hash
Puppet::Type.provider_hash_by_type(self.name)
end
# Retrieve a provider by name.
def self.provider(name)
name = Puppet::Util.symbolize(name)
# If we don't have it yet, try loading it.
@providerloader.load(name) unless provider_hash.has_key?(name)
provider_hash[name]
end
# Just list all of the providers.
def self.providers
provider_hash.keys
end
def self.validprovider?(name)
name = Puppet::Util.symbolize(name)
(provider_hash.has_key?(name) && provider_hash[name].suitable?)
end
# Create a new provider of a type. This method must be called
# directly on the type that it's implementing.
def self.provide(name, options = {}, &block)
name = Puppet::Util.symbolize(name)
if obj = provider_hash[name]
Puppet.debug "Reloading #{name} #{self.name} provider"
unprovide(name)
end
parent = if pname = options[:parent]
options.delete(:parent)
if pname.is_a? Class
pname
else
if provider = self.provider(pname)
provider
else
raise Puppet::DevError,
"Could not find parent provider #{pname} of #{name}"
end
end
else
Puppet::Provider
end
options[:resource_type] ||= self
self.providify
provider = genclass(
name,
:parent => parent,
:hash => provider_hash,
:prefix => "Provider",
:block => block,
:include => feature_module,
:extend => feature_module,
:attributes => options
)
provider
end
# Make sure we have a :provider parameter defined. Only gets called if there
# are providers.
def self.providify
return if @paramhash.has_key? :provider
newparam(:provider) do
desc "The specific backend for #{self.name.to_s} to use. You will
seldom need to specify this -- Puppet will usually discover the
appropriate provider for your platform."
# This is so we can refer back to the type to get a list of
# providers for documentation.
class << self
attr_accessor :parenttype
end
# We need to add documentation for each provider.
def self.doc
@doc + " Available providers are:\n\n" + parenttype.providers.sort { |a,b|
a.to_s <=> b.to_s
}.collect { |i|
"* **#{i}**: #{parenttype().provider(i).doc}"
}.join("\n")
end
defaultto {
@resource.class.defaultprovider.name
}
validate do |provider_class|
provider_class = provider_class[0] if provider_class.is_a? Array
provider_class = provider_class.class.name if provider_class.is_a?(Puppet::Provider)
unless provider = @resource.class.provider(provider_class)
raise ArgumentError, "Invalid #{@resource.class.name} provider '#{provider_class}'"
end
end
munge do |provider|
provider = provider[0] if provider.is_a? Array
provider = provider.intern if provider.is_a? String
@resource.provider = provider
if provider.is_a?(Puppet::Provider)
provider.class.name
else
provider
end
end
end.parenttype = self
end
def self.unprovide(name)
if provider_hash.has_key? name
rmclass(
name,
:hash => provider_hash,
:prefix => "Provider"
)
if @defaultprovider and @defaultprovider.name == name
@defaultprovider = nil
end
end
end
# Return an array of all of the suitable providers.
def self.suitableprovider
providerloader.loadall if provider_hash.empty?
provider_hash.find_all { |name, provider|
provider.suitable?
}.collect { |name, provider|
provider
}.reject { |p| p.name == :fake } # For testing
end
def provider=(name)
if name.is_a?(Puppet::Provider)
@provider = name
@provider.resource = self
elsif klass = self.class.provider(name)
@provider = klass.new(self)
else
raise ArgumentError, "Could not find #{name} provider of #{self.class.name}"
end
end
###############################
# All of the relationship code.
# Specify a block for generating a list of objects to autorequire. This
# makes it so that you don't have to manually specify things that you clearly
# require.
def self.autorequire(name, &block)
@autorequires ||= {}
@autorequires[name] = block
end
# Yield each of those autorequires in turn, yo.
def self.eachautorequire
@autorequires ||= {}
@autorequires.each { |type, block|
yield(type, block)
}
end
# Figure out of there are any objects we can automatically add as
# dependencies.
def autorequire(rel_catalog = nil)
rel_catalog ||= catalog
raise(Puppet::DevError, "You cannot add relationships without a catalog") unless rel_catalog
reqs = []
self.class.eachautorequire { |type, block|
# Ignore any types we can't find, although that would be a bit odd.
next unless typeobj = Puppet::Type.type(type)
# Retrieve the list of names from the block.
next unless list = self.instance_eval(&block)
list = [list] unless list.is_a?(Array)
# Collect the current prereqs
list.each { |dep|
obj = nil
# Support them passing objects directly, to save some effort.
unless dep.is_a? Puppet::Type
# Skip autorequires that we aren't managing
unless dep = rel_catalog.resource(type, dep)
next
end
end
reqs << Puppet::Relationship.new(dep, self)
}
}
reqs
end
# Build the dependencies associated with an individual object.
def builddepends
# Handle the requires
self.class.relationship_params.collect do |klass|
if param = @parameters[klass.name]
param.to_edges
end
end.flatten.reject { |r| r.nil? }
end
# Define the initial list of tags.
def tags=(list)
tag(self.class.name)
tag(*list)
end
# Types (which map to resources in the languages) are entirely composed of
# attribute value pairs. Generally, Puppet calls any of these things an
# 'attribute', but these attributes always take one of three specific
# forms: parameters, metaparams, or properties.
# In naming methods, I have tried to consistently name the method so
# that it is clear whether it operates on all attributes (thus has 'attr' in
# the method name, or whether it operates on a specific type of attributes.
attr_writer :title
attr_writer :noop
include Enumerable
# class methods dealing with Type management
public
# the Type class attribute accessors
class << self
attr_reader :name
attr_accessor :self_refresh
include Enumerable, Puppet::Util::ClassGen
include Puppet::MetaType::Manager
include Puppet::Util
include Puppet::Util::Logging
end
# all of the variables that must be initialized for each subclass
def self.initvars
# all of the instances of this class
@objects = Hash.new
@aliases = Hash.new
@defaults = {}
@parameters ||= []
@validproperties = {}
@properties = []
@parameters = []
@paramhash = {}
@attr_aliases = {}
@paramdoc = Hash.new { |hash,key|
key = key.intern if key.is_a?(String)
if hash.include?(key)
hash[key]
else
"Param Documentation for #{key} not found"
end
}
@doc ||= ""
end
def self.to_s
if defined?(@name)
"Puppet::Type::#{@name.to_s.capitalize}"
else
super
end
end
# Create a block to validate that our object is set up entirely. This will
# be run before the object is operated on.
def self.validate(&block)
define_method(:validate, &block)
#@validate = block
end
# The catalog that this resource is stored in.
attr_accessor :catalog
# is the resource exported
attr_accessor :exported
# is the resource virtual (it should not :-))
attr_accessor :virtual
# create a log at specified level
def log(msg)
Puppet::Util::Log.create(
:level => @parameters[:loglevel].value,
:message => msg,
:source => self
)
end
# instance methods related to instance intrinsics
# e.g., initialize and name
public
attr_reader :original_parameters
# initialize the type instance
def initialize(resource)
raise Puppet::DevError, "Got TransObject instead of Resource or hash" if resource.is_a?(Puppet::TransObject)
resource = self.class.hash2resource(resource) unless resource.is_a?(Puppet::Resource)
# The list of parameter/property instances.
@parameters = {}
# Set the title first, so any failures print correctly.
if resource.type.to_s.downcase.to_sym == self.class.name
self.title = resource.title
else
# This should only ever happen for components
self.title = resource.ref
end
[:file, :line, :catalog, :exported, :virtual].each do |getter|
setter = getter.to_s + "="
if val = resource.send(getter)
self.send(setter, val)
end
end
@tags = resource.tags
@original_parameters = resource.to_hash
set_name(@original_parameters)
set_default(:provider)
set_parameters(@original_parameters)
self.validate if self.respond_to?(:validate)
end
private
# Set our resource's name.
def set_name(hash)
self[name_var] = hash.delete(name_var) if name_var
end
# Set all of the parameters from a hash, in the appropriate order.
def set_parameters(hash)
# Use the order provided by allattrs, but add in any
# extra attributes from the resource so we get failures
# on invalid attributes.
no_values = []
(self.class.allattrs + hash.keys).uniq.each do |attr|
begin
# Set any defaults immediately. This is mostly done so
# that the default provider is available for any other
# property validation.
if hash.has_key?(attr)
self[attr] = hash[attr]
else
no_values << attr
end
rescue ArgumentError, Puppet::Error, TypeError
raise
rescue => detail
error = Puppet::DevError.new( "Could not set #{attr} on #{self.class.name}: #{detail}")
error.set_backtrace(detail.backtrace)
raise error
end
end
no_values.each do |attr|
set_default(attr)
end
end
public
# Set up all of our autorequires.
def finish
# Make sure all of our relationships are valid. Again, must be done
# when the entire catalog is instantiated.
self.class.relationship_params.collect do |klass|
if param = @parameters[klass.name]
param.validate_relationship
end
end.flatten.reject { |r| r.nil? }
end
# For now, leave the 'name' method functioning like it used to. Once 'title'
# works everywhere, I'll switch it.
def name
self[:name]
end
# Look up our parent in the catalog, if we have one.
def parent
return nil unless catalog
unless defined?(@parent)
if parents = catalog.adjacent(self, :direction => :in)
# We should never have more than one parent, so let's just ignore
# it if we happen to.
@parent = parents.shift
else
@parent = nil
end
end
@parent
end
# Return the "type[name]" style reference.
def ref
"#{self.class.name.to_s.capitalize}[#{self.title}]"
end
def self_refresh?
self.class.self_refresh
end
# Mark that we're purging.
def purging
@purging = true
end
# Is this resource being purged? Used by transactions to forbid
# deletion when there are dependencies.
def purging?
if defined?(@purging)
@purging
else
false
end
end
# Retrieve the title of an object. If no title was set separately,
# then use the object's name.
def title
unless @title
if self.class.validparameter?(name_var)
@title = self[:name]
elsif self.class.validproperty?(name_var)
@title = self.should(name_var)
else
self.devfail "Could not find namevar #{name_var} for #{self.class.name}"
end
end
@title
end
# convert to a string
def to_s
self.ref
end
# Convert to a transportable object
def to_trans(ret = true)
trans = TransObject.new(self.title, self.class.name)
values = retrieve_resource
values.each do |name, value|
name = name.name if name.respond_to? :name
trans[name] = value
end
@parameters.each do |name, param|
# Avoid adding each instance name twice
next if param.class.isnamevar? and param.value == self.title
# We've already got property values
next if param.is_a?(Puppet::Property)
trans[name] = param.value
end
trans.tags = self.tags
# FIXME I'm currently ignoring 'parent' and 'path'
trans
end
def to_resource
# this 'type instance' versus 'resource' distinction seems artificial
# I'd like to see it collapsed someday ~JW
self.to_trans.to_resource
end
def virtual?; !!@virtual; end
def exported?; !!@exported; end
end
end
require 'puppet/provider'
# Always load these types.
require 'puppet/type/component'
diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb
index 6523c99a0..eee948cd5 100644
--- a/lib/puppet/type/file.rb
+++ b/lib/puppet/type/file.rb
@@ -1,799 +1,801 @@
require 'digest/md5'
require 'cgi'
require 'etc'
require 'uri'
require 'fileutils'
require 'puppet/network/handler'
require 'puppet/util/diff'
require 'puppet/util/checksums'
require 'puppet/network/client'
require 'puppet/util/backups'
Puppet::Type.newtype(:file) do
include Puppet::Util::MethodHelper
include Puppet::Util::Checksums
include Puppet::Util::Backups
@doc = "Manages local files, including setting ownership and
permissions, creation of both files and directories, and
retrieving entire files from remote servers. As Puppet matures, it
expected that the `file` resource will be used less and less to
manage content, and instead native resources will be used to do so.
If you find that you are often copying files in from a central
location, rather than using native resources, please contact
Puppet Labs and we can hopefully work with you to develop a
native resource to support what you are doing."
def self.title_patterns
[ [ /^(.*?)\/*\Z/m, [ [ :path, lambda{|x| x} ] ] ] ]
end
newparam(:path) do
desc "The path to the file to manage. Must be fully qualified."
isnamevar
validate do |value|
# accept various path syntaxes: lone slash, posix, win32, unc
unless (Puppet.features.posix? and (value =~ /^\/$/ or value =~ /^\/[^\/]/)) or (Puppet.features.microsoft_windows? and (value =~ /^.:\// or value =~ /^\/\/[^\/]+\/[^\/]+/))
fail Puppet::Error, "File paths must be fully qualified, not '#{value}'"
end
end
# convert the current path in an index into the collection and the last
# path name. The aim is to use less storage for all common paths in a hierarchy
munge do |value|
path, name = File.split(value.gsub(/\/+/,'/'))
{ :index => Puppet::FileCollection.collection.index(path), :name => name }
end
# and the reverse
unmunge do |value|
basedir = Puppet::FileCollection.collection.path(value[:index])
# a lone slash as :name indicates a root dir on windows
if value[:name] == '/'
basedir
else
File.join( basedir, value[:name] )
end
end
end
newparam(:backup) do
desc "Whether files should be backed up before
being replaced. The preferred method of backing files up is via
a `filebucket`, which stores files by their MD5 sums and allows
easy retrieval without littering directories with backups. You
can specify a local filebucket or a network-accessible
server-based filebucket by setting `backup => bucket-name`.
Alternatively, if you specify any value that begins with a `.`
(e.g., `.puppet-bak`), then Puppet will use copy the file in
the same directory with that value as the extension of the
backup. Setting `backup => false` disables all backups of the
file in question.
Puppet automatically creates a local filebucket named `puppet` and
defaults to backing up there. To use a server-based filebucket,
you must specify one in your configuration
filebucket { main:
server => puppet
}
The `puppet master` daemon creates a filebucket by default,
so you can usually back up to your main server with this
configuration. Once you've described the bucket in your
configuration, you can use it in any file
file { \"/my/file\":
source => \"/path/in/nfs/or/something\",
backup => main
}
This will back the file up to the central server.
At this point, the benefits of using a filebucket are that you do not
have backup files lying around on each of your machines, a given
version of a file is only backed up once, and you can restore
any given file manually, no matter how old. Eventually,
transactional support will be able to automatically restore
filebucketed files.
"
defaultto "puppet"
munge do |value|
# I don't really know how this is happening.
value = value.shift if value.is_a?(Array)
case value
when false, "false", :false
false
when true, "true", ".puppet-bak", :true
".puppet-bak"
when String
value
else
self.fail "Invalid backup type #{value.inspect}"
end
end
end
newparam(:recurse) do
desc "Whether and how deeply to do recursive
management."
newvalues(:true, :false, :inf, :remote, /^[0-9]+$/)
# Replace the validation so that we allow numbers in
# addition to string representations of them.
validate { |arg| }
munge do |value|
newval = super(value)
case newval
when :true, :inf; true
when :false; false
when :remote; :remote
when Integer, Fixnum, Bignum
self.warning "Setting recursion depth with the recurse parameter is now deprecated, please use recurselimit"
# recurse == 0 means no recursion
return false if value == 0
resource[:recurselimit] = value
true
when /^\d+$/
self.warning "Setting recursion depth with the recurse parameter is now deprecated, please use recurselimit"
value = Integer(value)
# recurse == 0 means no recursion
return false if value == 0
resource[:recurselimit] = value
true
else
self.fail "Invalid recurse value #{value.inspect}"
end
end
end
newparam(:recurselimit) do
desc "How deeply to do recursive management."
newvalues(/^[0-9]+$/)
munge do |value|
newval = super(value)
case newval
when Integer, Fixnum, Bignum; value
when /^\d+$/; Integer(value)
else
self.fail "Invalid recurselimit value #{value.inspect}"
end
end
end
newparam(:replace, :boolean => true) do
desc "Whether or not to replace a file that is
sourced but exists. This is useful for using file sources
purely for initialization."
newvalues(:true, :false)
aliasvalue(:yes, :true)
aliasvalue(:no, :false)
defaultto :true
end
newparam(:force, :boolean => true) do
desc "Force the file operation. Currently only used when replacing
directories with links."
newvalues(:true, :false)
defaultto false
end
newparam(:ignore) do
desc "A parameter which omits action on files matching
specified patterns during recursion. Uses Ruby's builtin globbing
engine, so shell metacharacters are fully supported, e.g. `[a-z]*`.
Matches that would descend into the directory structure are ignored,
e.g., `*/*`."
validate do |value|
unless value.is_a?(Array) or value.is_a?(String) or value == false
self.devfail "Ignore must be a string or an Array"
end
end
end
newparam(:links) do
desc "How to handle links during file actions. During file copying,
`follow` will copy the target file instead of the link, `manage`
will copy the link itself, and `ignore` will just pass it by.
When not copying, `manage` and `ignore` behave equivalently
(because you cannot really ignore links entirely during local recursion), and `follow` will manage the file to which the
link points."
newvalues(:follow, :manage)
defaultto :manage
end
newparam(:purge, :boolean => true) do
desc "Whether unmanaged files should be purged. If you have a filebucket
configured the purged files will be uploaded, but if you do not,
this will destroy data. Only use this option for generated
files unless you really know what you are doing. This option only
makes sense when recursively managing directories.
Note that when using `purge` with `source`, Puppet will purge any files
that are not on the remote system."
defaultto :false
newvalues(:true, :false)
end
newparam(:sourceselect) do
desc "Whether to copy all valid sources, or just the first one. This parameter
is only used in recursive copies; by default, the first valid source is the
only one used as a recursive source, but if this parameter is set to `all`,
then all valid sources will have all of their contents copied to the local host,
and for sources that have the same file, the source earlier in the list will
be used."
defaultto :first
newvalues(:first, :all)
end
# Autorequire any parent directories.
autorequire(:file) do
basedir = File.dirname(self[:path])
if basedir != self[:path]
basedir
else
nil
end
end
# Autorequire the owner and group of the file.
{:user => :owner, :group => :group}.each do |type, property|
autorequire(type) do
if @parameters.include?(property)
# The user/group property automatically converts to IDs
next unless should = @parameters[property].shouldorig
val = should[0]
if val.is_a?(Integer) or val =~ /^\d+$/
nil
else
val
end
end
end
end
CREATORS = [:content, :source, :target]
validate do
count = 0
CREATORS.each do |param|
count += 1 if self.should(param)
end
count += 1 if @parameters.include?(:source)
self.fail "You cannot specify more than one of #{CREATORS.collect { |p| p.to_s}.join(", ")}" if count > 1
self.fail "You cannot specify a remote recursion without a source" if !self[:source] and self[:recurse] == :remote
self.warning "Possible error: recurselimit is set but not recurse, no recursion will happen" if !self[:recurse] and self[:recurselimit]
end
def self.[](path)
return nil unless path
super(path.gsub(/\/+/, '/').sub(/\/$/, ''))
end
# List files, but only one level deep.
def self.instances(base = "/")
return [] unless FileTest.directory?(base)
files = []
Dir.entries(base).reject { |e|
e == "." or e == ".."
}.each do |name|
path = File.join(base, name)
if obj = self[path]
obj[:audit] = :all
files << obj
else
files << self.new(
:name => path, :audit => :all
)
end
end
files
end
@depthfirst = false
# Determine the user to write files as.
def asuser
if self.should(:owner) and ! self.should(:owner).is_a?(Symbol)
writeable = Puppet::Util::SUIDManager.asuser(self.should(:owner)) {
FileTest.writable?(File.dirname(self[:path]))
}
# If the parent directory is writeable, then we execute
# as the user in question. Otherwise we'll rely on
# the 'owner' property to do things.
asuser = self.should(:owner) if writeable
end
asuser
end
def bucket
return @bucket if @bucket
backup = self[:backup]
return nil unless backup
return nil if backup =~ /^\./
unless catalog or backup == "puppet"
fail "Can not find filebucket for backups without a catalog"
end
unless catalog and filebucket = catalog.resource(:filebucket, backup) or backup == "puppet"
fail "Could not find filebucket #{backup} specified in backup"
end
return default_bucket unless filebucket
@bucket = filebucket.bucket
@bucket
end
def default_bucket
Puppet::Type.type(:filebucket).mkdefaultbucket.bucket
end
# Does the file currently exist? Just checks for whether
# we have a stat
def exist?
stat ? true : false
end
# We have to do some extra finishing, to retrieve our bucket if
# there is one.
def finish
# Look up our bucket, if there is one
bucket
super
end
# Create any children via recursion or whatever.
def eval_generate
return [] unless self.recurse?
recurse
#recurse.reject do |resource|
# catalog.resource(:file, resource[:path])
#end.each do |child|
# catalog.add_resource child
# catalog.relationship_graph.add_edge self, child
#end
end
def flush
# We want to make sure we retrieve metadata anew on each transaction.
@parameters.each do |name, param|
param.flush if param.respond_to?(:flush)
end
@stat = nil
end
def initialize(hash)
# Used for caching clients
@clients = {}
super
# If they've specified a source, we get our 'should' values
# from it.
unless self[:ensure]
if self[:target]
self[:ensure] = :symlink
elsif self[:content]
self[:ensure] = :file
end
end
@stat = nil
end
# Configure discovered resources to be purged.
def mark_children_for_purging(children)
children.each do |name, child|
next if child[:source]
child[:ensure] = :absent
end
end
# Create a new file or directory object as a child to the current
# object.
def newchild(path)
full_path = File.join(self[:path], path)
# Add some new values to our original arguments -- these are the ones
# set at initialization. We specifically want to exclude any param
# values set by the :source property or any default values.
# LAK:NOTE This is kind of silly, because the whole point here is that
# the values set at initialization should live as long as the resource
# but values set by default or by :source should only live for the transaction
# or so. Unfortunately, we don't have a straightforward way to manage
# the different lifetimes of this data, so we kludge it like this.
# The right-side hash wins in the merge.
options = @original_parameters.merge(:path => full_path).reject { |param, value| value.nil? }
# These should never be passed to our children.
[:parent, :ensure, :recurse, :recurselimit, :target, :alias, :source].each do |param|
options.delete(param) if options.include?(param)
end
self.class.new(options)
end
# Files handle paths specially, because they just lengthen their
# path names, rather than including the full parent's title each
# time.
def pathbuilder
# We specifically need to call the method here, so it looks
# up our parent in the catalog graph.
if parent = parent()
# We only need to behave specially when our parent is also
# a file
if parent.is_a?(self.class)
# Remove the parent file name
list = parent.pathbuilder
list.pop # remove the parent's path info
return list << self.ref
else
return super
end
else
return [self.ref]
end
end
# Should we be purging?
def purge?
@parameters.include?(:purge) and (self[:purge] == :true or self[:purge] == "true")
end
# Recursively generate a list of file resources, which will
# be used to copy remote files, manage local files, and/or make links
# to map to another directory.
def recurse
children = {}
children = recurse_local if self[:recurse] != :remote
if self[:target]
recurse_link(children)
elsif self[:source]
recurse_remote(children)
end
# If we're purging resources, then delete any resource that isn't on the
# remote system.
mark_children_for_purging(children) if self.purge?
result = children.values.sort { |a, b| a[:path] <=> b[:path] }
remove_less_specific_files(result)
end
# This is to fix bug #2296, where two files recurse over the same
# set of files. It's a rare case, and when it does happen you're
# not likely to have many actual conflicts, which is good, because
# this is a pretty inefficient implementation.
def remove_less_specific_files(files)
mypath = self[:path].split(File::Separator)
other_paths = catalog.vertices.
select { |r| r.is_a?(self.class) and r[:path] != self[:path] }.
collect { |r| r[:path].split(File::Separator) }.
select { |p| p[0,mypath.length] == mypath }
return files if other_paths.empty?
files.reject { |file|
path = file[:path].split(File::Separator)
other_paths.any? { |p| path[0,p.length] == p }
}
end
# A simple method for determining whether we should be recursing.
def recurse?
return false unless @parameters.include?(:recurse)
val = @parameters[:recurse].value
!!(val and (val == true or val == :remote))
end
# Recurse the target of the link.
def recurse_link(children)
perform_recursion(self[:target]).each do |meta|
if meta.relative_path == "."
self[:ensure] = :directory
next
end
children[meta.relative_path] ||= newchild(meta.relative_path)
if meta.ftype == "directory"
children[meta.relative_path][:ensure] = :directory
else
children[meta.relative_path][:ensure] = :link
children[meta.relative_path][:target] = meta.full_path
end
end
children
end
# Recurse the file itself, returning a Metadata instance for every found file.
def recurse_local
result = perform_recursion(self[:path])
return {} unless result
result.inject({}) do |hash, meta|
next hash if meta.relative_path == "."
hash[meta.relative_path] = newchild(meta.relative_path)
hash
end
end
# Recurse against our remote file.
def recurse_remote(children)
sourceselect = self[:sourceselect]
total = self[:source].collect do |source|
next unless result = perform_recursion(source)
return if top = result.find { |r| r.relative_path == "." } and top.ftype != "directory"
result.each { |data| data.source = "#{source}/#{data.relative_path}" }
break result if result and ! result.empty? and sourceselect == :first
result
end.flatten
# This only happens if we have sourceselect == :all
unless sourceselect == :first
found = []
total.reject! do |data|
result = found.include?(data.relative_path)
found << data.relative_path unless found.include?(data.relative_path)
result
end
end
total.each do |meta|
if meta.relative_path == "."
parameter(:source).metadata = meta
next
end
children[meta.relative_path] ||= newchild(meta.relative_path)
children[meta.relative_path][:source] = meta.source
children[meta.relative_path][:checksum] = :md5 if meta.ftype == "file"
children[meta.relative_path].parameter(:source).metadata = meta
end
children
end
def perform_recursion(path)
Puppet::FileServing::Metadata.search(
path,
:links => self[:links],
:recurse => (self[:recurse] == :remote ? true : self[:recurse]),
:recurselimit => self[:recurselimit],
:ignore => self[:ignore],
:checksum_type => (self[:source] || self[:content]) ? self[:checksum] : :none
)
end
# Remove any existing data. This is only used when dealing with
# links or directories.
def remove_existing(should)
return unless s = stat
self.fail "Could not back up; will not replace" unless perform_backup
unless should.to_s == "link"
return if s.ftype.to_s == should.to_s
end
case s.ftype
when "directory"
if self[:force] == :true
debug "Removing existing directory for replacement with #{should}"
FileUtils.rmtree(self[:path])
else
notice "Not removing directory; use 'force' to override"
end
when "link", "file"
debug "Removing existing #{s.ftype} for replacement with #{should}"
File.unlink(self[:path])
else
self.fail "Could not back up files of type #{s.ftype}"
end
expire
end
def retrieve
if source = parameter(:source)
source.copy_source_values
end
super
end
# Set the checksum, from another property. There are multiple
# properties that modify the contents of a file, and they need the
# ability to make sure that the checksum value is in sync.
def setchecksum(sum = nil)
if @parameters.include? :checksum
if sum
@parameters[:checksum].checksum = sum
else
# If they didn't pass in a sum, then tell checksum to
# figure it out.
currentvalue = @parameters[:checksum].retrieve
@parameters[:checksum].checksum = currentvalue
end
end
end
# Should this thing be a normal file? This is a relatively complex
# way of determining whether we're trying to create a normal file,
# and it's here so that the logic isn't visible in the content property.
def should_be_file?
return true if self[:ensure] == :file
# I.e., it's set to something like "directory"
return false if e = self[:ensure] and e != :present
# The user doesn't really care, apparently
if self[:ensure] == :present
return true unless s = stat
return(s.ftype == "file" ? true : false)
end
# If we've gotten here, then :ensure isn't set
return true if self[:content]
return true if stat and stat.ftype == "file"
false
end
# Stat our file. Depending on the value of the 'links' attribute, we
# use either 'stat' or 'lstat', and we expect the properties to use the
# resulting stat object accordingly (mostly by testing the 'ftype'
# value).
cached_attr(:stat) do
method = :stat
# Files are the only types that support links
if (self.class.name == :file and self[:links] != :follow) or self.class.name == :tidy
method = :lstat
end
path = self[:path]
begin
File.send(method, self[:path])
rescue Errno::ENOENT => error
return nil
rescue Errno::EACCES => error
warning "Could not stat; permission denied"
return nil
end
end
# We have to hack this just a little bit, because otherwise we'll get
# an error when the target and the contents are created as properties on
# the far side.
def to_trans(retrieve = true)
obj = super
obj.delete(:target) if obj[:target] == :notlink
obj
end
# Write out the file. Requires the property name for logging.
# Write will be done by the content property, along with checksum computation
def write(property)
remove_existing(:file)
use_temporary_file = write_temporary_file?
if use_temporary_file
path = "#{self[:path]}.puppettmp_#{rand(10000)}"
path = "#{self[:path]}.puppettmp_#{rand(10000)}" while File.exists?(path) or File.symlink?(path)
else
path = self[:path]
end
mode = self.should(:mode) # might be nil
umask = mode ? 000 : 022
mode_int = mode ? mode.to_i(8) : nil
content_checksum = Puppet::Util.withumask(umask) { File.open(path, 'w', mode_int ) { |f| write_content(f) } }
# And put our new file in place
if use_temporary_file # This is only not true when our file is empty.
begin
fail_if_checksum_is_wrong(path, content_checksum) if validate_checksum?
File.rename(path, self[:path])
rescue => detail
fail "Could not rename temporary file #{path} to #{self[:path]}: #{detail}"
ensure
# Make sure the created file gets removed
File.unlink(path) if FileTest.exists?(path)
end
end
# make sure all of the modes are actually correct
property_fix
end
private
# Should we validate the checksum of the file we're writing?
def validate_checksum?
self[:checksum] !~ /time/
end
# Make sure the file we wrote out is what we think it is.
def fail_if_checksum_is_wrong(path, content_checksum)
newsum = parameter(:checksum).sum_file(path)
return if [:absent, nil, content_checksum].include?(newsum)
self.fail "File written to disk did not match checksum; discarding changes (#{content_checksum} vs #{newsum})"
end
# write the current content. Note that if there is no content property
# simply opening the file with 'w' as done in write is enough to truncate
# or write an empty length file.
def write_content(file)
(content = property(:content)) && content.write(file)
end
private
def write_temporary_file?
# unfortunately we don't know the source file size before fetching it
# so let's assume the file won't be empty
(c = property(:content) and c.length) || (s = @parameters[:source] and 1)
end
# There are some cases where all of the work does not get done on
# file creation/modification, so we have to do some extra checking.
def property_fix
properties.each do |thing|
next unless [:mode, :owner, :group, :seluser, :selrole, :seltype, :selrange].include?(thing.name)
# Make sure we get a new stat objct
expire
currentvalue = thing.retrieve
thing.sync unless thing.insync?(currentvalue)
end
end
end
# We put all of the properties in separate files, because there are so many
# of them. The order these are loaded is important, because it determines
# the order they are in the property lit.
require 'puppet/type/file/checksum'
require 'puppet/type/file/content' # can create the file
require 'puppet/type/file/source' # can create the file
require 'puppet/type/file/target' # creates a different type of file
require 'puppet/type/file/ensure' # can create the file
require 'puppet/type/file/owner'
require 'puppet/type/file/group'
require 'puppet/type/file/mode'
require 'puppet/type/file/type'
require 'puppet/type/file/selcontext' # SELinux file context
+require 'puppet/type/file/ctime'
+require 'puppet/type/file/mtime'
diff --git a/lib/puppet/type/file/ctime.rb b/lib/puppet/type/file/ctime.rb
new file mode 100644
index 000000000..24b098703
--- /dev/null
+++ b/lib/puppet/type/file/ctime.rb
@@ -0,0 +1,18 @@
+module Puppet
+ Puppet::Type.type(:file).newproperty(:ctime) do
+ desc "A read-only state to check the file ctime."
+
+ def retrieve
+ current_value = :absent
+ if stat = @resource.stat(false)
+ current_value = stat.ctime
+ end
+ current_value
+ end
+
+ validate do
+ fail "ctime is read-only"
+ end
+ end
+end
+
diff --git a/lib/puppet/type/file/mtime.rb b/lib/puppet/type/file/mtime.rb
new file mode 100644
index 000000000..8ca7ed0d6
--- /dev/null
+++ b/lib/puppet/type/file/mtime.rb
@@ -0,0 +1,17 @@
+module Puppet
+ Puppet::Type.type(:file).newproperty(:mtime) do
+ desc "A read-only state to check the file mtime."
+
+ def retrieve
+ current_value = :absent
+ if stat = @resource.stat(false)
+ current_value = stat.mtime
+ end
+ current_value
+ end
+
+ validate do
+ fail "mtime is read-only"
+ end
+ end
+end
diff --git a/lib/puppet/type/file/type.rb b/lib/puppet/type/file/type.rb
index eb50b81f9..4da54e2cb 100755
--- a/lib/puppet/type/file/type.rb
+++ b/lib/puppet/type/file/type.rb
@@ -1,26 +1,19 @@
module Puppet
Puppet::Type.type(:file).newproperty(:type) do
require 'etc'
desc "A read-only state to check the file type."
- #munge do |value|
- # raise Puppet::Error, ":type is read-only"
- #end
-
def retrieve
- currentvalue = :absent
+ current_value = :absent
if stat = @resource.stat(false)
- currentvalue = stat.ftype
+ current_value = stat.ftype
end
- # so this state is never marked out of sync
- @should = [currentvalue]
- currentvalue
+ current_value
end
-
- def sync
- raise Puppet::Error, ":type is read-only"
+ validate do
+ fail "type is read-only"
end
end
end
diff --git a/lib/puppet/util/checksums.rb b/lib/puppet/util/checksums.rb
index 5aebd8392..6fdf14ecf 100644
--- a/lib/puppet/util/checksums.rb
+++ b/lib/puppet/util/checksums.rb
@@ -1,125 +1,136 @@
# A stand-alone module for calculating checksums
# in a generic way.
module Puppet::Util::Checksums
+ class FakeChecksum
+ def <<(*args)
+ self
+ end
+ end
+
# Is the provided string a checksum?
def checksum?(string)
string =~ /^\{(\w{3,5})\}\S+/
end
# Strip the checksum type from an existing checksum
def sumdata(checksum)
checksum =~ /^\{(\w+)\}(.+)/ ? $2 : nil
end
# Strip the checksum type from an existing checksum
def sumtype(checksum)
checksum =~ /^\{(\w+)\}/ ? $1 : nil
end
# Calculate a checksum using Digest::MD5.
def md5(content)
require 'digest/md5'
Digest::MD5.hexdigest(content)
end
# Calculate a checksum of the first 500 chars of the content using Digest::MD5.
def md5lite(content)
md5(content[0..511])
end
# Calculate a checksum of a file's content using Digest::MD5.
def md5_file(filename, lite = false)
require 'digest/md5'
digest = Digest::MD5.new
checksum_file(digest, filename, lite)
end
# Calculate a checksum of the first 500 chars of a file's content using Digest::MD5.
def md5lite_file(filename)
md5_file(filename, true)
end
def md5_stream(&block)
require 'digest/md5'
digest = Digest::MD5.new
yield digest
digest.hexdigest
end
alias :md5lite_stream :md5_stream
# Return the :mtime timestamp of a file.
def mtime_file(filename)
File.stat(filename).send(:mtime)
end
# by definition this doesn't exist
+ # but we still need to execute the block given
def mtime_stream
+ noop_digest = FakeChecksum.new
+ yield noop_digest
nil
end
alias :ctime_stream :mtime_stream
# Calculate a checksum using Digest::SHA1.
def sha1(content)
require 'digest/sha1'
Digest::SHA1.hexdigest(content)
end
# Calculate a checksum of the first 500 chars of the content using Digest::SHA1.
def sha1lite(content)
sha1(content[0..511])
end
# Calculate a checksum of a file's content using Digest::SHA1.
def sha1_file(filename, lite = false)
require 'digest/sha1'
digest = Digest::SHA1.new
checksum_file(digest, filename, lite)
end
# Calculate a checksum of the first 500 chars of a file's content using Digest::SHA1.
def sha1lite_file(filename)
sha1_file(filename, true)
end
def sha1_stream
require 'digest/sha1'
digest = Digest::SHA1.new
yield digest
digest.hexdigest
end
alias :sha1lite_stream :sha1_stream
# Return the :ctime of a file.
def ctime_file(filename)
File.stat(filename).send(:ctime)
end
# Return a "no checksum"
def none_file(filename)
""
end
def none_stream
+ noop_digest = FakeChecksum.new
+ yield noop_digest
""
end
private
# Perform an incremental checksum on a file.
def checksum_file(digest, filename, lite = false)
buffer = lite ? 512 : 4096
File.open(filename, 'r') do |file|
while content = file.read(buffer)
digest << content
break if lite
end
end
digest.hexdigest
end
end
diff --git a/lib/puppet/util/log.rb b/lib/puppet/util/log.rb
index 7764dc1d1..3fdac3f69 100644
--- a/lib/puppet/util/log.rb
+++ b/lib/puppet/util/log.rb
@@ -1,258 +1,258 @@
require 'puppet/util/tagging'
require 'puppet/util/classgen'
# Pass feedback to the user. Log levels are modeled after syslog's, and it is
# expected that that will be the most common log destination. Supports
# multiple destinations, one of which is a remote server.
class Puppet::Util::Log
include Puppet::Util
extend Puppet::Util::ClassGen
include Puppet::Util::Tagging
@levels = [:debug,:info,:notice,:warning,:err,:alert,:emerg,:crit]
@loglevel = 2
@desttypes = {}
# Create a new destination type.
def self.newdesttype(name, options = {}, &block)
dest = genclass(
name,
:parent => Puppet::Util::Log::Destination,
:prefix => "Dest",
:block => block,
:hash => @desttypes,
:attributes => options
)
dest.match(dest.name)
dest
end
require 'puppet/util/log/destination'
require 'puppet/util/log/destinations'
@destinations = {}
@queued = []
class << self
include Puppet::Util
include Puppet::Util::ClassGen
attr_reader :desttypes
end
# Reset log to basics. Basically just flushes and closes files and
# undefs other objects.
def Log.close(destination)
if @destinations.include?(destination)
@destinations[destination].flush if @destinations[destination].respond_to?(:flush)
@destinations[destination].close if @destinations[destination].respond_to?(:close)
@destinations.delete(destination)
end
end
def self.close_all
destinations.keys.each { |dest|
close(dest)
}
end
# Flush any log destinations that support such operations.
def Log.flush
@destinations.each { |type, dest|
dest.flush if dest.respond_to?(:flush)
}
end
# Create a new log message. The primary role of this method is to
# avoid creating log messages below the loglevel.
def Log.create(hash)
raise Puppet::DevError, "Logs require a level" unless hash.include?(:level)
raise Puppet::DevError, "Invalid log level #{hash[:level]}" unless @levels.index(hash[:level])
@levels.index(hash[:level]) >= @loglevel ? Puppet::Util::Log.new(hash) : nil
end
def Log.destinations
@destinations
end
# Yield each valid level in turn
def Log.eachlevel
@levels.each { |level| yield level }
end
# Return the current log level.
def Log.level
@levels[@loglevel]
end
# Set the current log level.
def Log.level=(level)
level = level.intern unless level.is_a?(Symbol)
raise Puppet::DevError, "Invalid loglevel #{level}" unless @levels.include?(level)
@loglevel = @levels.index(level)
end
def Log.levels
@levels.dup
end
# Create a new log destination.
def Log.newdestination(dest)
# Each destination can only occur once.
if @destinations.find { |name, obj| obj.name == dest }
return
end
name, type = @desttypes.find do |name, klass|
klass.match?(dest)
end
raise Puppet::DevError, "Unknown destination type #{dest}" unless type
begin
if type.instance_method(:initialize).arity == 1
@destinations[dest] = type.new(dest)
else
@destinations[dest] = type.new
end
flushqueue
@destinations[dest]
rescue => detail
puts detail.backtrace if Puppet[:debug]
# If this was our only destination, then add the console back in.
newdestination(:console) if @destinations.empty? and (dest != :console and dest != "console")
end
end
# Route the actual message. FIXME There are lots of things this method
# should do, like caching and a bit more. It's worth noting that there's
# a potential for a loop here, if the machine somehow gets the destination set as
# itself.
def Log.newmessage(msg)
return if @levels.index(msg.level) < @loglevel
queuemessage(msg) if @destinations.length == 0
@destinations.each do |name, dest|
threadlock(dest) do
dest.handle(msg)
end
end
end
def Log.queuemessage(msg)
@queued.push(msg)
end
def Log.flushqueue
return unless @destinations.size >= 1
@queued.each do |msg|
Log.newmessage(msg)
end
@queued.clear
end
def Log.sendlevel?(level)
@levels.index(level) >= @loglevel
end
# Reopen all of our logs.
def Log.reopen
Puppet.notice "Reopening log files"
types = @destinations.keys
@destinations.each { |type, dest|
dest.close if dest.respond_to?(:close)
}
@destinations.clear
# We need to make sure we always end up with some kind of destination
begin
types.each { |type|
Log.newdestination(type)
}
rescue => detail
if @destinations.empty?
Log.newdestination(:syslog)
Puppet.err detail.to_s
end
end
end
# Is the passed level a valid log level?
def self.validlevel?(level)
@levels.include?(level)
end
- attr_accessor :time, :remote, :file, :line, :version, :source
+ attr_accessor :time, :remote, :file, :line, :source
attr_reader :level, :message
def initialize(args)
self.level = args[:level]
self.message = args[:message]
self.source = args[:source] || "Puppet"
@time = Time.now
if tags = args[:tags]
tags.each { |t| self.tag(t) }
end
- [:file, :line, :version].each do |attr|
+ [:file, :line].each do |attr|
next unless value = args[attr]
send(attr.to_s + "=", value)
end
Log.newmessage(self)
end
def message=(msg)
raise ArgumentError, "Puppet::Util::Log requires a message" unless msg
@message = msg.to_s
end
def level=(level)
raise ArgumentError, "Puppet::Util::Log requires a log level" unless level
@level = level.to_sym
raise ArgumentError, "Invalid log level #{@level}" unless self.class.validlevel?(@level)
# Tag myself with my log level
tag(level)
end
# If they pass a source in to us, we make sure it is a string, and
# we retrieve any tags we can.
def source=(source)
if source.respond_to?(:source_descriptors)
descriptors = source.source_descriptors
@source = descriptors[:path]
descriptors[:tags].each { |t| tag(t) }
- [:file, :line, :version].each do |param|
+ [:file, :line].each do |param|
next unless descriptors[param]
send(param.to_s + "=", descriptors[param])
end
else
@source = source.to_s
end
end
def to_report
"#{time} #{source} (#{level}): #{to_s}"
end
def to_s
message
end
end
# This is for backward compatibility from when we changed the constant to Puppet::Util::Log
# because the reports include the constant name. Apparently the alias was created in
# March 2007, should could probably be removed soon.
Puppet::Log = Puppet::Util::Log
diff --git a/lib/puppet/util/log_paths.rb b/lib/puppet/util/log_paths.rb
index f59197ed1..2fefd4505 100644
--- a/lib/puppet/util/log_paths.rb
+++ b/lib/puppet/util/log_paths.rb
@@ -1,27 +1,27 @@
# Created by Luke Kanies on 2007-07-04.
# Copyright (c) 2007. All rights reserved.
module Puppet::Util::LogPaths
# return the full path to us, for logging and rollback
# some classes (e.g., FileTypeRecords) will have to override this
def path
@path ||= pathbuilder
"/" + @path.join("/")
end
def source_descriptors
descriptors = {}
descriptors[:tags] = tags
- [:path, :file, :line, :version].each do |param|
+ [:path, :file, :line].each do |param|
next unless value = send(param)
descriptors[param] = value
end
descriptors
end
end
diff --git a/lib/puppet/util/logging.rb b/lib/puppet/util/logging.rb
index f20444a3b..bc52b17f0 100644
--- a/lib/puppet/util/logging.rb
+++ b/lib/puppet/util/logging.rb
@@ -1,40 +1,40 @@
# A module to make logging a bit easier.
require 'puppet/util/log'
module Puppet::Util::Logging
def send_log(level, message)
Puppet::Util::Log.create({:level => level, :source => log_source, :message => message}.merge(log_metadata))
end
# Create a method for each log level.
Puppet::Util::Log.eachlevel do |level|
define_method(level) do |args|
args = args.join(" ") if args.is_a?(Array)
send_log(level, args)
end
end
private
def is_resource?
defined?(Puppet::Type) && is_a?(Puppet::Type)
end
def is_resource_parameter?
defined?(Puppet::Parameter) && is_a?(Puppet::Parameter)
end
def log_metadata
- [:file, :line, :version, :tags].inject({}) do |result, attr|
+ [:file, :line, :tags].inject({}) do |result, attr|
result[attr] = send(attr) if respond_to?(attr)
result
end
end
def log_source
# We need to guard the existence of the constants, since this module is used by the base Puppet module.
(is_resource? or is_resource_parameter?) and respond_to?(:path) and return path.to_s
to_s
end
end
diff --git a/lib/puppet/util/metric.rb b/lib/puppet/util/metric.rb
index 8f55e7b44..09bbb6137 100644
--- a/lib/puppet/util/metric.rb
+++ b/lib/puppet/util/metric.rb
@@ -1,187 +1,188 @@
# included so we can test object types
require 'puppet'
# A class for handling metrics. This is currently ridiculously hackish.
class Puppet::Util::Metric
attr_accessor :type, :name, :value, :label
attr_writer :values
attr_writer :basedir
# Return a specific value
def [](name)
if value = @values.find { |v| v[0] == name }
return value[2]
else
return 0
end
end
def basedir
if defined?(@basedir)
@basedir
else
Puppet[:rrddir]
end
end
def create(start = nil)
Puppet.settings.use(:main, :metrics)
start ||= Time.now.to_i - 5
args = []
if Puppet.features.rrd_legacy? && ! Puppet.features.rrd?
@rrd = RRDtool.new(self.path)
end
values.each { |value|
# the 7200 is the heartbeat -- this means that any data that isn't
# more frequently than every two hours gets thrown away
args.push "DS:#{value[0]}:GAUGE:7200:U:U"
}
args.push "RRA:AVERAGE:0.5:1:300"
begin
if Puppet.features.rrd_legacy? && ! Puppet.features.rrd?
@rrd.create( Puppet[:rrdinterval].to_i, start, args)
else
RRD.create( self.path, '-s', Puppet[:rrdinterval].to_i.to_s, '-b', start.to_i.to_s, *args)
end
rescue => detail
raise "Could not create RRD file #{path}: #{detail}"
end
end
def dump
if Puppet.features.rrd_legacy? && ! Puppet.features.rrd?
puts @rrd.info
else
puts RRD.info(self.path)
end
end
def graph(range = nil)
unless Puppet.features.rrd? || Puppet.features.rrd_legacy?
Puppet.warning "RRD library is missing; cannot graph metrics"
return
end
unit = 60 * 60 * 24
colorstack = %w{#00ff00 #ff0000 #0000ff #ffff00 #ff99ff #ff9966 #66ffff #990000 #099000 #000990 #f00990 #0f0f0f #555555 #333333 #ffffff}
{:daily => unit, :weekly => unit * 7, :monthly => unit * 30, :yearly => unit * 365}.each do |name, time|
file = self.path.sub(/\.rrd$/, "-#{name}.png")
args = [file]
args.push("--title",self.label)
args.push("--imgformat","PNG")
args.push("--interlace")
i = 0
defs = []
lines = []
#p @values.collect { |s,l| s }
values.zip(colorstack).each { |value,color|
next if value.nil?
# this actually uses the data label
defs.push("DEF:#{value[0]}=#{self.path}:#{value[0]}:AVERAGE")
lines.push("LINE2:#{value[0]}#{color}:#{value[1]}")
}
args << defs
args << lines
args.flatten!
if range
if Puppet.features.rrd_legacy? && ! Puppet.features.rrd?
args.push("--start",range[0],"--end",range[1])
else
args.push("--start",range[0].to_i.to_s,"--end",range[1].to_i.to_s)
end
else
if Puppet.features.rrd_legacy? && ! Puppet.features.rrd?
args.push("--start", Time.now.to_i - time, "--end", Time.now.to_i)
else
args.push("--start", (Time.now.to_i - time).to_s, "--end", Time.now.to_i.to_s)
end
end
begin
#Puppet.warning "args = #{args}"
if Puppet.features.rrd_legacy? && ! Puppet.features.rrd?
RRDtool.graph( args )
else
RRD.graph( *args )
end
rescue => detail
Puppet.err "Failed to graph #{self.name}: #{detail}"
end
end
end
def initialize(name,label = nil)
@name = name.to_s
@label = label || labelize(name)
@values = []
end
def path
File.join(self.basedir, @name + ".rrd")
end
def newvalue(name,value,label = nil)
+ raise ArgumentError.new("metric name #{name.inspect} is not a string") unless name.is_a? String
label ||= labelize(name)
@values.push [name,label,value]
end
def store(time)
unless Puppet.features.rrd? || Puppet.features.rrd_legacy?
Puppet.warning "RRD library is missing; cannot store metrics"
return
end
self.create(time - 5) unless FileTest.exists?(self.path)
if Puppet.features.rrd_legacy? && ! Puppet.features.rrd?
@rrd ||= RRDtool.new(self.path)
end
# XXX this is not terribly error-resistant
args = [time]
temps = []
values.each { |value|
#Puppet.warning "value[0]: #{value[0]}; value[1]: #{value[1]}; value[2]: #{value[2]}; "
args.push value[2]
temps.push value[0]
}
arg = args.join(":")
template = temps.join(":")
begin
if Puppet.features.rrd_legacy? && ! Puppet.features.rrd?
@rrd.update( template, [ arg ] )
else
RRD.update( self.path, '-t', template, arg )
end
#system("rrdtool updatev #{self.path} '#{arg}'")
rescue => detail
raise Puppet::Error, "Failed to update #{self.name}: #{detail}"
end
end
def values
@values.sort { |a, b| a[1] <=> b[1] }
end
private
# Convert a name into a label.
def labelize(name)
name.to_s.capitalize.gsub("_", " ")
end
end
# This is necessary because we changed the class path in early 2007,
# and reports directly yaml-dump these metrics, so both client and server
# have to agree on the class name.
Puppet::Metric = Puppet::Util::Metric
diff --git a/spec/integration/application/apply_spec.rb b/spec/integration/application/apply_spec.rb
index 840917eff..363aa3469 100755
--- a/spec/integration/application/apply_spec.rb
+++ b/spec/integration/application/apply_spec.rb
@@ -1,33 +1,32 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet_spec/files'
require 'puppet/application/apply'
describe "apply" do
include PuppetSpec::Files
- describe "when applying provided catalogs" do
- confine "PSON library is missing; cannot test applying catalogs" => Puppet.features.pson?
+ describe "when applying provided catalogs", :if => Puppet.features.pson? do
it "should be able to apply catalogs provided in a file in pson" do
file_to_create = tmpfile("pson_catalog")
catalog = Puppet::Resource::Catalog.new
resource = Puppet::Resource.new(:file, file_to_create, :parameters => {:content => "my stuff"})
catalog.add_resource resource
manifest = tmpfile("manifest")
File.open(manifest, "w") { |f| f.print catalog.to_pson }
puppet = Puppet::Application[:apply]
puppet.options[:catalog] = manifest
puppet.apply
File.should be_exist(file_to_create)
File.read(file_to_create).should == "my stuff"
end
end
end
diff --git a/spec/integration/indirector/catalog/queue_spec.rb b/spec/integration/indirector/catalog/queue_spec.rb
index 0f8bd41fb..4581e3062 100755
--- a/spec/integration/indirector/catalog/queue_spec.rb
+++ b/spec/integration/indirector/catalog/queue_spec.rb
@@ -1,61 +1,58 @@
#!/usr/bin/env ruby
Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
require 'puppet/resource/catalog'
-
-describe "Puppet::Resource::Catalog::Queue" do
- confine "Missing pson support; cannot test queue" => Puppet.features.pson?
-
+describe "Puppet::Resource::Catalog::Queue", :if => Puppet.features.pson? do
before do
Puppet::Resource::Catalog.indirection.terminus(:queue)
@catalog = Puppet::Resource::Catalog.new
@one = Puppet::Resource.new(:file, "/one")
@two = Puppet::Resource.new(:file, "/two")
@catalog.add_resource(@one, @two)
@catalog.add_edge(@one, @two)
Puppet[:trace] = true
end
after { Puppet.settings.clear }
it "should render catalogs to pson and send them via the queue client when catalogs are saved" do
terminus = Puppet::Resource::Catalog.indirection.terminus(:queue)
client = mock 'client'
terminus.stubs(:client).returns client
client.expects(:send_message).with(:catalog, @catalog.to_pson)
request = Puppet::Indirector::Request.new(:catalog, :save, "foo", :instance => @catalog)
terminus.save(request)
end
it "should intern catalog messages when they are passed via a subscription" do
client = mock 'client'
Puppet::Resource::Catalog::Queue.stubs(:client).returns client
pson = @catalog.to_pson
client.expects(:subscribe).with(:catalog).yields(pson)
Puppet.expects(:err).never
result = []
Puppet::Resource::Catalog::Queue.subscribe do |catalog|
result << catalog
end
catalog = result.shift
catalog.should be_instance_of(Puppet::Resource::Catalog)
catalog.resource(:file, "/one").should be_instance_of(Puppet::Resource)
catalog.resource(:file, "/two").should be_instance_of(Puppet::Resource)
catalog.should be_edge(catalog.resource(:file, "/one"), catalog.resource(:file, "/two"))
end
end
diff --git a/spec/integration/indirector/report/rest_spec.rb b/spec/integration/indirector/report/rest_spec.rb
index fdc218975..5b5b2ddd8 100644
--- a/spec/integration/indirector/report/rest_spec.rb
+++ b/spec/integration/indirector/report/rest_spec.rb
@@ -1,97 +1,93 @@
#!/usr/bin/env ruby
Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
require 'puppet/transaction/report'
require 'puppet/network/server'
require 'puppet/network/http/webrick/rest'
describe "Report REST Terminus" do
before do
Puppet[:masterport] = 34343
Puppet[:server] = "localhost"
# Get a safe temporary file
@tmpfile = Tempfile.new("webrick_integration_testing")
@dir = @tmpfile.path + "_dir"
Puppet.settings[:confdir] = @dir
Puppet.settings[:vardir] = @dir
Puppet.settings[:group] = Process.gid
Puppet.settings[:server] = "127.0.0.1"
Puppet.settings[:masterport] = "34343"
Puppet::Util::Cacher.expire
Puppet[:servertype] = 'webrick'
Puppet[:server] = '127.0.0.1'
Puppet[:certname] = '127.0.0.1'
# Generate the certificate with a local CA
Puppet::SSL::Host.ca_location = :local
ca = Puppet::SSL::CertificateAuthority.new
ca.generate(Puppet[:certname]) unless Puppet::SSL::Certificate.find(Puppet[:certname])
ca.generate("foo.madstop.com") unless Puppet::SSL::Certificate.find(Puppet[:certname])
@host = Puppet::SSL::Host.new(Puppet[:certname])
@params = { :port => 34343, :handlers => [ :report ] }
@server = Puppet::Network::Server.new(@params)
@server.listen
# Let's use REST for our reports :-)
@old_terminus = Puppet::Transaction::Report.indirection.terminus_class
Puppet::Transaction::Report.terminus_class = :rest
# LAK:NOTE We need to have a fake model here so that our indirected methods get
# passed through REST; otherwise we'd be stubbing 'save', which would cause an immediate
# return.
@report = stub_everything 'report'
@mock_model = stub_everything 'faked model', :name => "report", :convert_from => @report
Puppet::Indirector::Request.any_instance.stubs(:model).returns(@mock_model)
Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:check_authorization)
end
after do
Puppet::Network::HttpPool.expire
Puppet::SSL::Host.ca_location = :none
Puppet.settings.clear
@server.unlisten
Puppet::Transaction::Report.terminus_class = @old_terminus
end
it "should be able to send a report to the server" do
@report.expects(:save)
- report = Puppet::Transaction::Report.new
+ report = Puppet::Transaction::Report.new("apply")
resourcemetrics = {
- :total => 12,
- :out_of_sync => 20,
- :applied => 45,
- :skipped => 1,
- :restarted => 23,
- :failed_restarts => 1,
- :scheduled => 10
+ "total" => 12,
+ "out_of_sync" => 20,
+ "applied" => 45,
+ "skipped" => 1,
+ "restarted" => 23,
+ "failed_restarts" => 1,
+ "scheduled" => 10
}
report.add_metric(:resources, resourcemetrics)
timemetrics = {
- :resource1 => 10,
- :resource2 => 50,
- :resource3 => 40,
- :resource4 => 20,
+ "resource1" => 10,
+ "resource2" => 50,
+ "resource3" => 40,
+ "resource4" => 20,
}
report.add_metric(:times, timemetrics)
- report.add_metric(
- :changes,
-
- :total => 20
- )
+ report.add_metric(:changes, "total" => 20)
report.save
end
end
diff --git a/spec/integration/network/formats_spec.rb b/spec/integration/network/formats_spec.rb
index e6cf28abb..d141abf00 100755
--- a/spec/integration/network/formats_spec.rb
+++ b/spec/integration/network/formats_spec.rb
@@ -1,110 +1,107 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/network/formats'
class PsonIntTest
attr_accessor :string
def ==(other)
other.class == self.class and string == other.string
end
def self.from_pson(data)
new(data[0])
end
def initialize(string)
@string = string
end
def to_pson(*args)
{
'type' => self.class.name,
'data' => [@string]
}.to_pson(*args)
end
def self.canonical_order(s)
s.gsub(/\{"data":\[(.*?)\],"type":"PsonIntTest"\}/,'{"type":"PsonIntTest","data":[\1]}')
end
end
describe Puppet::Network::FormatHandler.format(:s) do
before do
@format = Puppet::Network::FormatHandler.format(:s)
end
it "should support certificates" do
@format.should be_supported(Puppet::SSL::Certificate)
end
it "should not support catalogs" do
@format.should_not be_supported(Puppet::Resource::Catalog)
end
end
describe Puppet::Network::FormatHandler.format(:pson) do
- describe "when pson is absent" do
- confine "'pson' library is present" => (! Puppet.features.pson?)
+ describe "when pson is absent", :if => (! Puppet.features.pson?) do
before do
@pson = Puppet::Network::FormatHandler.format(:pson)
end
it "should not be suitable" do
@pson.should_not be_suitable
end
end
- describe "when pson is available" do
- confine "Missing 'pson' library" => Puppet.features.pson?
-
+ describe "when pson is available", :if => Puppet.features.pson? do
before do
@pson = Puppet::Network::FormatHandler.format(:pson)
end
it "should be able to render an instance to pson" do
instance = PsonIntTest.new("foo")
PsonIntTest.canonical_order(@pson.render(instance)).should == PsonIntTest.canonical_order('{"type":"PsonIntTest","data":["foo"]}' )
end
it "should be able to render arrays to pson" do
@pson.render([1,2]).should == '[1,2]'
end
it "should be able to render arrays containing hashes to pson" do
@pson.render([{"one"=>1},{"two"=>2}]).should == '[{"one":1},{"two":2}]'
end
it "should be able to render multiple instances to pson" do
Puppet.features.add(:pson, :libs => ["pson"])
one = PsonIntTest.new("one")
two = PsonIntTest.new("two")
PsonIntTest.canonical_order(@pson.render([one,two])).should == PsonIntTest.canonical_order('[{"type":"PsonIntTest","data":["one"]},{"type":"PsonIntTest","data":["two"]}]')
end
it "should be able to intern pson into an instance" do
@pson.intern(PsonIntTest, '{"type":"PsonIntTest","data":["foo"]}').should == PsonIntTest.new("foo")
end
it "should be able to intern pson with no class information into an instance" do
@pson.intern(PsonIntTest, '["foo"]').should == PsonIntTest.new("foo")
end
it "should be able to intern multiple instances from pson" do
@pson.intern_multiple(PsonIntTest, '[{"type": "PsonIntTest", "data": ["one"]},{"type": "PsonIntTest", "data": ["two"]}]').should == [
PsonIntTest.new("one"), PsonIntTest.new("two")
]
end
it "should be able to intern multiple instances from pson with no class information" do
@pson.intern_multiple(PsonIntTest, '[["one"],["two"]]').should == [
PsonIntTest.new("one"), PsonIntTest.new("two")
]
end
end
end
diff --git a/spec/integration/network/server/mongrel_spec.rb b/spec/integration/network/server/mongrel_spec.rb
index cc90773e6..c2815b565 100755
--- a/spec/integration/network/server/mongrel_spec.rb
+++ b/spec/integration/network/server/mongrel_spec.rb
@@ -1,66 +1,65 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../spec_helper'
require 'puppet/network/server'
require 'socket'
describe Puppet::Network::Server do
- describe "when using mongrel" do
- confine "Mongrel is not available" => Puppet.features.mongrel?
+ describe "when using mongrel", :if => Puppet.features.mongrel? do
before :each do
Puppet[:servertype] = 'mongrel'
Puppet[:server] = '127.0.0.1'
@params = { :port => 34346, :handlers => [ :node ] }
@server = Puppet::Network::Server.new(@params)
end
after { Puppet.settings.clear }
describe "before listening" do
it "should not be reachable at the specified address and port" do
lambda { TCPSocket.new('127.0.0.1', 34346) }.should raise_error(Errno::ECONNREFUSED)
end
end
describe "when listening" do
it "should be reachable on the specified address and port" do
@server.listen
lambda { TCPSocket.new('127.0.0.1', 34346) }.should_not raise_error
end
it "should default to '127.0.0.1' as its bind address" do
@server = Puppet::Network::Server.new(@params.merge(:port => 34343))
@server.stubs(:unlisten) # we're breaking listening internally, so we have to keep it from unlistening
@server.send(:http_server).expects(:listen).with { |args| args[:address] == "127.0.0.1" }
@server.listen
end
it "should use any specified bind address" do
Puppet[:bindaddress] = "0.0.0.0"
@server = Puppet::Network::Server.new(@params.merge(:port => 34343))
@server.stubs(:unlisten) # we're breaking listening internally, so we have to keep it from unlistening
@server.send(:http_server).expects(:listen).with { |args| args[:address] == "0.0.0.0" }
@server.listen
end
it "should not allow multiple servers to listen on the same address and port" do
@server.listen
@server2 = Puppet::Network::Server.new(@params)
lambda { @server2.listen }.should raise_error
end
end
describe "after unlistening" do
it "should not be reachable on the port and address assigned" do
@server.listen
@server.unlisten
lambda { TCPSocket.new('127.0.0.1', 34346) }.should raise_error(Errno::ECONNREFUSED)
end
end
after :each do
@server.unlisten if @server.listening?
end
end
end
diff --git a/spec/integration/provider/mailalias/aliases_spec.rb b/spec/integration/provider/mailalias/aliases_spec.rb
index 0511205f2..1bce13f90 100755
--- a/spec/integration/provider/mailalias/aliases_spec.rb
+++ b/spec/integration/provider/mailalias/aliases_spec.rb
@@ -1,25 +1,24 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../spec_helper'
require 'puppettest/support/utils'
require 'puppettest/fileparsing'
provider_class = Puppet::Type.type(:mailalias).provider(:aliases)
describe provider_class do
- include PuppetTest
include PuppetTest::FileParsing
include PuppetTest::Support::Utils
before :each do
@provider = provider_class
end
# #1560
it "should be able to parse the mailalias examples" do
fakedata("data/providers/mailalias/aliases").each { |file|
fakedataparse(file)
}
end
end
diff --git a/spec/integration/provider/package_spec.rb b/spec/integration/provider/package_spec.rb
index 736a34e68..472662d6b 100755
--- a/spec/integration/provider/package_spec.rb
+++ b/spec/integration/provider/package_spec.rb
@@ -1,26 +1,24 @@
#!/usr/bin/env ruby
Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
describe "Package Provider" do
Puppet::Type.type(:package).providers.each do |name|
provider = Puppet::Type.type(:package).provider(name)
- describe name do
- confine "Provider #{name} is not suitable" => provider.suitable?
-
+ describe name, :if => provider.suitable? do
it "should fail when asked to install an invalid package" do
pending("This test hangs forever with recent versions of RubyGems") if provider.name == :gem
pkg = Puppet::Type.newpackage :name => "nosuch#{provider.name}", :provider => provider.name
lambda { pkg.provider.install }.should raise_error
end
it "should be able to get a list of existing packages" do
provider.instances.each do |package|
package.should be_instance_of(provider)
package.properties[:provider].should == provider.name
end
end
end
end
end
diff --git a/spec/integration/provider/service/init_spec.rb b/spec/integration/provider/service/init_spec.rb
index d916ab32a..2e2505bd4 100755
--- a/spec/integration/provider/service/init_spec.rb
+++ b/spec/integration/provider/service/init_spec.rb
@@ -1,32 +1,26 @@
#!/usr/bin/env ruby
# Find and load the spec file.
Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
provider = Puppet::Type.type(:service).provider(:init)
describe provider do
- describe "when running on FreeBSD" do
- confine "Not running on FreeBSD" => (Facter.value(:operatingsystem) == "FreeBSD")
-
+ describe "when running on FreeBSD", :if => (Facter.value(:operatingsystem) == "FreeBSD") do
it "should set its default path to include /etc/init.d and /usr/local/etc/init.d" do
provider.defpath.should == ["/etc/rc.d", "/usr/local/etc/rc.d"]
end
end
- describe "when running on HP-UX" do
- confine "Not running on HP-UX" => (Facter.value(:operatingsystem) == "HP-UX")
-
+ describe "when running on HP-UX", :if => (Facter.value(:operatingsystem) == "HP-UX")do
it "should set its default path to include /sbin/init.d" do
provider.defpath.should == "/sbin/init.d"
end
end
- describe "when not running on FreeBSD or HP-UX" do
- confine "Running on HP-UX or FreeBSD" => (! %w{HP-UX FreeBSD}.include?(Facter.value(:operatingsystem)))
-
+ describe "when not running on FreeBSD or HP-UX", :if => (! %w{HP-UX FreeBSD}.include?(Facter.value(:operatingsystem))) do
it "should set its default path to include /etc/init.d" do
provider.defpath.should == "/etc/init.d"
end
end
end
diff --git a/spec/integration/resource/catalog_spec.rb b/spec/integration/resource/catalog_spec.rb
index 0a3d47a80..da2b70409 100755
--- a/spec/integration/resource/catalog_spec.rb
+++ b/spec/integration/resource/catalog_spec.rb
@@ -1,61 +1,60 @@
#!/usr/bin/env ruby
#
# Created by Luke Kanies on 2007-4-8.
# Copyright (c) 2008. All rights reserved.
require File.dirname(__FILE__) + '/../../spec_helper'
describe Puppet::Resource::Catalog do
- describe "when pson is available" do
- confine "PSON library is missing" => Puppet.features.pson?
+ describe "when pson is available", :if => Puppet.features.pson? do
it "should support pson" do
Puppet::Resource::Catalog.supported_formats.should be_include(:pson)
end
end
describe "when using the indirector" do
after { Puppet::Util::Cacher.expire }
before do
# This is so the tests work w/out networking.
Facter.stubs(:to_hash).returns({"hostname" => "foo.domain.com"})
Facter.stubs(:value).returns("eh")
end
it "should be able to delegate to the :yaml terminus" do
Puppet::Resource::Catalog.indirection.stubs(:terminus_class).returns :yaml
# Load now, before we stub the exists? method.
terminus = Puppet::Resource::Catalog.indirection.terminus(:yaml)
terminus.expects(:path).with("me").returns "/my/yaml/file"
FileTest.expects(:exist?).with("/my/yaml/file").returns false
Puppet::Resource::Catalog.find("me").should be_nil
end
it "should be able to delegate to the :compiler terminus" do
Puppet::Resource::Catalog.indirection.stubs(:terminus_class).returns :compiler
# Load now, before we stub the exists? method.
compiler = Puppet::Resource::Catalog.indirection.terminus(:compiler)
node = mock 'node'
node.stub_everything
Puppet::Node.expects(:find).returns(node)
compiler.expects(:compile).with(node).returns nil
Puppet::Resource::Catalog.find("me").should be_nil
end
it "should pass provided node information directly to the terminus" do
terminus = mock 'terminus'
Puppet::Resource::Catalog.indirection.stubs(:terminus).returns terminus
node = mock 'node'
terminus.expects(:find).with { |request| request.options[:use_node] == node }
Puppet::Resource::Catalog.find("me", :use_node => node)
end
end
end
diff --git a/spec/integration/transaction/report_spec.rb b/spec/integration/transaction/report_spec.rb
index eed7acaa9..e7d952eb2 100755
--- a/spec/integration/transaction/report_spec.rb
+++ b/spec/integration/transaction/report_spec.rb
@@ -1,29 +1,29 @@
#!/usr/bin/env ruby
#
# Created by Luke Kanies on 2008-4-8.
# Copyright (c) 2008. All rights reserved.
require File.dirname(__FILE__) + '/../../spec_helper'
describe Puppet::Transaction::Report do
describe "when using the indirector" do
after do
Puppet::Util::Cacher.expire
Puppet.settings.stubs(:use)
end
it "should be able to delegate to the :processor terminus" do
Puppet::Transaction::Report.indirection.stubs(:terminus_class).returns :processor
terminus = Puppet::Transaction::Report.indirection.terminus(:processor)
Facter.stubs(:value).returns "host.domain.com"
- report = Puppet::Transaction::Report.new
+ report = Puppet::Transaction::Report.new("apply")
terminus.expects(:process).with(report)
report.save
end
end
end
diff --git a/spec/monkey_patches/add_confine_and_runnable_to_rspec_dsl.rb b/spec/monkey_patches/add_confine_and_runnable_to_rspec_dsl.rb
deleted file mode 100644
index 3762b7033..000000000
--- a/spec/monkey_patches/add_confine_and_runnable_to_rspec_dsl.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-dir = File.expand_path(File.dirname(__FILE__))
-[ "#{dir}/../../lib", "#{dir}/../../test/lib"].each do |dir|
- fulldir = File.expand_path(dir)
- $LOAD_PATH.unshift(fulldir) unless $LOAD_PATH.include?(fulldir)
-end
-
-require 'spec'
-require 'puppettest/runnable_test'
-
-module Spec
- module Runner
- class ExampleGroupRunner
- def run
- prepare
- success = true
- example_groups.each do |example_group|
- unless example_group.runnable?
- warn "Skipping unsuitable example group #{example_group.description}: #{example_group.messages.join(", ")}"
- next
- end
- success = success & example_group.run(@options)
- Puppet.settings.clear
- end
- return success
- ensure
- finish
- end
- end
- end
-end
-
-module Spec
- module Example
- class ExampleGroup
- extend PuppetTest::RunnableTest
- end
- end
-end
-
-module Test
- module Unit
- class TestCase
- extend PuppetTest::RunnableTest
- end
- end
-end
diff --git a/spec/monkey_patches/alias_should_to_must.rb b/spec/monkey_patches/alias_should_to_must.rb
index c8744136a..1a1111799 100644
--- a/spec/monkey_patches/alias_should_to_must.rb
+++ b/spec/monkey_patches/alias_should_to_must.rb
@@ -1,6 +1,8 @@
+require 'rspec'
+
class Object
# This is necessary because the RAL has a 'should'
# method.
alias :must :should
alias :must_not :should_not
end
diff --git a/spec/shared_behaviours/file_server_terminus.rb b/spec/shared_behaviours/file_server_terminus.rb
index 665b46cd5..94a044d2e 100644
--- a/spec/shared_behaviours/file_server_terminus.rb
+++ b/spec/shared_behaviours/file_server_terminus.rb
@@ -1,45 +1,45 @@
#!/usr/bin/env ruby
#
# Created by Luke Kanies on 2007-10-18.
# Copyright (c) 2007. All rights reserved.
-describe "Puppet::Indirector::FileServerTerminus", :shared => true do
+shared_examples_for "Puppet::Indirector::FileServerTerminus" do
# This only works if the shared behaviour is included before
# the 'before' block in the including context.
before do
Puppet::Util::Cacher.expire
FileTest.stubs(:exists?).returns true
FileTest.stubs(:exists?).with(Puppet[:fileserverconfig]).returns(true)
@path = Tempfile.new("file_server_testing")
path = @path.path
@path.close!
@path = path
Dir.mkdir(@path)
File.open(File.join(@path, "myfile"), "w") { |f| f.print "my content" }
# Use a real mount, so the integration is a bit deeper.
@mount1 = Puppet::FileServing::Configuration::Mount::File.new("one")
@mount1.path = @path
@parser = stub 'parser', :changed? => false
@parser.stubs(:parse).returns("one" => @mount1)
Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser)
# Stub out the modules terminus
@modules = mock 'modules terminus'
@request = Puppet::Indirector::Request.new(:indirection, :method, "puppet://myhost/one/myfile")
end
it "should use the file server configuration to find files" do
@modules.stubs(:find).returns(nil)
@terminus.indirection.stubs(:terminus).with(:modules).returns(@modules)
path = File.join(@path, "myfile")
@terminus.find(@request).should be_instance_of(@test_class)
end
end
diff --git a/spec/shared_behaviours/file_serving.rb b/spec/shared_behaviours/file_serving.rb
index 5f5b2b0af..450fff87a 100644
--- a/spec/shared_behaviours/file_serving.rb
+++ b/spec/shared_behaviours/file_serving.rb
@@ -1,71 +1,71 @@
#!/usr/bin/env ruby
#
# Created by Luke Kanies on 2007-10-18.
# Copyright (c) 2007. All rights reserved.
-describe "Puppet::FileServing::Files", :shared => true do
+shared_examples_for "Puppet::FileServing::Files" do
it "should use the rest terminus when the 'puppet' URI scheme is used and a host name is present" do
uri = "puppet://myhost/fakemod/my/file"
# It appears that the mocking somehow interferes with the caching subsystem.
# This mock somehow causes another terminus to get generated.
term = @indirection.terminus(:rest)
@indirection.stubs(:terminus).with(:rest).returns term
term.expects(:find)
@test_class.find(uri)
end
it "should use the rest terminus when the 'puppet' URI scheme is used, no host name is present, and the process name is not 'puppet' or 'apply'" do
uri = "puppet:///fakemod/my/file"
Puppet.settings.stubs(:value).returns "foo"
Puppet.settings.stubs(:value).with(:name).returns("puppetd")
Puppet.settings.stubs(:value).with(:modulepath).returns("")
@indirection.terminus(:rest).expects(:find)
@test_class.find(uri)
end
it "should use the file_server terminus when the 'puppet' URI scheme is used, no host name is present, and the process name is 'puppet'" do
uri = "puppet:///fakemod/my/file"
Puppet::Node::Environment.stubs(:new).returns(stub("env", :name => "testing", :module => nil, :modulepath => []))
Puppet.settings.stubs(:value).returns ""
Puppet.settings.stubs(:value).with(:name).returns("puppet")
Puppet.settings.stubs(:value).with(:fileserverconfig).returns("/whatever")
@indirection.terminus(:file_server).expects(:find)
@indirection.terminus(:file_server).stubs(:authorized?).returns(true)
@test_class.find(uri)
end
it "should use the file_server terminus when the 'puppet' URI scheme is used, no host name is present, and the process name is 'apply'" do
uri = "puppet:///fakemod/my/file"
Puppet::Node::Environment.stubs(:new).returns(stub("env", :name => "testing", :module => nil, :modulepath => []))
Puppet.settings.stubs(:value).returns ""
Puppet.settings.stubs(:value).with(:name).returns("apply")
Puppet.settings.stubs(:value).with(:fileserverconfig).returns("/whatever")
@indirection.terminus(:file_server).expects(:find)
@indirection.terminus(:file_server).stubs(:authorized?).returns(true)
@test_class.find(uri)
end
it "should use the file terminus when the 'file' URI scheme is used" do
uri = "file:///fakemod/my/file"
@indirection.terminus(:file).expects(:find)
@test_class.find(uri)
end
it "should use the file terminus when a fully qualified path is provided" do
uri = "/fakemod/my/file"
@indirection.terminus(:file).expects(:find)
@test_class.find(uri)
end
it "should use the configuration to test whether the request is allowed" do
uri = "fakemod/my/file"
mount = mock 'mount'
config = stub 'configuration', :split_path => [mount, "eh"]
@indirection.terminus(:file_server).stubs(:configuration).returns config
@indirection.terminus(:file_server).expects(:find)
mount.expects(:allowed?).returns(true)
@test_class.find(uri, :node => "foo", :ip => "bar")
end
end
diff --git a/spec/shared_behaviours/memory_terminus.rb b/spec/shared_behaviours/memory_terminus.rb
index 5c9f35cca..f9325a969 100644
--- a/spec/shared_behaviours/memory_terminus.rb
+++ b/spec/shared_behaviours/memory_terminus.rb
@@ -1,32 +1,32 @@
#
# Created by Luke Kanies on 2008-4-8.
# Copyright (c) 2008. All rights reserved.
-describe "A Memory Terminus", :shared => true do
+shared_examples_for "A Memory Terminus" do
it "should find no instances by default" do
@searcher.find(@request).should be_nil
end
it "should be able to find instances that were previously saved" do
@searcher.save(@request)
@searcher.find(@request).should equal(@instance)
end
it "should replace existing saved instances when a new instance with the same name is saved" do
@searcher.save(@request)
two = stub 'second', :name => @name
trequest = stub 'request', :key => @name, :instance => two
@searcher.save(trequest)
@searcher.find(@request).should equal(two)
end
it "should be able to remove previously saved instances" do
@searcher.save(@request)
@searcher.destroy(@request)
@searcher.find(@request).should be_nil
end
it "should fail when asked to destroy an instance that does not exist" do
proc { @searcher.destroy(@request) }.should raise_error(ArgumentError)
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index ed4e2c2fb..ed4e826c9 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,79 +1,81 @@
unless defined?(SPEC_HELPER_IS_LOADED)
SPEC_HELPER_IS_LOADED = 1
dir = File.expand_path(File.dirname(__FILE__))
$LOAD_PATH.unshift("#{dir}/")
$LOAD_PATH.unshift("#{dir}/lib") # a spec-specific test lib dir
$LOAD_PATH.unshift("#{dir}/../lib")
+$LOAD_PATH.unshift("#{dir}/../test/lib")
# Don't want puppet getting the command line arguments for rake or autotest
ARGV.clear
require 'puppet'
require 'mocha'
-gem 'rspec', '>=1.2.9'
-require 'spec/autorun'
+gem 'rspec', '>=2.0.0'
# So everyone else doesn't have to include this base constant.
module PuppetSpec
FIXTURE_DIR = File.join(dir = File.expand_path(File.dirname(__FILE__)), "fixtures") unless defined?(FIXTURE_DIR)
end
+module PuppetTest
+end
+
require 'lib/puppet_spec/files'
require 'monkey_patches/alias_should_to_must'
-require 'monkey_patches/add_confine_and_runnable_to_rspec_dsl'
require 'monkey_patches/publicize_methods'
-Spec::Runner.configure do |config|
+RSpec.configure do |config|
config.mock_with :mocha
- config.prepend_after :each do
+ config.after :each do
Puppet.settings.clear
Puppet::Node::Environment.clear
Puppet::Util::Storage.clear
if defined?($tmpfiles)
$tmpfiles.each do |file|
file = File.expand_path(file)
if Puppet.features.posix? and file !~ /^\/tmp/ and file !~ /^\/var\/folders/
puts "Not deleting tmpfile #{file} outside of /tmp or /var/folders"
next
elsif Puppet.features.microsoft_windows?
tempdir = File.expand_path(File.join(Dir::LOCAL_APPDATA, "Temp"))
if file !~ /^#{tempdir}/
puts "Not deleting tmpfile #{file} outside of #{tempdir}"
next
end
end
if FileTest.exist?(file)
system("chmod -R 755 '#{file}'")
system("rm -rf '#{file}'")
end
end
$tmpfiles.clear
end
@logs.clear
Puppet::Util::Log.close_all
end
- config.prepend_before :each do
+ config.before :each do
# these globals are set by Application
$puppet_application_mode = nil
$puppet_application_name = nil
# Set the confdir and vardir to gibberish so that tests
# have to be correctly mocked.
Puppet[:confdir] = "/dev/null"
Puppet[:vardir] = "/dev/null"
# Avoid opening ports to the outside world
Puppet.settings[:bindaddress] = "127.0.0.1"
@logs = []
Puppet::Util::Log.newdestination(@logs)
end
end
end
diff --git a/spec/spec_specs/runnable_spec.rb b/spec/spec_specs/runnable_spec.rb
deleted file mode 100644
index da4faca4e..000000000
--- a/spec/spec_specs/runnable_spec.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-require File.dirname(__FILE__) + '/../spec_helper'
-
-describe PuppetTest::RunnableTest do
- before do
- @runnable_test = Class.new.extend(PuppetTest::RunnableTest)
- end
-
- describe "#confine" do
- subject { @runnable_test }
-
- it "should accept a hash" do
- subject.confine({}).should_not raise_error(ArgumentError)
- end
-
- it "should accept a message and a block" do
- subject.confine(""){}.should_not raise_error(ArgumentError)
- end
-
- end
-
- describe "#runnable?" do
- describe "when the superclass is not runnable" do
- before { @runnable_test.stubs(:superclass).returns(stub("unrunnable superclass", :runnable? => false)) }
- subject { @runnable_test.runnable? }
-
- it { should be_false }
- end
-
- describe "when a confine is false" do
- before { @runnable_test.confine(:message => false) }
- subject { @runnable_test.runnable? }
-
- it { should be_false }
- end
-
- describe "when a confine has a block that returns false" do
- before { @runnable_test.confine(:message){ false } }
- subject { @runnable_test.runnable? }
-
- it { should be_false }
- end
-
- describe "when a confine is true and no false confines" do
- before { @runnable_test.confine(:message => true) }
- subject { @runnable_test.runnable? }
-
- it { should be_true }
- end
-
- describe "when a confine has block that returns true and no false confines" do
- before { @runnable_test.confine(:message){ true } }
- subject { @runnable_test.runnable? }
-
- it { should be_true }
- end
-
- end
-
- describe "#messages" do
- describe "before runnable? is called" do
- subject { @runnable_test.messages }
-
- it { should == [] }
- end
-
- describe "when runnable? is called and returns false" do
- before do
- @runnable_test.confine(:message => false)
- @runnable_test.runnable?
- end
-
- subject { @runnable_test.messages }
-
- it "should include the failed confine's message" do
- should include(:message)
- end
-
- end
-
- describe "when runnable? is called whose block returns false" do
- before do
- @runnable_test.confine(:message){ false }
- @runnable_test.runnable?
- end
-
- subject { @runnable_test.messages }
-
- it "should include the failed confine's message" do
- should include(:message)
- end
-
- end
-
- end
-end
diff --git a/spec/unit/application/apply_spec.rb b/spec/unit/application/apply_spec.rb
index 877c47bcc..4e1744206 100755
--- a/spec/unit/application/apply_spec.rb
+++ b/spec/unit/application/apply_spec.rb
@@ -1,405 +1,405 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/application/apply'
require 'puppet/file_bucket/dipper'
+require 'puppet/configurer'
describe Puppet::Application::Apply do
before :each do
@apply = Puppet::Application[:apply]
Puppet::Util::Log.stubs(:newdestination)
Puppet::Util::Log.stubs(:level=)
end
[:debug,:loadclasses,:verbose,:use_nodes,:detailed_exitcodes].each do |option|
it "should declare handle_#{option} method" do
@apply.should respond_to("handle_#{option}".to_sym)
end
it "should store argument value when calling handle_#{option}" do
@apply.options.expects(:[]=).with(option, 'arg')
@apply.send("handle_#{option}".to_sym, 'arg')
end
end
it "should set the code to the provided code when :execute is used" do
@apply.options.expects(:[]=).with(:code, 'arg')
@apply.send("handle_execute".to_sym, 'arg')
end
it "should ask Puppet::Application to parse Puppet configuration file" do
@apply.should_parse_config?.should be_true
end
describe "when applying options" do
it "should set the log destination with --logdest" do
Puppet::Log.expects(:newdestination).with("console")
@apply.handle_logdest("console")
end
it "should put the logset options to true" do
@apply.options.expects(:[]=).with(:logset,true)
@apply.handle_logdest("console")
end
end
describe "during setup" do
before :each do
Puppet::Log.stubs(:newdestination)
Puppet.stubs(:trap)
Puppet::Log.stubs(:level=)
Puppet.stubs(:parse_config)
Puppet::FileBucket::Dipper.stubs(:new)
STDIN.stubs(:read)
@apply.options.stubs(:[]).with(any_parameters)
end
it "should set show_diff on --noop" do
Puppet.stubs(:[]=)
Puppet.stubs(:[]).with(:config)
Puppet.stubs(:[]).with(:noop).returns(true)
Puppet.expects(:[]=).with(:show_diff, true)
@apply.setup
end
it "should set console as the log destination if logdest option wasn't provided" do
Puppet::Log.expects(:newdestination).with(:console)
@apply.setup
end
it "should set INT trap" do
@apply.expects(:trap).with(:INT)
@apply.setup
end
it "should set log level to debug if --debug was passed" do
@apply.options.stubs(:[]).with(:debug).returns(true)
Puppet::Log.expects(:level=).with(:debug)
@apply.setup
end
it "should set log level to info if --verbose was passed" do
@apply.options.stubs(:[]).with(:verbose).returns(true)
Puppet::Log.expects(:level=).with(:info)
@apply.setup
end
it "should print puppet config if asked to in Puppet config" do
@apply.stubs(:exit)
Puppet.settings.stubs(:print_configs?).returns(true)
Puppet.settings.expects(:print_configs)
@apply.setup
end
it "should exit after printing puppet config if asked to in Puppet config" do
Puppet.settings.stubs(:print_configs?).returns(true)
lambda { @apply.setup }.should raise_error(SystemExit)
end
end
describe "when executing" do
it "should dispatch to parseonly if parseonly is set" do
@apply.stubs(:options).returns({})
Puppet.stubs(:[]).with(:parseonly).returns(true)
@apply.expects(:parseonly)
@apply.run_command
end
it "should dispatch to 'apply' if it was called with 'apply'" do
@apply.options[:catalog] = "foo"
@apply.expects(:apply)
@apply.run_command
end
it "should dispatch to main if parseonly is not set" do
@apply.stubs(:options).returns({})
Puppet.stubs(:[]).with(:parseonly).returns(false)
@apply.expects(:main)
@apply.run_command
end
describe "the parseonly command" do
before :each do
Puppet.stubs(:[]).with(:environment)
Puppet.stubs(:[]).with(:manifest).returns("site.pp")
Puppet.stubs(:err)
@apply.stubs(:exit)
@apply.options.stubs(:[]).with(:code).returns "some code"
@collection = stub_everything
Puppet::Resource::TypeCollection.stubs(:new).returns(@collection)
end
it "should use a Puppet Resource Type Collection to parse the file" do
@collection.expects(:perform_initial_import)
@apply.parseonly
end
it "should exit with exit code 0 if no error" do
@apply.expects(:exit).with(0)
@apply.parseonly
end
it "should exit with exit code 1 if error" do
@collection.stubs(:perform_initial_import).raises(Puppet::ParseError)
@apply.expects(:exit).with(1)
@apply.parseonly
end
end
describe "the main command" do
before :each do
Puppet.stubs(:[])
Puppet.settings.stubs(:use)
Puppet.stubs(:[]).with(:prerun_command).returns ""
Puppet.stubs(:[]).with(:postrun_command).returns ""
Puppet.stubs(:[]).with(:trace).returns(true)
@apply.options.stubs(:[])
@facts = stub_everything 'facts'
Puppet::Node::Facts.stubs(:find).returns(@facts)
@node = stub_everything 'node'
Puppet::Node.stubs(:find).returns(@node)
@catalog = stub_everything 'catalog'
@catalog.stubs(:to_ral).returns(@catalog)
Puppet::Resource::Catalog.stubs(:find).returns(@catalog)
STDIN.stubs(:read)
@transaction = stub_everything 'transaction'
@catalog.stubs(:apply).returns(@transaction)
@apply.stubs(:exit)
+
+ Puppet::Util::Storage.stubs(:load)
+ Puppet::Configurer.any_instance.stubs(:save_last_run_summary) # to prevent it from trying to write files
end
it "should set the code to run from --code" do
@apply.options.stubs(:[]).with(:code).returns("code to run")
Puppet.expects(:[]=).with(:code,"code to run")
@apply.main
end
it "should set the code to run from STDIN if no arguments" do
@apply.command_line.stubs(:args).returns([])
STDIN.stubs(:read).returns("code to run")
Puppet.expects(:[]=).with(:code,"code to run")
@apply.main
end
it "should set the manifest if a file is passed on command line and the file exists" do
File.stubs(:exist?).with('site.pp').returns true
@apply.command_line.stubs(:args).returns(['site.pp'])
Puppet.expects(:[]=).with(:manifest,"site.pp")
@apply.main
end
it "should raise an error if a file is passed on command line and the file does not exist" do
File.stubs(:exist?).with('noexist.pp').returns false
@apply.command_line.stubs(:args).returns(['noexist.pp'])
lambda { @apply.main }.should raise_error(RuntimeError, 'Could not find file noexist.pp')
end
it "should set the manifest to the first file and warn other files will be skipped" do
File.stubs(:exist?).with('starwarsIV').returns true
File.expects(:exist?).with('starwarsI').never
@apply.command_line.stubs(:args).returns(['starwarsIV', 'starwarsI', 'starwarsII'])
Puppet.expects(:[]=).with(:manifest,"starwarsIV")
Puppet.expects(:warning).with('Only one file can be applied per run. Skipping starwarsI, starwarsII')
@apply.main
end
it "should collect the node facts" do
Puppet::Node::Facts.expects(:find).returns(@facts)
@apply.main
end
it "should raise an error if we can't find the node" do
Puppet::Node::Facts.expects(:find).returns(nil)
lambda { @apply.main }.should raise_error
end
it "should look for the node" do
Puppet::Node.expects(:find).returns(@node)
@apply.main
end
it "should raise an error if we can't find the node" do
Puppet::Node.expects(:find).returns(nil)
lambda { @apply.main }.should raise_error
end
it "should merge in our node the loaded facts" do
@facts.stubs(:values).returns("values")
@node.expects(:merge).with("values")
@apply.main
end
it "should load custom classes if loadclasses" do
@apply.options.stubs(:[]).with(:loadclasses).returns(true)
Puppet.stubs(:[]).with(:classfile).returns("/etc/puppet/classes.txt")
FileTest.stubs(:exists?).with("/etc/puppet/classes.txt").returns(true)
FileTest.stubs(:readable?).with("/etc/puppet/classes.txt").returns(true)
File.stubs(:read).with("/etc/puppet/classes.txt").returns("class")
@node.expects(:classes=)
@apply.main
end
it "should compile the catalog" do
Puppet::Resource::Catalog.expects(:find).returns(@catalog)
@apply.main
end
it "should transform the catalog to ral" do
@catalog.expects(:to_ral).returns(@catalog)
@apply.main
end
it "should finalize the catalog" do
@catalog.expects(:finalize)
@apply.main
end
it "should call the prerun and postrun commands on a Configurer instance" do
- configurer = stub 'configurer'
-
- Puppet::Configurer.expects(:new).returns configurer
- configurer.expects(:execute_prerun_command)
- configurer.expects(:execute_postrun_command)
+ Puppet::Configurer.any_instance.expects(:execute_prerun_command)
+ Puppet::Configurer.any_instance.expects(:execute_postrun_command)
@apply.main
end
it "should apply the catalog" do
@catalog.expects(:apply).returns(stub_everything('transaction'))
@apply.main
end
describe "with detailed_exitcodes" do
it "should exit with report's computed exit status" do
Puppet.stubs(:[]).with(:noop).returns(false)
@apply.options.stubs(:[]).with(:detailed_exitcodes).returns(true)
- report = stub 'report', :exit_status => 666
- @transaction.stubs(:report).returns(report)
+ Puppet::Transaction::Report.any_instance.stubs(:exit_status).returns(666)
@apply.expects(:exit).with(666)
@apply.main
end
it "should always exit with 0 if option is disabled" do
Puppet.stubs(:[]).with(:noop).returns(false)
@apply.options.stubs(:[]).with(:detailed_exitcodes).returns(false)
report = stub 'report', :exit_status => 666
@transaction.stubs(:report).returns(report)
@apply.expects(:exit).with(0)
@apply.main
end
it "should always exit with 0 if --noop" do
Puppet.stubs(:[]).with(:noop).returns(true)
@apply.options.stubs(:[]).with(:detailed_exitcodes).returns(true)
report = stub 'report', :exit_status => 666
@transaction.stubs(:report).returns(report)
@apply.expects(:exit).with(0)
@apply.main
end
end
end
describe "the 'apply' command" do
it "should read the catalog in from disk if a file name is provided" do
@apply.options[:catalog] = "/my/catalog.pson"
File.expects(:read).with("/my/catalog.pson").returns "something"
Puppet::Resource::Catalog.stubs(:convert_from).with(:pson,'something').returns Puppet::Resource::Catalog.new
@apply.apply
end
it "should read the catalog in from stdin if '-' is provided" do
@apply.options[:catalog] = "-"
$stdin.expects(:read).returns "something"
Puppet::Resource::Catalog.stubs(:convert_from).with(:pson,'something').returns Puppet::Resource::Catalog.new
@apply.apply
end
it "should deserialize the catalog from the default format" do
@apply.options[:catalog] = "/my/catalog.pson"
File.stubs(:read).with("/my/catalog.pson").returns "something"
Puppet::Resource::Catalog.stubs(:default_format).returns :rot13_piglatin
Puppet::Resource::Catalog.stubs(:convert_from).with(:rot13_piglatin,'something').returns Puppet::Resource::Catalog.new
@apply.apply
end
it "should fail helpfully if deserializing fails" do
@apply.options[:catalog] = "/my/catalog.pson"
File.stubs(:read).with("/my/catalog.pson").returns "something syntacically invalid"
lambda { @apply.apply }.should raise_error(Puppet::Error)
end
it "should convert plain data structures into a catalog if deserialization does not do so" do
@apply.options[:catalog] = "/my/catalog.pson"
File.stubs(:read).with("/my/catalog.pson").returns "something"
Puppet::Resource::Catalog.stubs(:convert_from).with(:pson,"something").returns({:foo => "bar"})
Puppet::Resource::Catalog.expects(:pson_create).with({:foo => "bar"}).returns(Puppet::Resource::Catalog.new)
@apply.apply
end
it "should convert the catalog to a RAL catalog and use a Configurer instance to apply it" do
@apply.options[:catalog] = "/my/catalog.pson"
File.stubs(:read).with("/my/catalog.pson").returns "something"
catalog = Puppet::Resource::Catalog.new
Puppet::Resource::Catalog.stubs(:convert_from).with(:pson,'something').returns catalog
catalog.expects(:to_ral).returns "mycatalog"
configurer = stub 'configurer'
Puppet::Configurer.expects(:new).returns configurer
configurer.expects(:run).with(:catalog => "mycatalog")
@apply.apply
end
end
end
end
diff --git a/spec/unit/application/inspect_spec.rb b/spec/unit/application/inspect_spec.rb
index a3cc74d86..b931708c3 100644
--- a/spec/unit/application/inspect_spec.rb
+++ b/spec/unit/application/inspect_spec.rb
@@ -1,79 +1,102 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/application/inspect'
require 'puppet/resource/catalog'
require 'puppet/indirector/catalog/yaml'
require 'puppet/indirector/report/rest'
describe Puppet::Application::Inspect do
before :each do
@inspect = Puppet::Application[:inspect]
end
describe "during setup" do
it "should print its configuration if asked" do
Puppet[:configprint] = "all"
Puppet.settings.expects(:print_configs).returns(true)
lambda { @inspect.setup }.should raise_error(SystemExit)
end
it "should fail if reporting is turned off" do
Puppet[:report] = false
lambda { @inspect.setup }.should raise_error(/report=true/)
end
end
describe "when executing" do
before :each do
Puppet[:report] = true
Puppet::Util::Log.stubs(:newdestination)
Puppet::Transaction::Report::Rest.any_instance.stubs(:save)
@inspect.setup
end
it "should retrieve the local catalog" do
Puppet::Resource::Catalog::Yaml.any_instance.expects(:find).with {|request| request.key == Puppet[:certname] }.returns(Puppet::Resource::Catalog.new)
@inspect.run_command
end
it "should save the report to REST" do
Puppet::Resource::Catalog::Yaml.any_instance.stubs(:find).returns(Puppet::Resource::Catalog.new)
Puppet::Transaction::Report::Rest.any_instance.expects(:save).with {|request| request.instance.host == Puppet[:certname] }
@inspect.run_command
end
it "should audit the specified properties" do
catalog = Puppet::Resource::Catalog.new
file = Tempfile.new("foo")
file.puts("file contents")
- file.flush
+ file.close
resource = Puppet::Resource.new(:file, file.path, :parameters => {:audit => "all"})
catalog.add_resource(resource)
Puppet::Resource::Catalog::Yaml.any_instance.stubs(:find).returns(catalog)
events = nil
Puppet::Transaction::Report::Rest.any_instance.expects(:save).with do |request|
events = request.instance.resource_statuses.values.first.events
end
@inspect.run_command
properties = events.inject({}) do |property_values, event|
property_values.merge(event.property => event.previous_value)
end
properties["ensure"].should == :file
properties["content"].should == "{md5}#{Digest::MD5.hexdigest("file contents\n")}"
+ properties.has_key?("target").should == false
+ end
+
+ it "should not report irrelevent attributes if the resource is absent" do
+ catalog = Puppet::Resource::Catalog.new
+ file = Tempfile.new("foo")
+ resource = Puppet::Resource.new(:file, file.path, :parameters => {:audit => "all"})
+ file.delete
+ catalog.add_resource(resource)
+ Puppet::Resource::Catalog::Yaml.any_instance.stubs(:find).returns(catalog)
+
+ events = nil
+
+ Puppet::Transaction::Report::Rest.any_instance.expects(:save).with do |request|
+ events = request.instance.resource_statuses.values.first.events
+ end
+
+ @inspect.run_command
+
+ properties = events.inject({}) do |property_values, event|
+ property_values.merge(event.property => event.previous_value)
+ end
+ properties.should == {"ensure" => :absent}
end
end
after :all do
Puppet::Resource::Catalog.indirection.reset_terminus_class
Puppet::Transaction::Report.indirection.terminus_class = :processor
end
end
diff --git a/spec/unit/application/kick_spec.rb b/spec/unit/application/kick_spec.rb
index dea7ec147..c18a84de3 100755
--- a/spec/unit/application/kick_spec.rb
+++ b/spec/unit/application/kick_spec.rb
@@ -1,313 +1,311 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/application/kick'
-describe Puppet::Application::Kick do
-
- confine "Kick's eventloops can only start on POSIX" => Puppet.features.posix?
+describe Puppet::Application::Kick, :if => Puppet.features.posix? do
before :each do
require 'puppet/util/ldap/connection'
Puppet::Util::Ldap::Connection.stubs(:new).returns(stub_everything)
@kick = Puppet::Application[:kick]
Puppet::Util::Log.stubs(:newdestination)
Puppet::Util::Log.stubs(:level=)
end
describe ".new" do
it "should take a command-line object as an argument" do
command_line = stub_everything "command_line"
lambda{ Puppet::Application::Kick.new( command_line ) }.should_not raise_error
end
end
it "should ask Puppet::Application to not parse Puppet configuration file" do
@kick.should_parse_config?.should be_false
end
it "should declare a main command" do
@kick.should respond_to(:main)
end
it "should declare a test command" do
@kick.should respond_to(:test)
end
it "should declare a preinit block" do
@kick.should respond_to(:preinit)
end
describe "during preinit" do
before :each do
@kick.stubs(:trap)
end
it "should catch INT and TERM" do
@kick.stubs(:trap).with { |arg,block| arg == :INT or arg == :TERM }
@kick.preinit
end
it "should set parallel option to 1" do
@kick.preinit
@kick.options[:parallel].should == 1
end
it "should set verbose by default" do
@kick.preinit
@kick.options[:verbose].should be_true
end
it "should set fqdn by default" do
@kick.preinit
@kick.options[:fqdn].should be_true
end
it "should set ignoreschedules to 'false'" do
@kick.preinit
@kick.options[:ignoreschedules].should be_false
end
it "should set foreground to 'false'" do
@kick.preinit
@kick.options[:foreground].should be_false
end
end
describe "when applying options" do
before do
@kick.preinit
end
[:all, :foreground, :debug, :ping, :test].each do |option|
it "should declare handle_#{option} method" do
@kick.should respond_to("handle_#{option}".to_sym)
end
it "should store argument value when calling handle_#{option}" do
@kick.options.expects(:[]=).with(option, 'arg')
@kick.send("handle_#{option}".to_sym, 'arg')
end
end
it "should add to the host list with the host option" do
@kick.handle_host('host')
@kick.hosts.should == ['host']
end
it "should add to the tag list with the tag option" do
@kick.handle_tag('tag')
@kick.tags.should == ['tag']
end
it "should add to the class list with the class option" do
@kick.handle_class('class')
@kick.classes.should == ['class']
end
end
describe "during setup" do
before :each do
@kick.classes = []
@kick.tags = []
@kick.hosts = []
Puppet::Log.stubs(:level=)
@kick.stubs(:trap)
@kick.stubs(:puts)
Puppet.stubs(:parse_config)
@kick.options.stubs(:[]).with(any_parameters)
end
it "should set log level to debug if --debug was passed" do
@kick.options.stubs(:[]).with(:debug).returns(true)
Puppet::Log.expects(:level=).with(:debug)
@kick.setup
end
it "should set log level to info if --verbose was passed" do
@kick.options.stubs(:[]).with(:verbose).returns(true)
Puppet::Log.expects(:level=).with(:info)
@kick.setup
end
it "should Parse puppet config" do
Puppet.expects(:parse_config)
@kick.setup
end
describe "when using the ldap node terminus" do
before :each do
Puppet.stubs(:[]).with(:node_terminus).returns("ldap")
end
it "should pass the fqdn option to search" do
@kick.options.stubs(:[]).with(:fqdn).returns(:something)
@kick.options.stubs(:[]).with(:all).returns(true)
@kick.stubs(:puts)
Puppet::Node.expects(:search).with("whatever",:fqdn => :something).returns([])
@kick.setup
end
it "should search for all nodes if --all" do
@kick.options.stubs(:[]).with(:all).returns(true)
@kick.stubs(:puts)
Puppet::Node.expects(:search).with("whatever",:fqdn => nil).returns([])
@kick.setup
end
it "should search for nodes including given classes" do
@kick.options.stubs(:[]).with(:all).returns(false)
@kick.stubs(:puts)
@kick.classes = ['class']
Puppet::Node.expects(:search).with("whatever", :class => "class", :fqdn => nil).returns([])
@kick.setup
end
end
describe "when using regular nodes" do
it "should fail if some classes have been specified" do
$stderr.stubs(:puts)
@kick.classes = ['class']
@kick.expects(:exit).with(24)
@kick.setup
end
end
end
describe "when running" do
before :each do
@kick.stubs(:puts)
end
it "should dispatch to test if --test is used" do
@kick.options.stubs(:[]).with(:test).returns(true)
@kick.expects(:test)
@kick.run_command
end
it "should dispatch to main if --test is not used" do
@kick.options.stubs(:[]).with(:test).returns(false)
@kick.expects(:main)
@kick.run_command
end
describe "the test command" do
it "should exit with exit code 0 " do
@kick.expects(:exit).with(0)
@kick.test
end
end
describe "the main command" do
before :each do
@kick.options.stubs(:[]).with(:parallel).returns(1)
@kick.options.stubs(:[]).with(:ping).returns(false)
@kick.options.stubs(:[]).with(:ignoreschedules).returns(false)
@kick.options.stubs(:[]).with(:foreground).returns(false)
@kick.options.stubs(:[]).with(:debug).returns(false)
@kick.stubs(:print)
@kick.stubs(:exit)
@kick.preinit
@kick.stubs(:parse_options)
@kick.setup
$stderr.stubs(:puts)
end
it "should create as much childs as --parallel" do
@kick.options.stubs(:[]).with(:parallel).returns(3)
@kick.hosts = ['host1', 'host2', 'host3']
@kick.stubs(:exit).raises(SystemExit)
Process.stubs(:wait).returns(1).then.returns(2).then.returns(3).then.raises(Errno::ECHILD)
@kick.expects(:fork).times(3).returns(1).then.returns(2).then.returns(3)
lambda { @kick.main }.should raise_error
end
it "should delegate to run_for_host per host" do
@kick.hosts = ['host1', 'host2']
@kick.stubs(:exit).raises(SystemExit)
@kick.stubs(:fork).returns(1).yields
Process.stubs(:wait).returns(1).then.raises(Errno::ECHILD)
@kick.expects(:run_for_host).times(2)
lambda { @kick.main }.should raise_error
end
describe "during call of run_for_host" do
before do
require 'puppet/run'
options = {
:background => true, :ignoreschedules => false, :tags => []
}
@kick.preinit
@agent_run = Puppet::Run.new( options.dup )
@agent_run.stubs(:status).returns("success")
Puppet::Run.indirection.expects(:terminus_class=).with( :rest )
Puppet::Run.expects(:new).with( options ).returns(@agent_run)
end
it "should call run on a Puppet::Run for the given host" do
@agent_run.expects(:save).with('https://host:8139/production/run/host').returns(@agent_run)
@kick.run_for_host('host')
end
it "should exit the child with 0 on success" do
@agent_run.stubs(:status).returns("success")
@kick.expects(:exit).with(0)
@kick.run_for_host('host')
end
it "should exit the child with 3 on running" do
@agent_run.stubs(:status).returns("running")
@kick.expects(:exit).with(3)
@kick.run_for_host('host')
end
it "should exit the child with 12 on unknown answer" do
@agent_run.stubs(:status).returns("whatever")
@kick.expects(:exit).with(12)
@kick.run_for_host('host')
end
end
end
end
end
diff --git a/spec/unit/application/master_spec.rb b/spec/unit/application/master_spec.rb
index 216c7dc90..074249a4d 100644
--- a/spec/unit/application/master_spec.rb
+++ b/spec/unit/application/master_spec.rb
@@ -1,453 +1,451 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/application/master'
require 'puppet/daemon'
require 'puppet/network/server'
describe Puppet::Application::Master do
before :each do
@master = Puppet::Application[:master]
@daemon = stub_everything 'daemon'
Puppet::Daemon.stubs(:new).returns(@daemon)
Puppet::Util::Log.stubs(:newdestination)
Puppet::Util::Log.stubs(:level=)
Puppet::Node.stubs(:terminus_class=)
Puppet::Node.stubs(:cache_class=)
Puppet::Node::Facts.stubs(:terminus_class=)
Puppet::Node::Facts.stubs(:cache_class=)
Puppet::Transaction::Report.stubs(:terminus_class=)
Puppet::Resource::Catalog.stubs(:terminus_class=)
end
it "should operate in master run_mode" do
@master.class.run_mode.name.should equal(:master)
end
it "should ask Puppet::Application to parse Puppet configuration file" do
@master.should_parse_config?.should be_true
end
it "should declare a main command" do
@master.should respond_to(:main)
end
it "should declare a parseonly command" do
@master.should respond_to(:parseonly)
end
it "should declare a compile command" do
@master.should respond_to(:compile)
end
it "should declare a preinit block" do
@master.should respond_to(:preinit)
end
describe "during preinit" do
before :each do
@master.stubs(:trap)
end
it "should catch INT" do
@master.stubs(:trap).with { |arg,block| arg == :INT }
@master.preinit
end
it "should create a Puppet Daemon" do
Puppet::Daemon.expects(:new).returns(@daemon)
@master.preinit
end
it "should give ARGV to the Daemon" do
argv = stub 'argv'
ARGV.stubs(:dup).returns(argv)
@daemon.expects(:argv=).with(argv)
@master.preinit
end
end
[:debug,:verbose].each do |option|
it "should declare handle_#{option} method" do
@master.should respond_to("handle_#{option}".to_sym)
end
it "should store argument value when calling handle_#{option}" do
@master.options.expects(:[]=).with(option, 'arg')
@master.send("handle_#{option}".to_sym, 'arg')
end
end
describe "when applying options" do
before do
@master.command_line.stubs(:args).returns([])
end
it "should set the log destination with --logdest" do
Puppet::Log.expects(:newdestination).with("console")
@master.handle_logdest("console")
end
it "should put the setdest options to true" do
@master.options.expects(:[]=).with(:setdest,true)
@master.handle_logdest("console")
end
it "should parse the log destination from ARGV" do
@master.command_line.stubs(:args).returns(%w{--logdest /my/file})
Puppet::Util::Log.expects(:newdestination).with("/my/file")
@master.parse_options
end
end
describe "during setup" do
before :each do
Puppet::Log.stubs(:newdestination)
Puppet.stubs(:settraps)
Puppet::Log.stubs(:level=)
Puppet::SSL::CertificateAuthority.stubs(:instance)
Puppet::SSL::CertificateAuthority.stubs(:ca?)
Puppet.settings.stubs(:use)
@master.options.stubs(:[]).with(any_parameters)
end
it "should set log level to debug if --debug was passed" do
@master.options.stubs(:[]).with(:debug).returns(true)
Puppet::Log.expects(:level=).with(:debug)
@master.setup
end
it "should set log level to info if --verbose was passed" do
@master.options.stubs(:[]).with(:verbose).returns(true)
Puppet::Log.expects(:level=).with(:info)
@master.setup
end
it "should set console as the log destination if no --logdest and --daemonize" do
@master.stubs(:[]).with(:daemonize).returns(:false)
Puppet::Log.expects(:newdestination).with(:syslog)
@master.setup
end
it "should set syslog as the log destination if no --logdest and not --daemonize" do
Puppet::Log.expects(:newdestination).with(:syslog)
@master.setup
end
it "should set syslog as the log destination if --rack" do
@master.options.stubs(:[]).with(:rack).returns(:true)
Puppet::Log.expects(:newdestination).with(:syslog)
@master.setup
end
it "should print puppet config if asked to in Puppet config" do
@master.stubs(:exit)
Puppet.settings.stubs(:print_configs?).returns(true)
Puppet.settings.expects(:print_configs)
@master.setup
end
it "should exit after printing puppet config if asked to in Puppet config" do
Puppet.settings.stubs(:print_configs?).returns(true)
lambda { @master.setup }.should raise_error(SystemExit)
end
it "should tell Puppet.settings to use :main,:ssl and :master category" do
Puppet.settings.expects(:use).with(:main,:master,:ssl)
@master.setup
end
it "should cache class in yaml" do
Puppet::Node.expects(:cache_class=).with(:yaml)
@master.setup
end
describe "with no ca" do
it "should set the ca_location to none" do
Puppet::SSL::Host.expects(:ca_location=).with(:none)
@master.setup
end
end
describe "with a ca configured" do
before :each do
Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true)
end
it "should set the ca_location to local" do
Puppet::SSL::Host.expects(:ca_location=).with(:local)
@master.setup
end
it "should tell Puppet.settings to use :ca category" do
Puppet.settings.expects(:use).with(:ca)
@master.setup
end
it "should instantiate the CertificateAuthority singleton" do
Puppet::SSL::CertificateAuthority.expects(:instance)
@master.setup
end
end
end
describe "when running" do
before do
@master.preinit
end
it "should dispatch to parseonly if parseonly is set" do
Puppet.stubs(:[]).with(:parseonly).returns(true)
@master.options[:node] = nil
@master.expects(:parseonly)
@master.run_command
end
it "should dispatch to compile if called with --compile" do
@master.options[:node] = "foo"
@master.expects(:compile)
@master.run_command
end
it "should dispatch to main if parseonly is not set" do
Puppet.stubs(:[]).with(:parseonly).returns(false)
@master.options[:node] = nil
@master.expects(:main)
@master.run_command
end
describe "the parseonly command" do
before :each do
Puppet.stubs(:[]).with(:environment)
Puppet.stubs(:[]).with(:manifest).returns("site.pp")
Puppet.stubs(:err)
@master.stubs(:exit)
@collection = stub_everything
Puppet::Resource::TypeCollection.stubs(:new).returns(@collection)
end
it "should use a Puppet Resource Type Collection to parse the file" do
@collection.expects(:perform_initial_import)
@master.parseonly
end
it "should exit with exit code 0 if no error" do
@master.expects(:exit).with(0)
@master.parseonly
end
it "should exit with exit code 1 if error" do
@collection.stubs(:perform_initial_import).raises(Puppet::ParseError)
@master.expects(:exit).with(1)
@master.parseonly
end
end
describe "the compile command" do
before do
Puppet.stubs(:[]).with(:environment)
Puppet.stubs(:[]).with(:manifest).returns("site.pp")
Puppet.stubs(:err)
@master.stubs(:jj)
@master.stubs(:exit)
Puppet.features.stubs(:pson?).returns true
end
it "should fail if pson isn't available" do
Puppet.features.expects(:pson?).returns false
lambda { @master.compile }.should raise_error
end
it "should compile a catalog for the specified node" do
@master.options[:node] = "foo"
Puppet::Resource::Catalog.expects(:find).with("foo").returns Puppet::Resource::Catalog.new
$stdout.stubs(:puts)
@master.compile
end
it "should convert the catalog to a pure-resource catalog and use 'jj' to pretty-print the catalog" do
catalog = Puppet::Resource::Catalog.new
Puppet::Resource::Catalog.expects(:find).returns catalog
catalog.expects(:to_resource).returns("rescat")
@master.options[:node] = "foo"
@master.expects(:jj).with("rescat")
@master.compile
end
it "should exit with error code 30 if no catalog can be found" do
@master.options[:node] = "foo"
Puppet::Resource::Catalog.expects(:find).returns nil
@master.expects(:exit).with(30)
$stderr.expects(:puts)
@master.compile
end
it "should exit with error code 30 if there's a failure" do
@master.options[:node] = "foo"
Puppet::Resource::Catalog.expects(:find).raises ArgumentError
@master.expects(:exit).with(30)
$stderr.expects(:puts)
@master.compile
end
end
describe "the main command" do
before :each do
@master.preinit
@server = stub_everything 'server'
Puppet::Network::Server.stubs(:new).returns(@server)
@app = stub_everything 'app'
Puppet::SSL::Host.stubs(:localhost)
Puppet::SSL::CertificateAuthority.stubs(:ca?)
Process.stubs(:uid).returns(1000)
Puppet.stubs(:service)
Puppet.stubs(:[])
Puppet.stubs(:notice)
Puppet.stubs(:start)
end
it "should create a Server" do
Puppet::Network::Server.expects(:new)
@master.main
end
it "should give the server to the daemon" do
@daemon.expects(:server=).with(@server)
@master.main
end
it "should create the server with the right XMLRPC handlers" do
Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers] == [:Status, :FileServer, :Master, :Report, :Filebucket]}
@master.main
end
it "should create the server with a :ca xmlrpc handler if needed" do
Puppet.stubs(:[]).with(:ca).returns(true)
Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers].include?(:CA) }
@master.main
end
it "should generate a SSL cert for localhost" do
Puppet::SSL::Host.expects(:localhost)
@master.main
end
it "should make sure to *only* hit the CA for data" do
Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true)
Puppet::SSL::Host.expects(:ca_location=).with(:only)
@master.main
end
it "should drop privileges if running as root" do
Puppet.features.stubs(:root?).returns true
Puppet::Util.expects(:chuser)
@master.main
end
it "should daemonize if needed" do
Puppet.stubs(:[]).with(:daemonize).returns(true)
@daemon.expects(:daemonize)
@master.main
end
it "should start the service" do
@daemon.expects(:start)
@master.main
end
- describe "with --rack" do
- confine "Rack is not available" => Puppet.features.rack?
-
+ describe "with --rack", :if => Puppet.features.rack? do
before do
require 'puppet/network/http/rack'
Puppet::Network::HTTP::Rack.stubs(:new).returns(@app)
end
it "it should create the app with REST and XMLRPC support" do
@master.options.stubs(:[]).with(:rack).returns(:true)
Puppet::Network::HTTP::Rack.expects(:new).with { |args|
args[:xmlrpc_handlers] == [:Status, :FileServer, :Master, :Report, :Filebucket] and
args[:protocols] == [:rest, :xmlrpc]
}
@master.main
end
it "it should not start a daemon" do
@master.options.stubs(:[]).with(:rack).returns(:true)
@daemon.expects(:start).never
@master.main
end
it "it should return the app" do
@master.options.stubs(:[]).with(:rack).returns(:true)
app = @master.main
app.should equal(@app)
end
end
end
end
end
diff --git a/spec/unit/application_spec.rb b/spec/unit/application_spec.rb
index be7cda340..f68a7e209 100755
--- a/spec/unit/application_spec.rb
+++ b/spec/unit/application_spec.rb
@@ -1,512 +1,509 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet/application'
require 'puppet'
require 'getoptlong'
describe Puppet::Application do
before do
@app = Class.new(Puppet::Application).new
@appclass = @app.class
# avoid actually trying to parse any settings
Puppet.settings.stubs(:parse)
end
describe ".run_mode" do
it "should default to user" do
@appclass.run_mode.name.should == :user
end
it "should set and get a value" do
@appclass.run_mode :agent
@appclass.run_mode.name.should == :agent
end
end
it "should have a run entry-point" do
@app.should respond_to(:run)
end
it "should have a read accessor to options" do
@app.should respond_to(:options)
end
it "should include a default setup method" do
@app.should respond_to(:setup)
end
it "should include a default preinit method" do
@app.should respond_to(:preinit)
end
it "should include a default run_command method" do
@app.should respond_to(:run_command)
end
it "should invoke main as the default" do
@app.expects( :main )
@app.run_command
end
describe 'when invoking clear!' do
before :each do
Puppet::Application.run_status = :stop_requested
Puppet::Application.clear!
end
it 'should have nil run_status' do
Puppet::Application.run_status.should be_nil
end
it 'should return false for restart_requested?' do
Puppet::Application.restart_requested?.should be_false
end
it 'should return false for stop_requested?' do
Puppet::Application.stop_requested?.should be_false
end
it 'should return false for interrupted?' do
Puppet::Application.interrupted?.should be_false
end
it 'should return true for clear?' do
Puppet::Application.clear?.should be_true
end
end
describe 'after invoking stop!' do
before :each do
Puppet::Application.run_status = nil
Puppet::Application.stop!
end
after :each do
Puppet::Application.run_status = nil
end
it 'should have run_status of :stop_requested' do
Puppet::Application.run_status.should == :stop_requested
end
it 'should return true for stop_requested?' do
Puppet::Application.stop_requested?.should be_true
end
it 'should return false for restart_requested?' do
Puppet::Application.restart_requested?.should be_false
end
it 'should return true for interrupted?' do
Puppet::Application.interrupted?.should be_true
end
it 'should return false for clear?' do
Puppet::Application.clear?.should be_false
end
end
describe 'when invoking restart!' do
before :each do
Puppet::Application.run_status = nil
Puppet::Application.restart!
end
after :each do
Puppet::Application.run_status = nil
end
it 'should have run_status of :restart_requested' do
Puppet::Application.run_status.should == :restart_requested
end
it 'should return true for restart_requested?' do
Puppet::Application.restart_requested?.should be_true
end
it 'should return false for stop_requested?' do
Puppet::Application.stop_requested?.should be_false
end
it 'should return true for interrupted?' do
Puppet::Application.interrupted?.should be_true
end
it 'should return false for clear?' do
Puppet::Application.clear?.should be_false
end
end
describe 'when performing a controlled_run' do
it 'should not execute block if not :clear?' do
Puppet::Application.run_status = :stop_requested
target = mock 'target'
target.expects(:some_method).never
Puppet::Application.controlled_run do
target.some_method
end
end
it 'should execute block if :clear?' do
Puppet::Application.run_status = nil
target = mock 'target'
target.expects(:some_method).once
Puppet::Application.controlled_run do
target.some_method
end
end
- describe 'on POSIX systems' do
- confine "HUP works only on POSIX systems" => Puppet.features.posix?
-
+ describe 'on POSIX systems', :if => Puppet.features.posix? do
it 'should signal process with HUP after block if restart requested during block execution' do
Puppet::Application.run_status = nil
target = mock 'target'
target.expects(:some_method).once
old_handler = trap('HUP') { target.some_method }
begin
Puppet::Application.controlled_run do
Puppet::Application.run_status = :restart_requested
end
ensure
trap('HUP', old_handler)
end
end
end
after :each do
Puppet::Application.run_status = nil
end
end
describe "when parsing command-line options" do
before :each do
@app.command_line.stubs(:args).returns([])
Puppet.settings.stubs(:optparse_addargs).returns([])
end
it "should pass the banner to the option parser" do
option_parser = stub "option parser"
option_parser.stubs(:on)
option_parser.stubs(:parse!)
@app.class.instance_eval do
banner "banner"
end
OptionParser.expects(:new).with("banner").returns(option_parser)
@app.parse_options
end
it "should get options from Puppet.settings.optparse_addargs" do
Puppet.settings.expects(:optparse_addargs).returns([])
@app.parse_options
end
it "should add Puppet.settings options to OptionParser" do
Puppet.settings.stubs(:optparse_addargs).returns( [["--option","-o", "Funny Option"]])
Puppet.settings.expects(:handlearg).with("--option", 'true')
@app.command_line.stubs(:args).returns(["--option"])
@app.parse_options
end
it "should ask OptionParser to parse the command-line argument" do
@app.command_line.stubs(:args).returns(%w{ fake args })
OptionParser.any_instance.expects(:parse!).with(%w{ fake args })
@app.parse_options
end
- describe "when using --help" do
- confine "rdoc" => Puppet.features.usage?
+ describe "when using --help", :if => Puppet.features.usage? do
it "should call RDoc::usage and exit" do
@app.expects(:exit)
RDoc.expects(:usage).returns(true)
@app.handle_help(nil)
end
end
describe "when using --version" do
it "should declare a version option" do
@app.should respond_to(:handle_version)
end
it "should exit after printing the version" do
@app.stubs(:puts)
lambda { @app.handle_version(nil) }.should raise_error(SystemExit)
end
end
describe "when dealing with an argument not declared directly by the application" do
it "should pass it to handle_unknown if this method exists" do
Puppet.settings.stubs(:optparse_addargs).returns([["--not-handled", :REQUIRED]])
@app.expects(:handle_unknown).with("--not-handled", "value").returns(true)
@app.command_line.stubs(:args).returns(["--not-handled", "value"])
@app.parse_options
end
it "should pass it to Puppet.settings if handle_unknown says so" do
Puppet.settings.stubs(:optparse_addargs).returns([["--topuppet", :REQUIRED]])
@app.stubs(:handle_unknown).with("--topuppet", "value").returns(false)
Puppet.settings.expects(:handlearg).with("--topuppet", "value")
@app.command_line.stubs(:args).returns(["--topuppet", "value"])
@app.parse_options
end
it "should pass it to Puppet.settings if there is no handle_unknown method" do
Puppet.settings.stubs(:optparse_addargs).returns([["--topuppet", :REQUIRED]])
@app.stubs(:respond_to?).returns(false)
Puppet.settings.expects(:handlearg).with("--topuppet", "value")
@app.command_line.stubs(:args).returns(["--topuppet", "value"])
@app.parse_options
end
it "should transform boolean false value to string for Puppet.settings" do
Puppet.settings.expects(:handlearg).with("--option", "false")
@app.handlearg("--option", false)
end
it "should transform boolean true value to string for Puppet.settings" do
Puppet.settings.expects(:handlearg).with("--option", "true")
@app.handlearg("--option", true)
end
it "should transform boolean option to normal form for Puppet.settings" do
Puppet.settings.expects(:handlearg).with("--option", "true")
@app.handlearg("--[no-]option", true)
end
it "should transform boolean option to no- form for Puppet.settings" do
Puppet.settings.expects(:handlearg).with("--no-option", "false")
@app.handlearg("--[no-]option", false)
end
end
it "should exit if OptionParser raises an error" do
$stderr.stubs(:puts)
OptionParser.any_instance.stubs(:parse!).raises(OptionParser::ParseError.new("blah blah"))
@app.expects(:exit)
lambda { @app.parse_options }.should_not raise_error
end
end
describe "when calling default setup" do
before :each do
@app.stubs(:should_parse_config?).returns(false)
@app.options.stubs(:[])
end
[ :debug, :verbose ].each do |level|
it "should honor option #{level}" do
@app.options.stubs(:[]).with(level).returns(true)
Puppet::Util::Log.stubs(:newdestination)
Puppet::Util::Log.expects(:level=).with(level == :verbose ? :info : :debug)
@app.setup
end
end
it "should honor setdest option" do
@app.options.stubs(:[]).with(:setdest).returns(false)
Puppet::Util::Log.expects(:newdestination).with(:syslog)
@app.setup
end
end
describe "when running" do
before :each do
@app.stubs(:preinit)
@app.stubs(:setup)
@app.stubs(:parse_options)
end
it "should call preinit" do
@app.stubs(:run_command)
@app.expects(:preinit)
@app.run
end
it "should call parse_options" do
@app.stubs(:run_command)
@app.expects(:parse_options)
@app.run
end
it "should call run_command" do
@app.expects(:run_command)
@app.run
end
it "should parse Puppet configuration if should_parse_config is called" do
@app.stubs(:run_command)
@app.class.should_parse_config
Puppet.settings.expects(:parse)
@app.run
end
it "should not parse_option if should_not_parse_config is called" do
@app.stubs(:run_command)
@app.class.should_not_parse_config
Puppet.settings.expects(:parse).never
@app.run
end
it "should parse Puppet configuration if needed" do
@app.stubs(:run_command)
@app.stubs(:should_parse_config?).returns(true)
Puppet.settings.expects(:parse)
@app.run
end
it "should call run_command" do
@app.expects(:run_command)
@app.run
end
it "should call main as the default command" do
@app.expects(:main)
@app.run
end
it "should warn and exit if no command can be called" do
$stderr.expects(:puts)
@app.expects(:exit).with(1)
@app.run
end
it "should raise an error if dispatch returns no command" do
@app.stubs(:get_command).returns(nil)
$stderr.expects(:puts)
@app.expects(:exit).with(1)
@app.run
end
it "should raise an error if dispatch returns an invalid command" do
@app.stubs(:get_command).returns(:this_function_doesnt_exist)
$stderr.expects(:puts)
@app.expects(:exit).with(1)
@app.run
end
end
describe "when metaprogramming" do
describe "when calling option" do
it "should create a new method named after the option" do
@app.class.option("--test1","-t") do
end
@app.should respond_to(:handle_test1)
end
it "should transpose in option name any '-' into '_'" do
@app.class.option("--test-dashes-again","-t") do
end
@app.should respond_to(:handle_test_dashes_again)
end
it "should create a new method called handle_test2 with option(\"--[no-]test2\")" do
@app.class.option("--[no-]test2","-t") do
end
@app.should respond_to(:handle_test2)
end
describe "when a block is passed" do
it "should create a new method with it" do
@app.class.option("--[no-]test2","-t") do
raise "I can't believe it, it works!"
end
lambda { @app.handle_test2 }.should raise_error
end
it "should declare the option to OptionParser" do
OptionParser.any_instance.stubs(:on)
OptionParser.any_instance.expects(:on).with { |*arg| arg[0] == "--[no-]test3" }
@app.class.option("--[no-]test3","-t") do
end
@app.parse_options
end
it "should pass a block that calls our defined method" do
OptionParser.any_instance.stubs(:on)
OptionParser.any_instance.stubs(:on).with('--test4','-t').yields(nil)
@app.expects(:send).with(:handle_test4, nil)
@app.class.option("--test4","-t") do
end
@app.parse_options
end
end
describe "when no block is given" do
it "should declare the option to OptionParser" do
OptionParser.any_instance.stubs(:on)
OptionParser.any_instance.expects(:on).with("--test4","-t")
@app.class.option("--test4","-t")
@app.parse_options
end
it "should give to OptionParser a block that adds the the value to the options array" do
OptionParser.any_instance.stubs(:on)
OptionParser.any_instance.stubs(:on).with("--test4","-t").yields(nil)
@app.options.expects(:[]=).with(:test4,nil)
@app.class.option("--test4","-t")
@app.parse_options
end
end
end
end
end
diff --git a/spec/unit/configurer_spec.rb b/spec/unit/configurer_spec.rb
index ebc5768ea..cf73a3706 100755
--- a/spec/unit/configurer_spec.rb
+++ b/spec/unit/configurer_spec.rb
@@ -1,494 +1,478 @@
#!/usr/bin/env ruby
#
# Created by Luke Kanies on 2007-11-12.
# Copyright (c) 2007. All rights reserved.
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet/configurer'
describe Puppet::Configurer do
before do
Puppet.settings.stubs(:use).returns(true)
@agent = Puppet::Configurer.new
end
it "should include the Plugin Handler module" do
Puppet::Configurer.ancestors.should be_include(Puppet::Configurer::PluginHandler)
end
it "should include the Fact Handler module" do
Puppet::Configurer.ancestors.should be_include(Puppet::Configurer::FactHandler)
end
it "should use the puppetdlockfile as its lockfile path" do
Puppet.settings.expects(:value).with(:puppetdlockfile).returns("/my/lock")
Puppet::Configurer.lockfile_path.should == "/my/lock"
end
describe "when executing a pre-run hook" do
it "should do nothing if the hook is set to an empty string" do
Puppet.settings[:prerun_command] = ""
Puppet::Util.expects(:exec).never
@agent.execute_prerun_command
end
it "should execute any pre-run command provided via the 'prerun_command' setting" do
Puppet.settings[:prerun_command] = "/my/command"
Puppet::Util.expects(:execute).with { |args| args[0] == "/my/command" }
@agent.execute_prerun_command
end
it "should fail if the command fails" do
Puppet.settings[:prerun_command] = "/my/command"
Puppet::Util.expects(:execute).raises Puppet::ExecutionFailure
lambda { @agent.execute_prerun_command }.should raise_error(Puppet::Configurer::CommandHookError)
end
end
describe "when executing a post-run hook" do
it "should do nothing if the hook is set to an empty string" do
Puppet.settings[:postrun_command] = ""
Puppet::Util.expects(:exec).never
@agent.execute_postrun_command
end
it "should execute any post-run command provided via the 'postrun_command' setting" do
Puppet.settings[:postrun_command] = "/my/command"
Puppet::Util.expects(:execute).with { |args| args[0] == "/my/command" }
@agent.execute_postrun_command
end
it "should fail if the command fails" do
Puppet.settings[:postrun_command] = "/my/command"
Puppet::Util.expects(:execute).raises Puppet::ExecutionFailure
lambda { @agent.execute_postrun_command }.should raise_error(Puppet::Configurer::CommandHookError)
end
end
end
-describe Puppet::Configurer, "when initializing a report" do
- it "should return an instance of a transaction report" do
- Puppet.settings.stubs(:use).returns(true)
- @agent = Puppet::Configurer.new
- @agent.initialize_report.should be_instance_of(Puppet::Transaction::Report)
- end
-end
-
describe Puppet::Configurer, "when executing a catalog run" do
before do
Puppet.settings.stubs(:use).returns(true)
@agent = Puppet::Configurer.new
@agent.stubs(:prepare)
@agent.stubs(:facts_for_uploading).returns({})
@catalog = Puppet::Resource::Catalog.new
@catalog.stubs(:apply)
@agent.stubs(:retrieve_catalog).returns @catalog
Puppet::Util::Log.stubs(:newdestination)
Puppet::Util::Log.stubs(:close)
end
it "should prepare for the run" do
@agent.expects(:prepare)
@agent.run
end
it "should initialize a transaction report if one is not provided" do
- report = stub 'report'
- @agent.expects(:initialize_report).returns report
+ report = Puppet::Transaction::Report.new("apply")
+ Puppet::Transaction::Report.expects(:new).returns report
@agent.run
end
it "should pass the new report to the catalog" do
- report = stub 'report'
- @agent.stubs(:initialize_report).returns report
+ report = Puppet::Transaction::Report.new("apply")
+ Puppet::Transaction::Report.stubs(:new).returns report
@catalog.expects(:apply).with{|options| options[:report] == report}
@agent.run
end
it "should use the provided report if it was passed one" do
- report = stub 'report'
- @agent.expects(:initialize_report).never
+ report = Puppet::Transaction::Report.new("apply")
+ Puppet::Transaction::Report.expects(:new).never
@catalog.expects(:apply).with{|options| options[:report] == report}
@agent.run(:report => report)
end
it "should set the report as a log destination" do
- report = stub 'report'
- @agent.expects(:initialize_report).returns report
+ report = Puppet::Transaction::Report.new("apply")
+ Puppet::Transaction::Report.expects(:new).returns report
Puppet::Util::Log.expects(:newdestination).with(report)
@agent.run
end
it "should retrieve the catalog" do
@agent.expects(:retrieve_catalog)
@agent.run
end
it "should log a failure and do nothing if no catalog can be retrieved" do
@agent.expects(:retrieve_catalog).returns nil
Puppet.expects(:err).with "Could not retrieve catalog; skipping run"
@agent.run
end
it "should apply the catalog with all options to :run" do
@agent.expects(:retrieve_catalog).returns @catalog
@catalog.expects(:apply).with { |args| args[:one] == true }
@agent.run :one => true
end
it "should accept a catalog and use it instead of retrieving a different one" do
@agent.expects(:retrieve_catalog).never
@catalog.expects(:apply)
@agent.run :one => true, :catalog => @catalog
end
it "should benchmark how long it takes to apply the catalog" do
@agent.expects(:benchmark).with(:notice, "Finished catalog run")
@agent.expects(:retrieve_catalog).returns @catalog
@catalog.expects(:apply).never # because we're not yielding
@agent.run
end
it "should execute post-run hooks after the run" do
@agent.expects(:execute_postrun_command)
@agent.run
end
it "should send the report" do
- report = stub 'report'
- @agent.expects(:initialize_report).returns report
+ report = Puppet::Transaction::Report.new("apply")
+ Puppet::Transaction::Report.expects(:new).returns(report)
@agent.expects(:send_report).with { |r, trans| r == report }
@agent.run
end
it "should send the transaction report with a reference to the transaction if a run was actually made" do
- report = stub 'report'
- @agent.expects(:initialize_report).returns report
+ report = Puppet::Transaction::Report.new("apply")
+ Puppet::Transaction::Report.expects(:new).returns(report)
trans = stub 'transaction'
@catalog.expects(:apply).returns trans
@agent.expects(:send_report).with { |r, t| t == trans }
@agent.run :catalog => @catalog
end
it "should send the transaction report even if the catalog could not be retrieved" do
@agent.expects(:retrieve_catalog).returns nil
- report = stub 'report'
- @agent.expects(:initialize_report).returns report
+ report = Puppet::Transaction::Report.new("apply")
+ Puppet::Transaction::Report.expects(:new).returns(report)
@agent.expects(:send_report)
@agent.run
end
it "should send the transaction report even if there is a failure" do
@agent.expects(:retrieve_catalog).raises "whatever"
- report = stub 'report'
- @agent.expects(:initialize_report).returns report
+ report = Puppet::Transaction::Report.new("apply")
+ Puppet::Transaction::Report.expects(:new).returns(report)
@agent.expects(:send_report)
lambda { @agent.run }.should raise_error
end
it "should remove the report as a log destination when the run is finished" do
- report = stub 'report'
- @agent.expects(:initialize_report).returns report
+ report = Puppet::Transaction::Report.new("apply")
+ Puppet::Transaction::Report.expects(:new).returns(report)
Puppet::Util::Log.expects(:close).with(report)
@agent.run
end
it "should return the report as the result of the run" do
- report = stub 'report'
- @agent.expects(:initialize_report).returns report
+ report = Puppet::Transaction::Report.new("apply")
+ Puppet::Transaction::Report.expects(:new).returns(report)
@agent.run.should equal(report)
end
end
describe Puppet::Configurer, "when sending a report" do
before do
Puppet.settings.stubs(:use).returns(true)
@configurer = Puppet::Configurer.new
- @report = stub 'report'
+ @report = Puppet::Transaction::Report.new("apply")
@trans = stub 'transaction'
end
- it "should require a report" do
- lambda { @configurer.send_report }.should raise_error(ArgumentError)
- end
-
- it "should allow specification of a transaction" do
- lambda { @configurer.send_report(@report, @trans) }.should_not raise_error(ArgumentError)
- end
-
- it "should use any provided transaction to add metrics to the report" do
- @trans.expects(:generate_report)
+ it "should finalize the report" do
+ @report.expects(:finalize_report)
@configurer.send_report(@report, @trans)
end
it "should print a report summary if configured to do so" do
Puppet.settings[:summarize] = true
@report.expects(:summary).returns "stuff"
@configurer.expects(:puts).with("stuff")
- @configurer.send_report(@report)
+ @configurer.send_report(@report, nil)
end
it "should not print a report summary if not configured to do so" do
Puppet.settings[:summarize] = false
@configurer.expects(:puts).never
- @configurer.send_report(@report)
+ @configurer.send_report(@report, nil)
end
it "should save the report if reporting is enabled" do
Puppet.settings[:report] = true
@report.expects(:save)
- @configurer.send_report(@report)
+ @configurer.send_report(@report, nil)
end
it "should not save the report if reporting is disabled" do
Puppet.settings[:report] = false
@report.expects(:save).never
- @configurer.send_report(@report)
+ @configurer.send_report(@report, nil)
end
it "should log but not fail if saving the report fails" do
Puppet.settings[:report] = true
@report.expects(:save).raises "whatever"
Puppet.expects(:err)
- lambda { @configurer.send_report(@report) }.should_not raise_error
+ lambda { @configurer.send_report(@report, nil) }.should_not raise_error
end
end
describe Puppet::Configurer, "when retrieving a catalog" do
before do
Puppet.settings.stubs(:use).returns(true)
@agent = Puppet::Configurer.new
@agent.stubs(:facts_for_uploading).returns({})
@catalog = Puppet::Resource::Catalog.new
# this is the default when using a Configurer instance
Puppet::Resource::Catalog.indirection.stubs(:terminus_class).returns :rest
@agent.stubs(:convert_catalog).returns @catalog
end
describe "and configured to only retrieve a catalog from the cache" do
before do
Puppet.settings[:use_cached_catalog] = true
end
it "should first look in the cache for a catalog" do
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns @catalog
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.never
@agent.retrieve_catalog.should == @catalog
end
it "should compile a new catalog if none is found in the cache" do
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns nil
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns @catalog
@agent.retrieve_catalog.should == @catalog
end
end
describe "when not using a REST terminus for catalogs" do
it "should not pass any facts when retrieving the catalog" do
@agent.expects(:facts_for_uploading).never
Puppet::Resource::Catalog.expects(:find).with { |name, options|
options[:facts].nil?
}.returns @catalog
@agent.retrieve_catalog
end
end
describe "when using a REST terminus for catalogs" do
it "should pass the prepared facts and the facts format as arguments when retrieving the catalog" do
@agent.expects(:facts_for_uploading).returns(:facts => "myfacts", :facts_format => :foo)
Puppet::Resource::Catalog.expects(:find).with { |name, options|
options[:facts] == "myfacts" and options[:facts_format] == :foo
}.returns @catalog
@agent.retrieve_catalog
end
end
it "should use the Catalog class to get its catalog" do
Puppet::Resource::Catalog.expects(:find).returns @catalog
@agent.retrieve_catalog
end
it "should use its certname to retrieve the catalog" do
Facter.stubs(:value).returns "eh"
Puppet.settings[:certname] = "myhost.domain.com"
Puppet::Resource::Catalog.expects(:find).with { |name, options| name == "myhost.domain.com" }.returns @catalog
@agent.retrieve_catalog
end
it "should default to returning a catalog retrieved directly from the server, skipping the cache" do
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns @catalog
@agent.retrieve_catalog.should == @catalog
end
it "should log and return the cached catalog when no catalog can be retrieved from the server" do
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns @catalog
Puppet.expects(:notice)
@agent.retrieve_catalog.should == @catalog
end
it "should not look in the cache for a catalog if one is returned from the server" do
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns @catalog
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.never
@agent.retrieve_catalog.should == @catalog
end
it "should return the cached catalog when retrieving the remote catalog throws an exception" do
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.raises "eh"
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns @catalog
@agent.retrieve_catalog.should == @catalog
end
it "should log and return nil if no catalog can be retrieved from the server and :usecacheonfailure is disabled" do
Puppet.stubs(:[])
Puppet.expects(:[]).with(:usecacheonfailure).returns false
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil
Puppet.expects(:warning)
@agent.retrieve_catalog.should be_nil
end
it "should return nil if no cached catalog is available and no catalog can be retrieved from the server" do
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns nil
@agent.retrieve_catalog.should be_nil
end
it "should convert the catalog before returning" do
Puppet::Resource::Catalog.stubs(:find).returns @catalog
@agent.expects(:convert_catalog).with { |cat, dur| cat == @catalog }.returns "converted catalog"
@agent.retrieve_catalog.should == "converted catalog"
end
it "should return nil if there is an error while retrieving the catalog" do
Puppet::Resource::Catalog.expects(:find).at_least_once.raises "eh"
@agent.retrieve_catalog.should be_nil
end
end
describe Puppet::Configurer, "when converting the catalog" do
before do
Puppet.settings.stubs(:use).returns(true)
@agent = Puppet::Configurer.new
@catalog = Puppet::Resource::Catalog.new
@oldcatalog = stub 'old_catalog', :to_ral => @catalog
end
it "should convert the catalog to a RAL-formed catalog" do
@oldcatalog.expects(:to_ral).returns @catalog
@agent.convert_catalog(@oldcatalog, 10).should equal(@catalog)
end
it "should finalize the catalog" do
@catalog.expects(:finalize)
@agent.convert_catalog(@oldcatalog, 10)
end
it "should record the passed retrieval time with the RAL catalog" do
@catalog.expects(:retrieval_duration=).with 10
@agent.convert_catalog(@oldcatalog, 10)
end
it "should write the RAL catalog's class file" do
@catalog.expects(:write_class_file)
@agent.convert_catalog(@oldcatalog, 10)
end
end
describe Puppet::Configurer, "when preparing for a run" do
before do
Puppet.settings.stubs(:use).returns(true)
@agent = Puppet::Configurer.new
@agent.stubs(:dostorage)
@agent.stubs(:download_fact_plugins)
@agent.stubs(:download_plugins)
@agent.stubs(:execute_prerun_command)
@facts = {"one" => "two", "three" => "four"}
end
it "should initialize the metadata store" do
@agent.class.stubs(:facts).returns(@facts)
@agent.expects(:dostorage)
- @agent.prepare
+ @agent.prepare({})
end
it "should download fact plugins" do
@agent.expects(:download_fact_plugins)
- @agent.prepare
+ @agent.prepare({})
end
it "should download plugins" do
@agent.expects(:download_plugins)
- @agent.prepare
+ @agent.prepare({})
end
it "should perform the pre-run commands" do
@agent.expects(:execute_prerun_command)
- @agent.prepare
+ @agent.prepare({})
end
end
diff --git a/spec/unit/file_serving/metadata_spec.rb b/spec/unit/file_serving/metadata_spec.rb
index aa0dcd511..dd40324bc 100755
--- a/spec/unit/file_serving/metadata_spec.rb
+++ b/spec/unit/file_serving/metadata_spec.rb
@@ -1,286 +1,286 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/file_serving/metadata'
describe Puppet::FileServing::Metadata do
it "should should be a subclass of Base" do
Puppet::FileServing::Metadata.superclass.should equal(Puppet::FileServing::Base)
end
it "should indirect file_metadata" do
Puppet::FileServing::Metadata.indirection.name.should == :file_metadata
end
it "should should include the IndirectionHooks module in its indirection" do
Puppet::FileServing::Metadata.indirection.singleton_class.included_modules.should include(Puppet::FileServing::IndirectionHooks)
end
it "should have a method that triggers attribute collection" do
Puppet::FileServing::Metadata.new("/foo/bar").should respond_to(:collect)
end
it "should support pson serialization" do
Puppet::FileServing::Metadata.new("/foo/bar").should respond_to(:to_pson)
end
it "should support to_pson_data_hash" do
Puppet::FileServing::Metadata.new("/foo/bar").should respond_to(:to_pson_data_hash)
end
it "should support pson deserialization" do
Puppet::FileServing::Metadata.should respond_to(:from_pson)
end
describe "when serializing" do
before do
@metadata = Puppet::FileServing::Metadata.new("/foo/bar")
end
it "should perform pson serialization by calling to_pson on it's pson_data_hash" do
pdh = mock "data hash"
pdh_as_pson = mock "data as pson"
@metadata.expects(:to_pson_data_hash).returns pdh
pdh.expects(:to_pson).returns pdh_as_pson
@metadata.to_pson.should == pdh_as_pson
end
it "should serialize as FileMetadata" do
@metadata.to_pson_data_hash['document_type'].should == "FileMetadata"
end
it "the data should include the path, relative_path, links, owner, group, mode, checksum, type, and destination" do
@metadata.to_pson_data_hash['data'].keys.sort.should == %w{ path relative_path links owner group mode checksum type destination }.sort
end
it "should pass the path in the hash verbatum" do
@metadata.to_pson_data_hash['data']['path'] == @metadata.path
end
it "should pass the relative_path in the hash verbatum" do
@metadata.to_pson_data_hash['data']['relative_path'] == @metadata.relative_path
end
it "should pass the links in the hash verbatum" do
@metadata.to_pson_data_hash['data']['links'] == @metadata.links
end
it "should pass the path owner in the hash verbatum" do
@metadata.to_pson_data_hash['data']['owner'] == @metadata.owner
end
it "should pass the group in the hash verbatum" do
@metadata.to_pson_data_hash['data']['group'] == @metadata.group
end
it "should pass the mode in the hash verbatum" do
@metadata.to_pson_data_hash['data']['mode'] == @metadata.mode
end
it "should pass the ftype in the hash verbatum as the 'type'" do
@metadata.to_pson_data_hash['data']['type'] == @metadata.ftype
end
it "should pass the destination verbatum" do
@metadata.to_pson_data_hash['data']['destination'] == @metadata.destination
end
it "should pass the checksum in the hash as a nested hash" do
@metadata.to_pson_data_hash['data']['checksum'].should be_is_a(Hash)
end
it "should pass the checksum_type in the hash verbatum as the checksum's type" do
@metadata.to_pson_data_hash['data']['checksum']['type'] == @metadata.checksum_type
end
it "should pass the checksum in the hash verbatum as the checksum's value" do
@metadata.to_pson_data_hash['data']['checksum']['value'] == @metadata.checksum
end
end
end
describe Puppet::FileServing::Metadata, " when finding the file to use for setting attributes" do
before do
@path = "/my/path"
@metadata = Puppet::FileServing::Metadata.new(@path)
# Use a link because it's easier to test -- no checksumming
@stat = stub "stat", :uid => 10, :gid => 20, :mode => 0755, :ftype => "link"
# Not quite. We don't want to checksum links, but we must because they might be being followed.
@checksum = Digest::MD5.hexdigest("some content\n") # Remove these when :managed links are no longer checksumed.
@metadata.stubs(:md5_file).returns(@checksum) #
end
it "should accept a base path path to which the file should be relative" do
File.expects(:lstat).with(@path).returns @stat
File.expects(:readlink).with(@path).returns "/what/ever"
@metadata.collect
end
it "should use the set base path if one is not provided" do
File.expects(:lstat).with(@path).returns @stat
File.expects(:readlink).with(@path).returns "/what/ever"
@metadata.collect
end
it "should raise an exception if the file does not exist" do
File.expects(:lstat).with(@path).raises(Errno::ENOENT)
proc { @metadata.collect}.should raise_error(Errno::ENOENT)
end
end
describe Puppet::FileServing::Metadata, " when collecting attributes" do
before do
@path = "/my/file"
# Use a real file mode, so we can validate the masking is done.
@stat = stub 'stat', :uid => 10, :gid => 20, :mode => 33261, :ftype => "file"
File.stubs(:lstat).returns(@stat)
@checksum = Digest::MD5.hexdigest("some content\n")
@metadata = Puppet::FileServing::Metadata.new("/my/file")
@metadata.stubs(:md5_file).returns(@checksum)
@metadata.collect
end
it "should be able to produce xmlrpc-style attribute information" do
@metadata.should respond_to(:attributes_with_tabs)
end
# LAK:FIXME This should actually change at some point
it "should set the owner by id" do
@metadata.owner.should be_instance_of(Fixnum)
end
# LAK:FIXME This should actually change at some point
it "should set the group by id" do
@metadata.group.should be_instance_of(Fixnum)
end
it "should set the owner to the file's current owner" do
@metadata.owner.should == 10
end
it "should set the group to the file's current group" do
@metadata.group.should == 20
end
it "should set the mode to the file's masked mode" do
@metadata.mode.should == 0755
end
it "should set the checksum to the file's current checksum" do
@metadata.checksum.should == "{md5}#{@checksum}"
end
describe "when managing files" do
it "should default to a checksum of type MD5" do
@metadata.checksum.should == "{md5}#{@checksum}"
end
it "should give a mtime checksum when checksum_type is set" do
time = Time.now
@metadata.checksum_type = "mtime"
@metadata.expects(:mtime_file).returns(@time)
@metadata.collect
@metadata.checksum.should == "{mtime}#{@time}"
end
it "should produce tab-separated mode, type, owner, group, and checksum for xmlrpc" do
@metadata.attributes_with_tabs.should == "#{0755.to_s}\tfile\t10\t20\t{md5}#{@checksum}"
end
end
describe "when managing directories" do
before do
@stat.stubs(:ftype).returns("directory")
@time = Time.now
@metadata.expects(:ctime_file).returns(@time)
end
it "should only use checksums of type 'ctime' for directories" do
@metadata.collect
@metadata.checksum.should == "{ctime}#{@time}"
end
it "should only use checksums of type 'ctime' for directories even if checksum_type set" do
@metadata.checksum_type = "mtime"
@metadata.expects(:mtime_file).never
@metadata.collect
@metadata.checksum.should == "{ctime}#{@time}"
end
it "should produce tab-separated mode, type, owner, group, and checksum for xmlrpc" do
@metadata.collect
@metadata.attributes_with_tabs.should == "#{0755.to_s}\tdirectory\t10\t20\t{ctime}#{@time.to_s}"
end
end
describe "when managing links" do
before do
@stat.stubs(:ftype).returns("link")
File.expects(:readlink).with("/my/file").returns("/path/to/link")
@metadata.collect
@checksum = Digest::MD5.hexdigest("some content\n") # Remove these when :managed links are no longer checksumed.
@file.stubs(:md5_file).returns(@checksum) #
end
it "should read links instead of returning their checksums" do
@metadata.destination.should == "/path/to/link"
end
- it "should produce tab-separated mode, type, owner, group, and destination for xmlrpc" do
- pending "We'd like this to be true, but we need to always collect the checksum because in the server/client/server round trip we lose the distintion between manage and follow."
+ pending "should produce tab-separated mode, type, owner, group, and destination for xmlrpc" do
+ # "We'd like this to be true, but we need to always collect the checksum because in the server/client/server round trip we lose the distintion between manage and follow."
@metadata.attributes_with_tabs.should == "#{0755}\tlink\t10\t20\t/path/to/link"
end
it "should produce tab-separated mode, type, owner, group, checksum, and destination for xmlrpc" do
@metadata.attributes_with_tabs.should == "#{0755}\tlink\t10\t20\t{md5}eb9c2bf0eb63f3a7bc0ea37ef18aeba5\t/path/to/link"
end
end
end
describe Puppet::FileServing::Metadata, " when pointing to a link" do
describe "when links are managed" do
before do
@file = Puppet::FileServing::Metadata.new("/base/path/my/file", :links => :manage)
File.expects(:lstat).with("/base/path/my/file").returns stub("stat", :uid => 1, :gid => 2, :ftype => "link", :mode => 0755)
File.expects(:readlink).with("/base/path/my/file").returns "/some/other/path"
@checksum = Digest::MD5.hexdigest("some content\n") # Remove these when :managed links are no longer checksumed.
@file.stubs(:md5_file).returns(@checksum) #
end
it "should store the destination of the link in :destination if links are :manage" do
@file.collect
@file.destination.should == "/some/other/path"
end
- it "should not collect the checksum if links are :manage" do
- pending "We'd like this to be true, but we need to always collect the checksum because in the server/client/server round trip we lose the distintion between manage and follow."
+ pending "should not collect the checksum if links are :manage" do
+ # We'd like this to be true, but we need to always collect the checksum because in the server/client/server round trip we lose the distintion between manage and follow.
@file.collect
@file.checksum.should be_nil
end
it "should collect the checksum if links are :manage" do # see pending note above
@file.collect
@file.checksum.should == "{md5}#{@checksum}"
end
end
describe "when links are followed" do
before do
@file = Puppet::FileServing::Metadata.new("/base/path/my/file", :links => :follow)
File.expects(:stat).with("/base/path/my/file").returns stub("stat", :uid => 1, :gid => 2, :ftype => "file", :mode => 0755)
File.expects(:readlink).with("/base/path/my/file").never
@checksum = Digest::MD5.hexdigest("some content\n")
@file.stubs(:md5_file).returns(@checksum)
end
it "should not store the destination of the link in :destination if links are :follow" do
@file.collect
@file.destination.should be_nil
end
it "should collect the checksum if links are :follow" do
@file.collect
@file.checksum.should == "{md5}#{@checksum}"
end
end
end
diff --git a/spec/unit/indirector/catalog/active_record_spec.rb b/spec/unit/indirector/catalog/active_record_spec.rb
index ba8f1dad9..a368fb3a6 100755
--- a/spec/unit/indirector/catalog/active_record_spec.rb
+++ b/spec/unit/indirector/catalog/active_record_spec.rb
@@ -1,159 +1,157 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../spec_helper'
-describe "Puppet::Resource::Catalog::ActiveRecord" do
- confine "Missing Rails" => Puppet.features.rails?
-
+describe "Puppet::Resource::Catalog::ActiveRecord", :if => Puppet.features.rails? do
require 'puppet/rails'
before :all do
class Tableless < ActiveRecord::Base
def self.columns
@columns ||= []
end
def self.column(name, sql_type=nil, default=nil, null=true)
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
end
end
class Host < Tableless
column :name, :string, :null => false
column :ip, :string
column :environment, :string
column :last_compile, :datetime
end
end
before do
require 'puppet/indirector/catalog/active_record'
Puppet.features.stubs(:rails?).returns true
Puppet::Rails.stubs(:init)
@terminus = Puppet::Resource::Catalog::ActiveRecord.new
end
it "should be a subclass of the ActiveRecord terminus class" do
Puppet::Resource::Catalog::ActiveRecord.ancestors.should be_include(Puppet::Indirector::ActiveRecord)
end
it "should use Puppet::Rails::Host as its ActiveRecord model" do
Puppet::Resource::Catalog::ActiveRecord.ar_model.should equal(Puppet::Rails::Host)
end
describe "when finding an instance" do
before do
@request = stub 'request', :key => "foo", :options => {:cache_integration_hack => true}
end
# This hack is here because we don't want to look in the db unless we actually want
# to look in the db, but our indirection architecture in 0.24.x isn't flexible
# enough to tune that via configuration.
it "should return nil unless ':cache_integration_hack' is set to true" do
@request.options[:cache_integration_hack] = false
Puppet::Rails::Host.expects(:find_by_name).never
@terminus.find(@request).should be_nil
end
it "should use the Hosts ActiveRecord class to find the host" do
Puppet::Rails::Host.expects(:find_by_name).with { |key, args| key == "foo" }
@terminus.find(@request)
end
it "should return nil if no host instance can be found" do
Puppet::Rails::Host.expects(:find_by_name).returns nil
@terminus.find(@request).should be_nil
end
it "should return a catalog with the same name as the host if the host can be found" do
host = stub 'host', :name => "foo", :resources => []
Puppet::Rails::Host.expects(:find_by_name).returns host
result = @terminus.find(@request)
result.should be_instance_of(Puppet::Resource::Catalog)
result.name.should == "foo"
end
it "should set each of the host's resources as a transportable resource within the catalog" do
host = stub 'host', :name => "foo"
Puppet::Rails::Host.expects(:find_by_name).returns host
res1 = mock 'res1', :to_transportable => "trans_res1"
res2 = mock 'res2', :to_transportable => "trans_res2"
host.expects(:resources).returns [res1, res2]
catalog = stub 'catalog'
Puppet::Resource::Catalog.expects(:new).returns catalog
catalog.expects(:add_resource).with "trans_res1"
catalog.expects(:add_resource).with "trans_res2"
@terminus.find(@request)
end
end
describe "when saving an instance" do
before do
@host = Host.new(:name => "foo")
@host.stubs(:merge_resources)
@host.stubs(:save)
@host.stubs(:railsmark).yields
@node = Puppet::Node.new("foo", :environment => "environment")
Puppet::Node.indirection.stubs(:find).with("foo").returns(@node)
Puppet::Rails::Host.stubs(:find_by_name).returns @host
@catalog = Puppet::Resource::Catalog.new("foo")
@request = Puppet::Indirector::Request.new(:active_record, :save, @catalog)
end
it "should find the Rails host with the same name" do
Puppet::Rails::Host.expects(:find_by_name).with("foo").returns @host
@terminus.save(@request)
end
it "should create a new Rails host if none can be found" do
Puppet::Rails::Host.expects(:find_by_name).with("foo").returns nil
Puppet::Rails::Host.expects(:create).with(:name => "foo").returns @host
@terminus.save(@request)
end
it "should set the catalog vertices as resources on the Rails host instance" do
@catalog.expects(:vertices).returns "foo"
@host.expects(:merge_resources).with("foo")
@terminus.save(@request)
end
it "should set host ip if we could find a matching node" do
@node.stubs(:parameters).returns({"ipaddress" => "192.168.0.1"})
@terminus.save(@request)
@host.ip.should == '192.168.0.1'
end
it "should set host environment if we could find a matching node" do
@terminus.save(@request)
@host.environment.should == "environment"
end
it "should set the last compile time on the host" do
now = Time.now
Time.expects(:now).returns now
@terminus.save(@request)
@host.last_compile.should == now
end
it "should save the Rails host instance" do
@host.expects(:save)
@terminus.save(@request)
end
end
end
diff --git a/spec/unit/indirector/facts/active_record_spec.rb b/spec/unit/indirector/facts/active_record_spec.rb
index 0bdcfcb77..e96e01056 100755
--- a/spec/unit/indirector/facts/active_record_spec.rb
+++ b/spec/unit/indirector/facts/active_record_spec.rb
@@ -1,105 +1,103 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../spec_helper'
require 'puppet/rails'
require 'puppet/node/facts'
-describe "Puppet::Node::Facts::ActiveRecord" do
- confine "Missing Rails" => Puppet.features.rails?
-
+describe "Puppet::Node::Facts::ActiveRecord", :if => Puppet.features.rails? do
before do
require 'puppet/indirector/facts/active_record'
Puppet.features.stubs(:rails?).returns true
Puppet::Rails.stubs(:init)
@terminus = Puppet::Node::Facts::ActiveRecord.new
end
it "should be a subclass of the ActiveRecord terminus class" do
Puppet::Node::Facts::ActiveRecord.ancestors.should be_include(Puppet::Indirector::ActiveRecord)
end
it "should use Puppet::Rails::Host as its ActiveRecord model" do
Puppet::Node::Facts::ActiveRecord.ar_model.should equal(Puppet::Rails::Host)
end
describe "when finding an instance" do
before do
@request = stub 'request', :key => "foo"
end
it "should use the Hosts ActiveRecord class to find the host" do
Puppet::Rails::Host.expects(:find_by_name).with { |key, args| key == "foo" }
@terminus.find(@request)
end
it "should include the fact names and values when finding the host" do
Puppet::Rails::Host.expects(:find_by_name).with { |key, args| args[:include] == {:fact_values => :fact_name} }
@terminus.find(@request)
end
it "should return nil if no host instance can be found" do
Puppet::Rails::Host.expects(:find_by_name).returns nil
@terminus.find(@request).should be_nil
end
it "should convert the node's parameters into a Facts instance if a host instance is found" do
host = stub 'host', :name => "foo"
host.expects(:get_facts_hash).returns("one" => [mock("two_value", :value => "two")], "three" => [mock("three_value", :value => "four")])
Puppet::Rails::Host.expects(:find_by_name).returns host
result = @terminus.find(@request)
result.should be_instance_of(Puppet::Node::Facts)
result.name.should == "foo"
result.values.should == {"one" => "two", "three" => "four"}
end
it "should convert all single-member arrays into non-arrays" do
host = stub 'host', :name => "foo"
host.expects(:get_facts_hash).returns("one" => [mock("two_value", :value => "two")])
Puppet::Rails::Host.expects(:find_by_name).returns host
@terminus.find(@request).values["one"].should == "two"
end
end
describe "when saving an instance" do
before do
@host = stub 'host', :name => "foo", :save => nil, :merge_facts => nil
Puppet::Rails::Host.stubs(:find_by_name).returns @host
@facts = Puppet::Node::Facts.new("foo", "one" => "two", "three" => "four")
@request = stub 'request', :key => "foo", :instance => @facts
end
it "should find the Rails host with the same name" do
Puppet::Rails::Host.expects(:find_by_name).with("foo").returns @host
@terminus.save(@request)
end
it "should create a new Rails host if none can be found" do
Puppet::Rails::Host.expects(:find_by_name).with("foo").returns nil
Puppet::Rails::Host.expects(:create).with(:name => "foo").returns @host
@terminus.save(@request)
end
it "should set the facts as facts on the Rails host instance" do
# There is other stuff added to the hash.
@host.expects(:merge_facts).with { |args| args["one"] == "two" and args["three"] == "four" }
@terminus.save(@request)
end
it "should save the Rails host instance" do
@host.expects(:save)
@terminus.save(@request)
end
end
end
diff --git a/spec/unit/indirector/facts/couch_spec.rb b/spec/unit/indirector/facts/couch_spec.rb
index c0dd54b8a..e3a9d7f14 100644
--- a/spec/unit/indirector/facts/couch_spec.rb
+++ b/spec/unit/indirector/facts/couch_spec.rb
@@ -1,98 +1,97 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../spec_helper'
require 'puppet/node/facts'
-describe "Puppet::Node::Facts::Couch" do
- confine "couchrest gem is missing; cannot test couch terminus" => Puppet.features.couchdb?
+describe "Puppet::Node::Facts::Couch", :if => Puppet.features.couchdb? do
require 'puppet/indirector/facts/couch' if Puppet.features.couchdb?
before do
@mock_db = mock('couch db')
mock_document = CouchRest::Document.new(:_id => fake_request.key, :facts => fake_request.values)
mock_document.stubs(:database).returns(@mock_db)
@mock_db.stubs(:get).with(fake_request.key).returns(mock_document)
Puppet::Node::Facts::Couch.stubs(:db).returns(@mock_db)
end
subject { Puppet::Node::Facts::Couch }
describe "#find" do
describe "when the node document exists" do
it "should find the request by key" do
@mock_db.expects(:get).with(fake_request.key).returns({'_id' => fake_request.key, 'facts' => fake_request.instance.values})
subject.new.find(fake_request).should == fake_request.instance
end
end
describe "when the node document does not exist" do
before do
@mock_db.expects(:get).
with(fake_request.key).
raises(RestClient::ResourceNotFound)
end
it "should return nil" do
subject.new.find(fake_request).should be_nil
end
it "should send Puppet a debug message" do
Puppet.expects(:debug).with("No couchdb document with id: test.local")
subject.new.find(fake_request).should be_nil
end
end
end
describe "#save" do
describe "with options" do
subject do
lambda { Puppet::Node::Facts::Couch.new.save(fake_request([1])) }
end
it { should raise_error(ArgumentError, "PUT does not accept options") }
end
it "should save the json to the CouchDB database" do
@mock_db.expects(:save_doc).at_least_once.returns({'ok' => true })
subject.new.save(fake_request)
end
describe "when the document exists" do
before do
@doc = CouchRest::Document.new(:_id => fake_request.key, :facts => fake_request.instance.values)
@mock_db.expects(:get).with(fake_request.key).returns(@doc)
end
it "saves the document" do
@doc.expects(:save)
subject.new.save(fake_request)
end
end
describe "when the document does not exist" do
before do
@mock_db.expects(:get).
with(fake_request.key).
raises(RestClient::ResourceNotFound)
end
it "saves the document" do
@mock_db.expects(:save_doc)
subject.new.save(fake_request)
end
end
end
def fake_request(options={})
facts = YAML.load_file(File.join(PuppetSpec::FIXTURE_DIR, 'yaml', 'test.local.yaml'))
Struct.new(:instance, :key, :options).new(facts, facts.name, options)
end
private :fake_request
end
diff --git a/spec/unit/indirector/indirection_spec.rb b/spec/unit/indirector/indirection_spec.rb
index b0e0f019c..1e774fb2e 100755
--- a/spec/unit/indirector/indirection_spec.rb
+++ b/spec/unit/indirector/indirection_spec.rb
@@ -1,795 +1,795 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/indirector/indirection'
-describe "Indirection Delegator", :shared => true do
+shared_examples_for "Indirection Delegator" do
it "should create a request object with the appropriate method name and all of the passed arguments" do
request = Puppet::Indirector::Request.new(:indirection, :find, "me")
@indirection.expects(:request).with(@method, "mystuff", :one => :two).returns request
@terminus.stubs(@method)
@indirection.send(@method, "mystuff", :one => :two)
end
it "should let the :select_terminus method choose the terminus using the created request if the :select_terminus method is available" do
# Define the method, so our respond_to? hook matches.
class << @indirection
def select_terminus(request)
end
end
request = Puppet::Indirector::Request.new(:indirection, :find, "me")
@indirection.stubs(:request).returns request
@indirection.expects(:select_terminus).with(request).returns :test_terminus
@indirection.stubs(:check_authorization)
@terminus.expects(@method)
@indirection.send(@method, "me")
end
it "should fail if the :select_terminus hook does not return a terminus name" do
# Define the method, so our respond_to? hook matches.
class << @indirection
def select_terminus(request)
end
end
request = stub 'request', :key => "me", :options => {}
@indirection.stubs(:request).returns request
@indirection.expects(:select_terminus).with(request).returns nil
lambda { @indirection.send(@method, "me") }.should raise_error(ArgumentError)
end
it "should choose the terminus returned by the :terminus_class method if no :select_terminus method is available" do
@indirection.expects(:terminus_class).returns :test_terminus
@terminus.expects(@method)
@indirection.send(@method, "me")
end
it "should let the appropriate terminus perform the lookup" do
@terminus.expects(@method).with { |r| r.is_a?(Puppet::Indirector::Request) }
@indirection.send(@method, "me")
end
end
-describe "Delegation Authorizer", :shared => true do
+shared_examples_for "Delegation Authorizer" do
before do
# So the :respond_to? turns out correctly.
class << @terminus
def authorized?
end
end
end
it "should not check authorization if a node name is not provided" do
@terminus.expects(:authorized?).never
@terminus.stubs(@method)
# The quotes are necessary here, else it looks like a block.
@request.stubs(:options).returns({})
@indirection.send(@method, "/my/key")
end
it "should pass the request to the terminus's authorization method" do
@terminus.expects(:authorized?).with { |r| r.is_a?(Puppet::Indirector::Request) }.returns(true)
@terminus.stubs(@method)
@indirection.send(@method, "/my/key", :node => "mynode")
end
it "should fail if authorization returns false" do
@terminus.expects(:authorized?).returns(false)
@terminus.stubs(@method)
proc { @indirection.send(@method, "/my/key", :node => "mynode") }.should raise_error(ArgumentError)
end
it "should continue if authorization returns true" do
@terminus.expects(:authorized?).returns(true)
@terminus.stubs(@method)
@indirection.send(@method, "/my/key", :node => "mynode")
end
end
describe Puppet::Indirector::Indirection do
after do
Puppet::Util::Cacher.expire
end
describe "when initializing" do
# (LAK) I've no idea how to test this, really.
it "should store a reference to itself before it consumes its options" do
proc { @indirection = Puppet::Indirector::Indirection.new(Object.new, :testingness, :not_valid_option) }.should raise_error
Puppet::Indirector::Indirection.instance(:testingness).should be_instance_of(Puppet::Indirector::Indirection)
Puppet::Indirector::Indirection.instance(:testingness).delete
end
it "should keep a reference to the indirecting model" do
model = mock 'model'
@indirection = Puppet::Indirector::Indirection.new(model, :myind)
@indirection.model.should equal(model)
end
it "should set the name" do
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :myind)
@indirection.name.should == :myind
end
it "should require indirections to have unique names" do
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
proc { Puppet::Indirector::Indirection.new(:test) }.should raise_error(ArgumentError)
end
it "should extend itself with any specified module" do
mod = Module.new
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test, :extend => mod)
@indirection.singleton_class.included_modules.should include(mod)
end
after do
@indirection.delete if defined?(@indirection)
end
end
describe "when an instance" do
before :each do
@terminus_class = mock 'terminus_class'
@terminus = mock 'terminus'
@terminus_class.stubs(:new).returns(@terminus)
@cache = stub 'cache', :name => "mycache"
@cache_class = mock 'cache_class'
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :cache_terminus).returns(@cache_class)
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :test_terminus).returns(@terminus_class)
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
@indirection.terminus_class = :test_terminus
@instance = stub 'instance', :expiration => nil, :expiration= => nil, :name => "whatever"
@name = :mything
#@request = stub 'instance', :key => "/my/key", :instance => @instance, :options => {}
@request = mock 'instance'
end
it "should allow setting the ttl" do
@indirection.ttl = 300
@indirection.ttl.should == 300
end
it "should default to the :runinterval setting, converted to an integer, for its ttl" do
Puppet.settings.expects(:value).returns "1800"
@indirection.ttl.should == 1800
end
it "should calculate the current expiration by adding the TTL to the current time" do
@indirection.stubs(:ttl).returns(100)
now = Time.now
Time.stubs(:now).returns now
@indirection.expiration.should == (Time.now + 100)
end
it "should have a method for creating an indirection request instance" do
@indirection.should respond_to(:request)
end
describe "creates a request" do
it "should create it with its name as the request's indirection name" do
Puppet::Indirector::Request.expects(:new).with { |name, *other| @indirection.name == name }
@indirection.request(:funtest, "yayness")
end
it "should require a method and key" do
Puppet::Indirector::Request.expects(:new).with { |name, method, key, *other| method == :funtest and key == "yayness" }
@indirection.request(:funtest, "yayness")
end
it "should support optional arguments" do
Puppet::Indirector::Request.expects(:new).with { |name, method, key, other| other == {:one => :two} }
@indirection.request(:funtest, "yayness", :one => :two)
end
it "should not pass options if none are supplied" do
Puppet::Indirector::Request.expects(:new).with { |*args| args.length < 4 }
@indirection.request(:funtest, "yayness")
end
it "should return the request" do
request = mock 'request'
Puppet::Indirector::Request.expects(:new).returns request
@indirection.request(:funtest, "yayness").should equal(request)
end
end
describe "and looking for a model instance" do
before { @method = :find }
it_should_behave_like "Indirection Delegator"
it_should_behave_like "Delegation Authorizer"
it "should return the results of the delegation" do
@terminus.expects(:find).returns(@instance)
@indirection.find("me").should equal(@instance)
end
it "should set the expiration date on any instances without one set" do
@terminus.stubs(:find).returns(@instance)
@indirection.expects(:expiration).returns :yay
@instance.expects(:expiration).returns(nil)
@instance.expects(:expiration=).with(:yay)
@indirection.find("/my/key")
end
it "should not override an already-set expiration date on returned instances" do
@terminus.stubs(:find).returns(@instance)
@indirection.expects(:expiration).never
@instance.expects(:expiration).returns(:yay)
@instance.expects(:expiration=).never
@indirection.find("/my/key")
end
it "should filter the result instance if the terminus supports it" do
@terminus.stubs(:find).returns(@instance)
@terminus.stubs(:respond_to?).with(:filter).returns(true)
@terminus.expects(:filter).with(@instance)
@indirection.find("/my/key")
end
describe "when caching is enabled" do
before do
@indirection.cache_class = :cache_terminus
@cache_class.stubs(:new).returns(@cache)
@instance.stubs(:expired?).returns false
end
it "should first look in the cache for an instance" do
@terminus.stubs(:find).never
@cache.expects(:find).returns @instance
@indirection.find("/my/key")
end
it "should not look in the cache if the request specifies not to use the cache" do
@terminus.expects(:find).returns @instance
@cache.expects(:find).never
@cache.stubs(:save)
@indirection.find("/my/key", :ignore_cache => true)
end
it "should still save to the cache even if the cache is being ignored during readin" do
@terminus.expects(:find).returns @instance
@cache.expects(:save)
@indirection.find("/my/key", :ignore_cache => true)
end
it "should only look in the cache if the request specifies not to use the terminus" do
@terminus.expects(:find).never
@cache.expects(:find)
@indirection.find("/my/key", :ignore_terminus => true)
end
it "should use a request to look in the cache for cached objects" do
@cache.expects(:find).with { |r| r.method == :find and r.key == "/my/key" }.returns @instance
@cache.stubs(:save)
@indirection.find("/my/key")
end
it "should return the cached object if it is not expired" do
@instance.stubs(:expired?).returns false
@cache.stubs(:find).returns @instance
@indirection.find("/my/key").should equal(@instance)
end
it "should not fail if the cache fails" do
@terminus.stubs(:find).returns @instance
@cache.expects(:find).raises ArgumentError
@cache.stubs(:save)
lambda { @indirection.find("/my/key") }.should_not raise_error
end
it "should look in the main terminus if the cache fails" do
@terminus.expects(:find).returns @instance
@cache.expects(:find).raises ArgumentError
@cache.stubs(:save)
@indirection.find("/my/key").should equal(@instance)
end
it "should send a debug log if it is using the cached object" do
Puppet.expects(:debug)
@cache.stubs(:find).returns @instance
@indirection.find("/my/key")
end
it "should not return the cached object if it is expired" do
@instance.stubs(:expired?).returns true
@cache.stubs(:find).returns @instance
@terminus.stubs(:find).returns nil
@indirection.find("/my/key").should be_nil
end
it "should send an info log if it is using the cached object" do
Puppet.expects(:info)
@instance.stubs(:expired?).returns true
@cache.stubs(:find).returns @instance
@terminus.stubs(:find).returns nil
@indirection.find("/my/key")
end
it "should cache any objects not retrieved from the cache" do
@cache.expects(:find).returns nil
@terminus.expects(:find).returns(@instance)
@cache.expects(:save)
@indirection.find("/my/key")
end
it "should use a request to look in the cache for cached objects" do
@cache.expects(:find).with { |r| r.method == :find and r.key == "/my/key" }.returns nil
@terminus.stubs(:find).returns(@instance)
@cache.stubs(:save)
@indirection.find("/my/key")
end
it "should cache the instance using a request with the instance set to the cached object" do
@cache.stubs(:find).returns nil
@terminus.stubs(:find).returns(@instance)
@cache.expects(:save).with { |r| r.method == :save and r.instance == @instance }
@indirection.find("/my/key")
end
it "should send an info log that the object is being cached" do
@cache.stubs(:find).returns nil
@terminus.stubs(:find).returns(@instance)
@cache.stubs(:save)
Puppet.expects(:info)
@indirection.find("/my/key")
end
end
end
describe "and storing a model instance" do
before { @method = :save }
it_should_behave_like "Indirection Delegator"
it_should_behave_like "Delegation Authorizer"
it "should return the result of the save" do
@terminus.stubs(:save).returns "foo"
@indirection.save(@instance).should == "foo"
end
describe "when caching is enabled" do
before do
@indirection.cache_class = :cache_terminus
@cache_class.stubs(:new).returns(@cache)
@instance.stubs(:expired?).returns false
end
it "should return the result of saving to the terminus" do
request = stub 'request', :instance => @instance, :node => nil
@indirection.expects(:request).returns request
@cache.stubs(:save)
@terminus.stubs(:save).returns @instance
@indirection.save(@instance).should equal(@instance)
end
it "should use a request to save the object to the cache" do
request = stub 'request', :instance => @instance, :node => nil
@indirection.expects(:request).returns request
@cache.expects(:save).with(request)
@terminus.stubs(:save)
@indirection.save(@instance)
end
it "should not save to the cache if the normal save fails" do
request = stub 'request', :instance => @instance, :node => nil
@indirection.expects(:request).returns request
@cache.expects(:save).never
@terminus.expects(:save).raises "eh"
lambda { @indirection.save(@instance) }.should raise_error
end
end
end
describe "and removing a model instance" do
before { @method = :destroy }
it_should_behave_like "Indirection Delegator"
it_should_behave_like "Delegation Authorizer"
it "should return the result of removing the instance" do
@terminus.stubs(:destroy).returns "yayness"
@indirection.destroy("/my/key").should == "yayness"
end
describe "when caching is enabled" do
before do
@indirection.cache_class = :cache_terminus
@cache_class.expects(:new).returns(@cache)
@instance.stubs(:expired?).returns false
end
it "should use a request instance to search in and remove objects from the cache" do
destroy = stub 'destroy_request', :key => "/my/key", :node => nil
find = stub 'destroy_request', :key => "/my/key", :node => nil
@indirection.expects(:request).with(:destroy, "/my/key").returns destroy
@indirection.expects(:request).with(:find, "/my/key").returns find
cached = mock 'cache'
@cache.expects(:find).with(find).returns cached
@cache.expects(:destroy).with(destroy)
@terminus.stubs(:destroy)
@indirection.destroy("/my/key")
end
end
end
describe "and searching for multiple model instances" do
before { @method = :search }
it_should_behave_like "Indirection Delegator"
it_should_behave_like "Delegation Authorizer"
it "should set the expiration date on any instances without one set" do
@terminus.stubs(:search).returns([@instance])
@indirection.expects(:expiration).returns :yay
@instance.expects(:expiration).returns(nil)
@instance.expects(:expiration=).with(:yay)
@indirection.search("/my/key")
end
it "should not override an already-set expiration date on returned instances" do
@terminus.stubs(:search).returns([@instance])
@indirection.expects(:expiration).never
@instance.expects(:expiration).returns(:yay)
@instance.expects(:expiration=).never
@indirection.search("/my/key")
end
it "should return the results of searching in the terminus" do
@terminus.expects(:search).returns([@instance])
@indirection.search("/my/key").should == [@instance]
end
end
describe "and expiring a model instance" do
describe "when caching is not enabled" do
it "should do nothing" do
@cache_class.expects(:new).never
@indirection.expire("/my/key")
end
end
describe "when caching is enabled" do
before do
@indirection.cache_class = :cache_terminus
@cache_class.expects(:new).returns(@cache)
@instance.stubs(:expired?).returns false
@cached = stub 'cached', :expiration= => nil, :name => "/my/key"
end
it "should use a request to find within the cache" do
@cache.expects(:find).with { |r| r.is_a?(Puppet::Indirector::Request) and r.method == :find }
@indirection.expire("/my/key")
end
it "should do nothing if no such instance is cached" do
@cache.expects(:find).returns nil
@indirection.expire("/my/key")
end
it "should log when expiring a found instance" do
@cache.expects(:find).returns @cached
@cache.stubs(:save)
Puppet.expects(:info)
@indirection.expire("/my/key")
end
it "should set the cached instance's expiration to a time in the past" do
@cache.expects(:find).returns @cached
@cache.stubs(:save)
@cached.expects(:expiration=).with { |t| t < Time.now }
@indirection.expire("/my/key")
end
it "should save the now expired instance back into the cache" do
@cache.expects(:find).returns @cached
@cached.expects(:expiration=).with { |t| t < Time.now }
@cache.expects(:save)
@indirection.expire("/my/key")
end
it "should use a request to save the expired resource to the cache" do
@cache.expects(:find).returns @cached
@cached.expects(:expiration=).with { |t| t < Time.now }
@cache.expects(:save).with { |r| r.is_a?(Puppet::Indirector::Request) and r.instance == @cached and r.method == :save }.returns(@cached)
@indirection.expire("/my/key")
end
end
end
after :each do
@indirection.delete
Puppet::Util::Cacher.expire
end
end
describe "when managing indirection instances" do
it "should allow an indirection to be retrieved by name" do
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
Puppet::Indirector::Indirection.instance(:test).should equal(@indirection)
end
it "should return nil when the named indirection has not been created" do
Puppet::Indirector::Indirection.instance(:test).should be_nil
end
it "should allow an indirection's model to be retrieved by name" do
mock_model = mock('model')
@indirection = Puppet::Indirector::Indirection.new(mock_model, :test)
Puppet::Indirector::Indirection.model(:test).should equal(mock_model)
end
it "should return nil when no model matches the requested name" do
Puppet::Indirector::Indirection.model(:test).should be_nil
end
after do
@indirection.delete if defined?(@indirection)
end
end
describe "when routing to the correct the terminus class" do
before do
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
@terminus = mock 'terminus'
@terminus_class = stub 'terminus class', :new => @terminus
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :default).returns(@terminus_class)
end
it "should fail if no terminus class can be picked" do
proc { @indirection.terminus_class }.should raise_error(Puppet::DevError)
end
it "should choose the default terminus class if one is specified" do
@indirection.terminus_class = :default
@indirection.terminus_class.should equal(:default)
end
it "should use the provided Puppet setting if told to do so" do
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :my_terminus).returns(mock("terminus_class2"))
Puppet.settings.expects(:value).with(:my_setting).returns("my_terminus")
@indirection.terminus_setting = :my_setting
@indirection.terminus_class.should equal(:my_terminus)
end
it "should fail if the provided terminus class is not valid" do
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :nosuchclass).returns(nil)
proc { @indirection.terminus_class = :nosuchclass }.should raise_error(ArgumentError)
end
after do
@indirection.delete if defined?(@indirection)
end
end
describe "when specifying the terminus class to use" do
before do
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
@terminus = mock 'terminus'
@terminus_class = stub 'terminus class', :new => @terminus
end
it "should allow specification of a terminus type" do
@indirection.should respond_to(:terminus_class=)
end
it "should fail to redirect if no terminus type has been specified" do
proc { @indirection.find("blah") }.should raise_error(Puppet::DevError)
end
it "should fail when the terminus class name is an empty string" do
proc { @indirection.terminus_class = "" }.should raise_error(ArgumentError)
end
it "should fail when the terminus class name is nil" do
proc { @indirection.terminus_class = nil }.should raise_error(ArgumentError)
end
it "should fail when the specified terminus class cannot be found" do
Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(nil)
proc { @indirection.terminus_class = :foo }.should raise_error(ArgumentError)
end
it "should select the specified terminus class if a terminus class name is provided" do
Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(@terminus_class)
@indirection.terminus(:foo).should equal(@terminus)
end
it "should use the configured terminus class if no terminus name is specified" do
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class)
@indirection.terminus_class = :foo
@indirection.terminus.should equal(@terminus)
end
after do
@indirection.delete if defined?(@indirection)
end
end
describe "when managing terminus instances" do
before do
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
@terminus = mock 'terminus'
@terminus_class = mock 'terminus class'
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class)
end
it "should create an instance of the chosen terminus class" do
@terminus_class.stubs(:new).returns(@terminus)
@indirection.terminus(:foo).should equal(@terminus)
end
# Make sure it caches the terminus.
it "should return the same terminus instance each time for a given name" do
@terminus_class.stubs(:new).returns(@terminus)
@indirection.terminus(:foo).should equal(@terminus)
@indirection.terminus(:foo).should equal(@terminus)
end
it "should not create a terminus instance until one is actually needed" do
Puppet::Indirector.expects(:terminus).never
indirection = Puppet::Indirector::Indirection.new(mock('model'), :lazytest)
end
after do
@indirection.delete
end
end
describe "when deciding whether to cache" do
before do
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
@terminus = mock 'terminus'
@terminus_class = mock 'terminus class'
@terminus_class.stubs(:new).returns(@terminus)
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class)
@indirection.terminus_class = :foo
end
it "should provide a method for setting the cache terminus class" do
@indirection.should respond_to(:cache_class=)
end
it "should fail to cache if no cache type has been specified" do
proc { @indirection.cache }.should raise_error(Puppet::DevError)
end
it "should fail to set the cache class when the cache class name is an empty string" do
proc { @indirection.cache_class = "" }.should raise_error(ArgumentError)
end
it "should allow resetting the cache_class to nil" do
@indirection.cache_class = nil
@indirection.cache_class.should be_nil
end
it "should fail to set the cache class when the specified cache class cannot be found" do
Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(nil)
proc { @indirection.cache_class = :foo }.should raise_error(ArgumentError)
end
after do
@indirection.delete
end
end
describe "when using a cache" do
before :each do
Puppet.settings.stubs(:value).with("test_terminus").returns("test_terminus")
@terminus_class = mock 'terminus_class'
@terminus = mock 'terminus'
@terminus_class.stubs(:new).returns(@terminus)
@cache = mock 'cache'
@cache_class = mock 'cache_class'
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :cache_terminus).returns(@cache_class)
Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :test_terminus).returns(@terminus_class)
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
@indirection.terminus_class = :test_terminus
end
describe "and managing the cache terminus" do
it "should not create a cache terminus at initialization" do
# This is weird, because all of the code is in the setup. If we got
# new called on the cache class, we'd get an exception here.
end
it "should reuse the cache terminus" do
@cache_class.expects(:new).returns(@cache)
Puppet.settings.stubs(:value).with("test_cache").returns("cache_terminus")
@indirection.cache_class = :cache_terminus
@indirection.cache.should equal(@cache)
@indirection.cache.should equal(@cache)
end
end
describe "and saving" do
end
describe "and finding" do
end
after :each do
@indirection.delete
end
end
end
diff --git a/spec/unit/indirector/ldap_spec.rb b/spec/unit/indirector/ldap_spec.rb
index 31a3406e9..c071f870e 100755
--- a/spec/unit/indirector/ldap_spec.rb
+++ b/spec/unit/indirector/ldap_spec.rb
@@ -1,143 +1,139 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/indirector/ldap'
describe Puppet::Indirector::Ldap do
before do
@indirection = stub 'indirection', :name => :testing
Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection)
@ldap_class = Class.new(Puppet::Indirector::Ldap) do
def self.to_s
"Testing::Mytype"
end
end
@connection = mock 'ldap'
@searcher = @ldap_class.new
end
describe "when searching ldap" do
before do
# Stub everything, and we can selectively replace with an expect as
# we need to for testing.
@searcher.stubs(:connection).returns(@connection)
@searcher.stubs(:search_filter).returns(:filter)
@searcher.stubs(:search_base).returns(:base)
@searcher.stubs(:process)
@request = stub 'request', :key => "yay"
end
it "should call the ldapsearch method with the search filter" do
@searcher.expects(:search_filter).with("yay").returns("yay's filter")
@searcher.expects(:ldapsearch).with("yay's filter")
@searcher.find @request
end
it "should fail if no block is passed to the ldapsearch method" do
proc { @searcher.ldapsearch("blah") }.should raise_error(ArgumentError)
end
it "should use the results of the ldapbase method as the ldap search base" do
@searcher.stubs(:search_base).returns("mybase")
@connection.expects(:search).with do |*args|
args[0].should == "mybase"
true
end
@searcher.find @request
end
it "should default to the value of the :search_base setting as the result of the ldapbase method" do
Puppet.expects(:[]).with(:ldapbase).returns("myldapbase")
searcher = @ldap_class.new
searcher.search_base.should == "myldapbase"
end
it "should use the results of the :search_attributes method as the list of attributes to return" do
@searcher.stubs(:search_attributes).returns(:myattrs)
@connection.expects(:search).with do |*args|
args[3].should == :myattrs
true
end
@searcher.find @request
end
it "should use depth 2 when searching" do
@connection.expects(:search).with do |*args|
args[1].should == 2
true
end
@searcher.find @request
end
it "should call process() on the first found entry" do
@connection.expects(:search).yields("myresult")
@searcher.expects(:process).with("myresult")
@searcher.find @request
end
it "should reconnect and retry the search if there is a failure" do
run = false
@connection.stubs(:search).with do |*args|
if run
true
else
run = true
raise "failed"
end
end.yields("myresult")
@searcher.expects(:process).with("myresult")
@searcher.find @request
end
it "should not reconnect on failure more than once" do
count = 0
@connection.stubs(:search).with do |*args|
count += 1
raise ArgumentError, "yay"
end
proc { @searcher.find(@request) }.should raise_error(Puppet::Error)
count.should == 2
end
it "should return true if an entry is found" do
@connection.expects(:search).yields("result")
@searcher.ldapsearch("whatever") { |r| }.should be_true
end
end
- describe "when connecting to ldap" do
- confine "LDAP is not available" => Puppet.features.ldap?
-
+ describe "when connecting to ldap", :if => Puppet.features.ldap? do
it "should create and start a Util::Ldap::Connection instance" do
conn = mock 'connection', :connection => "myconn", :start => nil
Puppet::Util::Ldap::Connection.expects(:instance).returns conn
@searcher.connection.should == "myconn"
end
it "should only create the ldap connection when asked for it the first time" do
conn = mock 'connection', :connection => "myconn", :start => nil
Puppet::Util::Ldap::Connection.expects(:instance).returns conn
@searcher.connection
end
it "should cache the connection" do
conn = mock 'connection', :connection => "myconn", :start => nil
Puppet::Util::Ldap::Connection.expects(:instance).returns conn
@searcher.connection.should equal(@searcher.connection)
end
end
- describe "when reconnecting to ldap" do
- confine "Not running on culain as root" => (Puppet.features.root? and Facter.value("hostname") == "culain")
-
+ describe "when reconnecting to ldap", :if => (Puppet.features.root? and Facter.value("hostname") == "culain") do
it "should reconnect to ldap when connections are lost"
end
end
diff --git a/spec/unit/indirector/node/active_record_spec.rb b/spec/unit/indirector/node/active_record_spec.rb
index 3540ef738..69229e144 100755
--- a/spec/unit/indirector/node/active_record_spec.rb
+++ b/spec/unit/indirector/node/active_record_spec.rb
@@ -1,40 +1,38 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../spec_helper'
require 'puppet/node'
-describe "Puppet::Node::ActiveRecord" do
+describe "Puppet::Node::ActiveRecord", :if => Puppet.features.rails? && Puppet.features.sqlite? do
include PuppetSpec::Files
- confine "Missing Rails" => Puppet.features.rails?
- confine "Missing sqlite" => Puppet.features.sqlite?
before do
require 'puppet/indirector/node/active_record'
end
it "should be a subclass of the ActiveRecord terminus class" do
Puppet::Node::ActiveRecord.ancestors.should be_include(Puppet::Indirector::ActiveRecord)
end
it "should use Puppet::Rails::Host as its ActiveRecord model" do
Puppet::Node::ActiveRecord.ar_model.should equal(Puppet::Rails::Host)
end
it "should call fact_merge when a node is found" do
db_instance = stub 'db_instance'
Puppet::Node::ActiveRecord.ar_model.expects(:find_by_name).returns db_instance
node = Puppet::Node.new("foo")
db_instance.expects(:to_puppet).returns node
Puppet[:statedir] = tmpdir('active_record_tmp')
Puppet[:railslog] = '$statedir/rails.log'
ar = Puppet::Node::ActiveRecord.new
node.expects(:fact_merge)
request = Puppet::Indirector::Request.new(:node, :find, "what.ever")
ar.find(request)
end
end
diff --git a/spec/unit/indirector/queue_spec.rb b/spec/unit/indirector/queue_spec.rb
index 83e9c771d..00463ee0f 100755
--- a/spec/unit/indirector/queue_spec.rb
+++ b/spec/unit/indirector/queue_spec.rb
@@ -1,123 +1,121 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/indirector/queue'
class Puppet::Indirector::Queue::TestClient
end
class FooExampleData
attr_accessor :name
def self.pson_create(pson)
new(pson['data'].to_sym)
end
def initialize(name = nil)
@name = name if name
end
def render(format = :pson)
to_pson
end
def to_pson(*args)
{:type => self.class.to_s, :data => name}.to_pson(*args)
end
end
-describe Puppet::Indirector::Queue do
- confine "PSON library is missing; cannot test queueing" => Puppet.features.pson?
-
+describe Puppet::Indirector::Queue, :if => Puppet.features.pson? do
before :each do
@model = mock 'model'
@indirection = stub 'indirection', :name => :my_queue, :register_terminus_type => nil, :model => @model
Puppet::Indirector::Indirection.stubs(:instance).with(:my_queue).returns(@indirection)
@store_class = Class.new(Puppet::Indirector::Queue) do
def self.to_s
'MyQueue::MyType'
end
end
@store = @store_class.new
@subject_class = FooExampleData
@subject = @subject_class.new
@subject.name = :me
Puppet.settings.stubs(:value).returns("bogus setting data")
Puppet.settings.stubs(:value).with(:queue_type).returns(:test_client)
Puppet::Util::Queue.stubs(:queue_type_to_class).with(:test_client).returns(Puppet::Indirector::Queue::TestClient)
@request = stub 'request', :key => :me, :instance => @subject
end
it "should require PSON" do
Puppet.features.expects(:pson?).returns false
lambda { @store_class.new }.should raise_error(ArgumentError)
end
it 'should use the correct client type and queue' do
@store.queue.should == :my_queue
@store.client.should be_an_instance_of(Puppet::Indirector::Queue::TestClient)
end
describe "when saving" do
it 'should render the instance using pson' do
@subject.expects(:render).with(:pson)
@store.client.stubs(:send_message)
@store.save(@request)
end
it "should send the rendered message to the appropriate queue on the client" do
@subject.expects(:render).returns "mypson"
@store.client.expects(:send_message).with(:my_queue, "mypson")
@store.save(@request)
end
it "should catch any exceptions raised" do
@store.client.expects(:send_message).raises ArgumentError
lambda { @store.save(@request) }.should raise_error(Puppet::Error)
end
end
describe "when subscribing to the queue" do
before do
@store_class.stubs(:model).returns @model
end
it "should use the model's Format support to intern the message from pson" do
@model.expects(:convert_from).with(:pson, "mymessage")
@store_class.client.expects(:subscribe).yields("mymessage")
@store_class.subscribe {|o| o }
end
it "should yield each interned received message" do
@model.stubs(:convert_from).returns "something"
@subject_two = @subject_class.new
@subject_two.name = :too
@store_class.client.expects(:subscribe).with(:my_queue).multiple_yields(@subject, @subject_two)
received = []
@store_class.subscribe do |obj|
received.push(obj)
end
received.should == %w{something something}
end
it "should log but not propagate errors" do
@store_class.client.expects(:subscribe).yields("foo")
@store_class.expects(:intern).raises ArgumentError
Puppet.expects(:err)
@store_class.subscribe {|o| o }
end
end
end
diff --git a/spec/unit/indirector/rest_spec.rb b/spec/unit/indirector/rest_spec.rb
index 3efbfce97..5f0fe363a 100755
--- a/spec/unit/indirector/rest_spec.rb
+++ b/spec/unit/indirector/rest_spec.rb
@@ -1,455 +1,455 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/indirector/rest'
-describe "a REST http call", :shared => true do
+shared_examples_for "a REST http call" do
it "should accept a path" do
lambda { @search.send(@method, *@arguments) }.should_not raise_error(ArgumentError)
end
it "should require a path" do
lambda { @searcher.send(@method) }.should raise_error(ArgumentError)
end
it "should return the results of deserializing the response to the request" do
conn = mock 'connection'
conn.stubs(:put).returns @response
conn.stubs(:delete).returns @response
conn.stubs(:get).returns @response
Puppet::Network::HttpPool.stubs(:http_instance).returns conn
@searcher.expects(:deserialize).with(@response).returns "myobject"
@searcher.send(@method, *@arguments).should == 'myobject'
end
end
describe Puppet::Indirector::REST do
before do
Puppet::Indirector::Terminus.stubs(:register_terminus_class)
@model = stub('model', :supported_formats => %w{}, :convert_from => nil)
@instance = stub('model instance', :name= => nil)
@indirection = stub('indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model)
Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection)
@rest_class = Class.new(Puppet::Indirector::REST) do
def self.to_s
"This::Is::A::Test::Class"
end
end
@response = stub('mock response', :body => 'result', :code => "200")
@response.stubs(:[]).with('content-type').returns "text/plain"
@response.stubs(:[]).with('content-encoding').returns nil
@searcher = @rest_class.new
@searcher.stubs(:model).returns @model
end
it "should include the v1 REST API module" do
Puppet::Indirector::REST.ancestors.should be_include(Puppet::Network::HTTP::API::V1)
end
it "should have a method for specifying what setting a subclass should use to retrieve its server" do
@rest_class.should respond_to(:use_server_setting)
end
it "should use any specified setting to pick the server" do
@rest_class.expects(:server_setting).returns :servset
Puppet.settings.expects(:value).with(:servset).returns "myserver"
@rest_class.server.should == "myserver"
end
it "should default to :server for the server setting" do
@rest_class.expects(:server_setting).returns nil
Puppet.settings.expects(:value).with(:server).returns "myserver"
@rest_class.server.should == "myserver"
end
it "should have a method for specifying what setting a subclass should use to retrieve its port" do
@rest_class.should respond_to(:use_port_setting)
end
it "should use any specified setting to pick the port" do
@rest_class.expects(:port_setting).returns :servset
Puppet.settings.expects(:value).with(:servset).returns "321"
@rest_class.port.should == 321
end
it "should default to :port for the port setting" do
@rest_class.expects(:port_setting).returns nil
Puppet.settings.expects(:value).with(:masterport).returns "543"
@rest_class.port.should == 543
end
describe "when deserializing responses" do
it "should return nil if the response code is 404" do
response = mock 'response'
response.expects(:code).returns "404"
@searcher.deserialize(response).should be_nil
end
[300,400,403,405,500,501,502,503,504].each { |rc|
describe "when the response code is #{rc}" do
before :each do
@model.expects(:convert_from).never
@response = mock 'response'
@response.stubs(:code).returns rc.to_s
@response.stubs(:[]).with('content-encoding').returns nil
@response.stubs(:message).returns "There was a problem (header)"
end
it "should fail" do
@response.stubs(:body).returns nil
lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError)
end
it "should take the error message from the body, if present" do
@response.stubs(:body).returns "There was a problem (body)"
lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (body)")
end
it "should take the error message from the response header if the body is empty" do
@response.stubs(:body).returns ""
lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (header)")
end
it "should take the error message from the response header if the body is absent" do
@response.stubs(:body).returns nil
lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (header)")
end
describe "and with http compression" do
it "should uncompress the body" do
@response.stubs(:body).returns("compressed body")
@searcher.expects(:uncompress_body).with(@response).returns("uncompressed")
lambda { @searcher.deserialize(@response) }.should raise_error { |e| e.message =~ /uncompressed/ }
end
end
end
}
it "should return the results of converting from the format specified by the content-type header if the response code is in the 200s" do
@model.expects(:convert_from).with("myformat", "mydata").returns "myobject"
response = mock 'response'
response.stubs(:[]).with("content-type").returns "myformat"
response.stubs(:[]).with("content-encoding").returns nil
response.stubs(:body).returns "mydata"
response.stubs(:code).returns "200"
@searcher.deserialize(response).should == "myobject"
end
it "should convert and return multiple instances if the return code is in the 200s and 'multiple' is specified" do
@model.expects(:convert_from_multiple).with("myformat", "mydata").returns "myobjects"
response = mock 'response'
response.stubs(:[]).with("content-type").returns "myformat"
response.stubs(:[]).with("content-encoding").returns nil
response.stubs(:body).returns "mydata"
response.stubs(:code).returns "200"
@searcher.deserialize(response, true).should == "myobjects"
end
it "should strip the content-type header to keep only the mime-type" do
@model.expects(:convert_from).with("text/plain", "mydata").returns "myobject"
response = mock 'response'
response.stubs(:[]).with("content-type").returns "text/plain; charset=utf-8"
response.stubs(:[]).with("content-encoding").returns nil
response.stubs(:body).returns "mydata"
response.stubs(:code).returns "200"
@searcher.deserialize(response)
end
it "should uncompress the body" do
@model.expects(:convert_from).with("myformat", "uncompressed mydata").returns "myobject"
response = mock 'response'
response.stubs(:[]).with("content-type").returns "myformat"
response.stubs(:body).returns "compressed mydata"
response.stubs(:code).returns "200"
@searcher.expects(:uncompress_body).with(response).returns("uncompressed mydata")
@searcher.deserialize(response).should == "myobject"
end
end
describe "when creating an HTTP client" do
before do
Puppet.settings.stubs(:value).returns("rest_testing")
end
it "should use the class's server and port if the indirection request provides neither" do
@request = stub 'request', :key => "foo", :server => nil, :port => nil
@searcher.class.expects(:port).returns 321
@searcher.class.expects(:server).returns "myserver"
Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn"
@searcher.network(@request).should == "myconn"
end
it "should use the server from the indirection request if one is present" do
@request = stub 'request', :key => "foo", :server => "myserver", :port => nil
@searcher.class.stubs(:port).returns 321
Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn"
@searcher.network(@request).should == "myconn"
end
it "should use the port from the indirection request if one is present" do
@request = stub 'request', :key => "foo", :server => nil, :port => 321
@searcher.class.stubs(:server).returns "myserver"
Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn"
@searcher.network(@request).should == "myconn"
end
end
describe "when doing a find" do
before :each do
@connection = stub('mock http connection', :get => @response)
@searcher.stubs(:network).returns(@connection) # neuter the network connection
# Use a key with spaces, so we can test escaping
@request = Puppet::Indirector::Request.new(:foo, :find, "foo bar")
end
it "should call the GET http method on a network connection" do
@searcher.expects(:network).returns @connection
@connection.expects(:get).returns @response
@searcher.find(@request)
end
it "should deserialize and return the http response, setting name" do
@connection.expects(:get).returns @response
instance = stub 'object'
instance.expects(:name=)
@searcher.expects(:deserialize).with(@response).returns instance
@searcher.find(@request).should == instance
end
it "should deserialize and return the http response, and not require name=" do
@connection.expects(:get).returns @response
instance = stub 'object'
@searcher.expects(:deserialize).with(@response).returns instance
@searcher.find(@request).should == instance
end
it "should use the URI generated by the Handler module" do
@searcher.expects(:indirection2uri).with(@request).returns "/my/uri"
@connection.expects(:get).with { |path, args| path == "/my/uri" }.returns(@response)
@searcher.find(@request)
end
it "should provide an Accept header containing the list of supported formats joined with commas" do
@connection.expects(:get).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response)
@searcher.model.expects(:supported_formats).returns %w{supported formats}
@searcher.find(@request)
end
it "should add Accept-Encoding header" do
@searcher.expects(:add_accept_encoding).returns({"accept-encoding" => "gzip"})
@connection.expects(:get).with { |path, args| args["accept-encoding"] == "gzip" }.returns(@response)
@searcher.find(@request)
end
it "should deserialize and return the network response" do
@searcher.expects(:deserialize).with(@response).returns @instance
@searcher.find(@request).should equal(@instance)
end
it "should set the name of the resulting instance to the asked-for name" do
@searcher.expects(:deserialize).with(@response).returns @instance
@instance.expects(:name=).with "foo bar"
@searcher.find(@request)
end
it "should generate an error when result data deserializes fails" do
@searcher.expects(:deserialize).raises(ArgumentError)
lambda { @searcher.find(@request) }.should raise_error(ArgumentError)
end
end
describe "when doing a search" do
before :each do
@connection = stub('mock http connection', :get => @response)
@searcher.stubs(:network).returns(@connection) # neuter the network connection
@model.stubs(:convert_from_multiple)
@request = Puppet::Indirector::Request.new(:foo, :search, "foo bar")
end
it "should call the GET http method on a network connection" do
@searcher.expects(:network).returns @connection
@connection.expects(:get).returns @response
@searcher.search(@request)
end
it "should deserialize as multiple instances and return the http response" do
@connection.expects(:get).returns @response
@searcher.expects(:deserialize).with(@response, true).returns "myobject"
@searcher.search(@request).should == 'myobject'
end
it "should use the URI generated by the Handler module" do
@searcher.expects(:indirection2uri).with(@request).returns "/mys/uri"
@connection.expects(:get).with { |path, args| path == "/mys/uri" }.returns(@response)
@searcher.search(@request)
end
it "should provide an Accept header containing the list of supported formats joined with commas" do
@connection.expects(:get).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response)
@searcher.model.expects(:supported_formats).returns %w{supported formats}
@searcher.search(@request)
end
it "should return an empty array if serialization returns nil" do
@model.stubs(:convert_from_multiple).returns nil
@searcher.search(@request).should == []
end
it "should generate an error when result data deserializes fails" do
@searcher.expects(:deserialize).raises(ArgumentError)
lambda { @searcher.search(@request) }.should raise_error(ArgumentError)
end
end
describe "when doing a destroy" do
before :each do
@connection = stub('mock http connection', :delete => @response)
@searcher.stubs(:network).returns(@connection) # neuter the network connection
@request = Puppet::Indirector::Request.new(:foo, :destroy, "foo bar")
end
it "should call the DELETE http method on a network connection" do
@searcher.expects(:network).returns @connection
@connection.expects(:delete).returns @response
@searcher.destroy(@request)
end
it "should fail if any options are provided, since DELETE apparently does not support query options" do
@request.stubs(:options).returns(:one => "two", :three => "four")
lambda { @searcher.destroy(@request) }.should raise_error(ArgumentError)
end
it "should deserialize and return the http response" do
@connection.expects(:delete).returns @response
@searcher.expects(:deserialize).with(@response).returns "myobject"
@searcher.destroy(@request).should == 'myobject'
end
it "should use the URI generated by the Handler module" do
@searcher.expects(:indirection2uri).with(@request).returns "/my/uri"
@connection.expects(:delete).with { |path, args| path == "/my/uri" }.returns(@response)
@searcher.destroy(@request)
end
it "should not include the query string" do
@connection.stubs(:delete).returns @response
@searcher.destroy(@request)
end
it "should provide an Accept header containing the list of supported formats joined with commas" do
@connection.expects(:delete).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response)
@searcher.model.expects(:supported_formats).returns %w{supported formats}
@searcher.destroy(@request)
end
it "should deserialize and return the network response" do
@searcher.expects(:deserialize).with(@response).returns @instance
@searcher.destroy(@request).should equal(@instance)
end
it "should generate an error when result data deserializes fails" do
@searcher.expects(:deserialize).raises(ArgumentError)
lambda { @searcher.destroy(@request) }.should raise_error(ArgumentError)
end
end
describe "when doing a save" do
before :each do
@connection = stub('mock http connection', :put => @response)
@searcher.stubs(:network).returns(@connection) # neuter the network connection
@instance = stub 'instance', :render => "mydata", :mime => "mime"
@request = Puppet::Indirector::Request.new(:foo, :save, "foo bar")
@request.instance = @instance
end
it "should call the PUT http method on a network connection" do
@searcher.expects(:network).returns @connection
@connection.expects(:put).returns @response
@searcher.save(@request)
end
it "should fail if any options are provided, since DELETE apparently does not support query options" do
@request.stubs(:options).returns(:one => "two", :three => "four")
lambda { @searcher.save(@request) }.should raise_error(ArgumentError)
end
it "should use the URI generated by the Handler module" do
@searcher.expects(:indirection2uri).with(@request).returns "/my/uri"
@connection.expects(:put).with { |path, args| path == "/my/uri" }.returns(@response)
@searcher.save(@request)
end
it "should serialize the instance using the default format and pass the result as the body of the request" do
@instance.expects(:render).returns "serial_instance"
@connection.expects(:put).with { |path, data, args| data == "serial_instance" }.returns @response
@searcher.save(@request)
end
it "should deserialize and return the http response" do
@connection.expects(:put).returns @response
@searcher.expects(:deserialize).with(@response).returns "myobject"
@searcher.save(@request).should == 'myobject'
end
it "should provide an Accept header containing the list of supported formats joined with commas" do
@connection.expects(:put).with { |path, data, args| args["Accept"] == "supported, formats" }.returns(@response)
@searcher.model.expects(:supported_formats).returns %w{supported formats}
@searcher.save(@request)
end
it "should provide a Content-Type header containing the mime-type of the sent object" do
@connection.expects(:put).with { |path, data, args| args['Content-Type'] == "mime" }.returns(@response)
@instance.expects(:mime).returns "mime"
@searcher.save(@request)
end
it "should deserialize and return the network response" do
@searcher.expects(:deserialize).with(@response).returns @instance
@searcher.save(@request).should equal(@instance)
end
it "should generate an error when result data deserializes fails" do
@searcher.expects(:deserialize).raises(ArgumentError)
lambda { @searcher.save(@request) }.should raise_error(ArgumentError)
end
end
end
diff --git a/spec/unit/indirector_spec.rb b/spec/unit/indirector_spec.rb
index fb21532ba..acbfc1726 100755
--- a/spec/unit/indirector_spec.rb
+++ b/spec/unit/indirector_spec.rb
@@ -1,157 +1,157 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet/defaults'
require 'puppet/indirector'
describe Puppet::Indirector, " when available to a model" do
before do
@thingie = Class.new do
extend Puppet::Indirector
end
end
it "should provide a way for the model to register an indirection under a name" do
@thingie.should respond_to(:indirects)
end
end
describe Puppet::Indirector, "when registering an indirection" do
before do
@thingie = Class.new do
extend Puppet::Indirector
attr_reader :name
def initialize(name)
@name = name
end
end
end
it "should require a name when registering a model" do
Proc.new {@thingie.send(:indirects) }.should raise_error(ArgumentError)
end
it "should create an indirection instance to manage each indirecting model" do
@indirection = @thingie.indirects(:test)
@indirection.should be_instance_of(Puppet::Indirector::Indirection)
end
it "should not allow a model to register under multiple names" do
# Keep track of the indirection instance so we can delete it on cleanup
@indirection = @thingie.indirects :first
Proc.new { @thingie.indirects :second }.should raise_error(ArgumentError)
end
it "should make the indirection available via an accessor" do
@indirection = @thingie.indirects :first
@thingie.indirection.should equal(@indirection)
end
it "should pass any provided options to the indirection during initialization" do
klass = mock 'terminus class'
Puppet::Indirector::Indirection.expects(:new).with(@thingie, :first, {:some => :options})
@indirection = @thingie.indirects :first, :some => :options
end
it "should extend the class with the Format Handler" do
@indirection = @thingie.indirects :first
@thingie.singleton_class.ancestors.should be_include(Puppet::Network::FormatHandler)
end
after do
@indirection.delete if @indirection
end
end
-describe "Delegated Indirection Method", :shared => true do
+shared_examples_for "Delegated Indirection Method" do
it "should delegate to the indirection" do
@indirection.expects(@method)
@thingie.send(@method, "me")
end
it "should pass all of the passed arguments directly to the indirection instance" do
@indirection.expects(@method).with("me", :one => :two)
@thingie.send(@method, "me", :one => :two)
end
it "should return the results of the delegation as its result" do
request = mock 'request'
@indirection.expects(@method).returns "yay"
@thingie.send(@method, "me").should == "yay"
end
end
describe Puppet::Indirector, "when redirecting a model" do
before do
@thingie = Class.new do
extend Puppet::Indirector
attr_reader :name
def initialize(name)
@name = name
end
end
@indirection = @thingie.send(:indirects, :test)
end
it "should include the Envelope module in the model" do
@thingie.ancestors.should be_include(Puppet::Indirector::Envelope)
end
describe "when finding instances via the model" do
before { @method = :find }
it_should_behave_like "Delegated Indirection Method"
end
describe "when destroying instances via the model" do
before { @method = :destroy }
it_should_behave_like "Delegated Indirection Method"
end
describe "when searching for instances via the model" do
before { @method = :search }
it_should_behave_like "Delegated Indirection Method"
end
describe "when expiring instances via the model" do
before { @method = :expire }
it_should_behave_like "Delegated Indirection Method"
end
# This is an instance method, so it behaves a bit differently.
describe "when saving instances via the model" do
before do
@instance = @thingie.new("me")
end
it "should delegate to the indirection" do
@indirection.expects(:save)
@instance.save
end
it "should pass the instance and an optional key to the indirection's :save method" do
@indirection.expects(:save).with("key", @instance)
@instance.save "key"
end
it "should return the results of the delegation as its result" do
request = mock 'request'
@indirection.expects(:save).returns "yay"
@instance.save.should == "yay"
end
end
it "should give the model the ability to set the indirection terminus class" do
@indirection.expects(:terminus_class=).with(:myterm)
@thingie.terminus_class = :myterm
end
it "should give the model the ability to set the indirection cache class" do
@indirection.expects(:cache_class=).with(:mycache)
@thingie.cache_class = :mycache
end
after do
@indirection.delete
end
end
diff --git a/spec/unit/module_spec.rb b/spec/unit/module_spec.rb
index c3436dfdd..37dad7e25 100755
--- a/spec/unit/module_spec.rb
+++ b/spec/unit/module_spec.rb
@@ -1,547 +1,545 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
describe Puppet::Module do
before do
# This is necessary because of the extra checks we have for the deprecated
# 'plugins' directory
FileTest.stubs(:exist?).returns false
end
it "should have a class method that returns a named module from a given environment" do
env = mock 'module'
env.expects(:module).with("mymod").returns "yep"
Puppet::Node::Environment.expects(:new).with("myenv").returns env
Puppet::Module.find("mymod", "myenv").should == "yep"
end
it "should return nil if asked for a named module that doesn't exist" do
env = mock 'module'
env.expects(:module).with("mymod").returns nil
Puppet::Node::Environment.expects(:new).with("myenv").returns env
Puppet::Module.find("mymod", "myenv").should be_nil
end
it "should support a 'version' attribute" do
mod = Puppet::Module.new("mymod")
mod.version = 1.09
mod.version.should == 1.09
end
it "should support a 'source' attribute" do
mod = Puppet::Module.new("mymod")
mod.source = "http://foo/bar"
mod.source.should == "http://foo/bar"
end
it "should support a 'project_page' attribute" do
mod = Puppet::Module.new("mymod")
mod.project_page = "http://foo/bar"
mod.project_page.should == "http://foo/bar"
end
it "should support an 'author' attribute" do
mod = Puppet::Module.new("mymod")
mod.author = "Luke Kanies "
mod.author.should == "Luke Kanies "
end
it "should support a 'license' attribute" do
mod = Puppet::Module.new("mymod")
mod.license = "GPL2"
mod.license.should == "GPL2"
end
it "should support a 'summary' attribute" do
mod = Puppet::Module.new("mymod")
mod.summary = "GPL2"
mod.summary.should == "GPL2"
end
it "should support a 'description' attribute" do
mod = Puppet::Module.new("mymod")
mod.description = "GPL2"
mod.description.should == "GPL2"
end
it "should support specifying a compatible puppet version" do
mod = Puppet::Module.new("mymod")
mod.puppetversion = "0.25"
mod.puppetversion.should == "0.25"
end
it "should validate that the puppet version is compatible" do
mod = Puppet::Module.new("mymod")
mod.puppetversion = "0.25"
Puppet.expects(:version).returns "0.25"
mod.validate_puppet_version
end
it "should fail if the specified puppet version is not compatible" do
mod = Puppet::Module.new("mymod")
mod.puppetversion = "0.25"
Puppet.stubs(:version).returns "0.24"
lambda { mod.validate_puppet_version }.should raise_error(Puppet::Module::IncompatibleModule)
end
describe "when specifying required modules" do
it "should support specifying a required module" do
mod = Puppet::Module.new("mymod")
mod.requires "foobar"
end
it "should support specifying multiple required modules" do
mod = Puppet::Module.new("mymod")
mod.requires "foobar"
mod.requires "baz"
end
it "should support specifying a required module and version" do
mod = Puppet::Module.new("mymod")
mod.requires "foobar", 1.0
end
it "should fail when required modules are missing" do
mod = Puppet::Module.new("mymod")
mod.requires "foobar"
mod.environment.expects(:module).with("foobar").returns nil
lambda { mod.validate_dependencies }.should raise_error(Puppet::Module::MissingModule)
end
it "should fail when required modules are present but of the wrong version" do
mod = Puppet::Module.new("mymod")
mod.requires "foobar", 1.0
foobar = Puppet::Module.new("foobar")
foobar.version = 2.0
mod.environment.expects(:module).with("foobar").returns foobar
lambda { mod.validate_dependencies }.should raise_error(Puppet::Module::IncompatibleModule)
end
it "should have valid dependencies when no dependencies have been specified" do
mod = Puppet::Module.new("mymod")
lambda { mod.validate_dependencies }.should_not raise_error
end
it "should fail when some dependencies are present but others aren't" do
mod = Puppet::Module.new("mymod")
mod.requires "foobar"
mod.requires "baz"
mod.environment.expects(:module).with("foobar").returns Puppet::Module.new("foobar")
mod.environment.expects(:module).with("baz").returns nil
lambda { mod.validate_dependencies }.should raise_error(Puppet::Module::MissingModule)
end
it "should have valid dependencies when all dependencies are met" do
mod = Puppet::Module.new("mymod")
mod.requires "foobar", 1.0
mod.requires "baz"
foobar = Puppet::Module.new("foobar")
foobar.version = 1.0
baz = Puppet::Module.new("baz")
mod.environment.expects(:module).with("foobar").returns foobar
mod.environment.expects(:module).with("baz").returns baz
lambda { mod.validate_dependencies }.should_not raise_error
end
it "should validate its dependendencies on initialization" do
Puppet::Module.any_instance.expects(:validate_dependencies)
Puppet::Module.new("mymod")
end
end
describe "when managing supported platforms" do
it "should support specifying a supported platform" do
mod = Puppet::Module.new("mymod")
mod.supports "solaris"
end
it "should support specifying a supported platform and version" do
mod = Puppet::Module.new("mymod")
mod.supports "solaris", 1.0
end
it "should fail when not running on a supported platform" do
pending "Not sure how to send client platform to the module"
mod = Puppet::Module.new("mymod")
Facter.expects(:value).with("operatingsystem").returns "Solaris"
mod.supports "hpux"
lambda { mod.validate_supported_platform }.should raise_error(Puppet::Module::UnsupportedPlatform)
end
it "should fail when supported platforms are present but of the wrong version" do
pending "Not sure how to send client platform to the module"
mod = Puppet::Module.new("mymod")
Facter.expects(:value).with("operatingsystem").returns "Solaris"
Facter.expects(:value).with("operatingsystemrelease").returns 2.0
mod.supports "Solaris", 1.0
lambda { mod.validate_supported_platform }.should raise_error(Puppet::Module::IncompatiblePlatform)
end
it "should be considered supported when no supported platforms have been specified" do
pending "Not sure how to send client platform to the module"
mod = Puppet::Module.new("mymod")
lambda { mod.validate_supported_platform }.should_not raise_error
end
it "should be considered supported when running on a supported platform" do
pending "Not sure how to send client platform to the module"
mod = Puppet::Module.new("mymod")
Facter.expects(:value).with("operatingsystem").returns "Solaris"
Facter.expects(:value).with("operatingsystemrelease").returns 2.0
mod.supports "Solaris", 1.0
lambda { mod.validate_supported_platform }.should raise_error(Puppet::Module::IncompatiblePlatform)
end
it "should be considered supported when running on any of multiple supported platforms" do
pending "Not sure how to send client platform to the module"
end
it "should validate its platform support on initialization" do
pending "Not sure how to send client platform to the module"
end
end
it "should return nil if asked for a module whose name is 'nil'" do
Puppet::Module.find(nil, "myenv").should be_nil
end
it "should provide support for logging" do
Puppet::Module.ancestors.should be_include(Puppet::Util::Logging)
end
it "should be able to be converted to a string" do
Puppet::Module.new("foo").to_s.should == "Module foo"
end
it "should add the path to its string form if the module is found" do
mod = Puppet::Module.new("foo")
mod.stubs(:path).returns "/a"
mod.to_s.should == "Module foo(/a)"
end
it "should fail if its name is not alphanumeric" do
lambda { Puppet::Module.new(".something") }.should raise_error(Puppet::Module::InvalidName)
end
it "should require a name at initialization" do
lambda { Puppet::Module.new }.should raise_error(ArgumentError)
end
it "should convert an environment name into an Environment instance" do
Puppet::Module.new("foo", "prod").environment.should be_instance_of(Puppet::Node::Environment)
end
it "should accept an environment at initialization" do
Puppet::Module.new("foo", :prod).environment.name.should == :prod
end
it "should use the default environment if none is provided" do
env = Puppet::Node::Environment.new
Puppet::Module.new("foo").environment.should equal(env)
end
it "should use any provided Environment instance" do
env = Puppet::Node::Environment.new
Puppet::Module.new("foo", env).environment.should equal(env)
end
it "should return the path to the first found instance in its environment's module paths as its path" do
mod = Puppet::Module.new("foo")
env = mock 'environment'
mod.stubs(:environment).returns env
env.expects(:modulepath).returns %w{/a /b /c}
FileTest.expects(:exist?).with("/a/foo").returns false
FileTest.expects(:exist?).with("/b/foo").returns true
FileTest.expects(:exist?).with("/c/foo").never
mod.path.should == "/b/foo"
end
it "should be considered existent if it exists in at least one module path" do
mod = Puppet::Module.new("foo")
mod.expects(:path).returns "/a/foo"
mod.should be_exist
end
it "should be considered nonexistent if it does not exist in any of the module paths" do
mod = Puppet::Module.new("foo")
mod.expects(:path).returns nil
mod.should_not be_exist
end
[:plugins, :templates, :files, :manifests].each do |filetype|
dirname = filetype == :plugins ? "lib" : filetype.to_s
it "should be able to return individual #{filetype}" do
mod = Puppet::Module.new("foo")
mod.stubs(:path).returns "/a/foo"
path = File.join("/a/foo", dirname, "my/file")
FileTest.expects(:exist?).with(path).returns true
mod.send(filetype.to_s.sub(/s$/, ''), "my/file").should == path
end
it "should consider #{filetype} to be present if their base directory exists" do
mod = Puppet::Module.new("foo")
mod.stubs(:path).returns "/a/foo"
path = File.join("/a/foo", dirname)
FileTest.expects(:exist?).with(path).returns true
mod.send(filetype.to_s + "?").should be_true
end
it "should consider #{filetype} to be absent if their base directory does not exist" do
mod = Puppet::Module.new("foo")
mod.stubs(:path).returns "/a/foo"
path = File.join("/a/foo", dirname)
FileTest.expects(:exist?).with(path).returns false
mod.send(filetype.to_s + "?").should be_false
end
it "should consider #{filetype} to be absent if the module base directory does not exist" do
mod = Puppet::Module.new("foo")
mod.stubs(:path).returns nil
mod.send(filetype.to_s + "?").should be_false
end
it "should return nil if asked to return individual #{filetype} that don't exist" do
mod = Puppet::Module.new("foo")
mod.stubs(:path).returns "/a/foo"
path = File.join("/a/foo", dirname, "my/file")
FileTest.expects(:exist?).with(path).returns false
mod.send(filetype.to_s.sub(/s$/, ''), "my/file").should be_nil
end
it "should return nil when asked for individual #{filetype} if the module does not exist" do
mod = Puppet::Module.new("foo")
mod.stubs(:path).returns nil
mod.send(filetype.to_s.sub(/s$/, ''), "my/file").should be_nil
end
it "should return the base directory if asked for a nil path" do
mod = Puppet::Module.new("foo")
mod.stubs(:path).returns "/a/foo"
base = File.join("/a/foo", dirname)
FileTest.expects(:exist?).with(base).returns true
mod.send(filetype.to_s.sub(/s$/, ''), nil).should == base
end
end
%w{plugins files}.each do |filetype|
short = filetype.sub(/s$/, '')
dirname = filetype == "plugins" ? "lib" : filetype.to_s
it "should be able to return the #{short} directory" do
Puppet::Module.new("foo").should respond_to(short + "_directory")
end
it "should return the path to the #{short} directory" do
mod = Puppet::Module.new("foo")
mod.stubs(:path).returns "/a/foo"
mod.send(short + "_directory").should == "/a/foo/#{dirname}"
end
end
it "should throw a warning if plugins are in a 'plugins' directory rather than a 'lib' directory" do
mod = Puppet::Module.new("foo")
mod.stubs(:path).returns "/a/foo"
FileTest.expects(:exist?).with("/a/foo/plugins").returns true
mod.expects(:warning)
mod.plugin_directory.should == "/a/foo/plugins"
end
it "should default to 'lib' for the plugins directory" do
mod = Puppet::Module.new("foo")
mod.stubs(:path).returns "/a/foo"
mod.plugin_directory.should == "/a/foo/lib"
end
end
describe Puppet::Module, " when building its search path" do
it "should use the current environment's search path if no environment is specified" do
env = mock 'env'
env.expects(:modulepath).returns "eh"
Puppet::Node::Environment.expects(:new).with(nil).returns env
Puppet::Module.modulepath.should == "eh"
end
it "should use the specified environment's search path if an environment is specified" do
env = mock 'env'
env.expects(:modulepath).returns "eh"
Puppet::Node::Environment.expects(:new).with("foo").returns env
Puppet::Module.modulepath("foo").should == "eh"
end
end
describe Puppet::Module, "when finding matching manifests" do
before do
@mod = Puppet::Module.new("mymod")
@mod.stubs(:path).returns "/a"
@pq_glob_with_extension = "yay/*.xx"
@fq_glob_with_extension = "/a/manifests/#{@pq_glob_with_extension}"
end
it "should return all manifests matching the glob pattern" do
Dir.expects(:glob).with(@fq_glob_with_extension).returns(%w{foo bar})
FileTest.stubs(:directory?).returns false
@mod.match_manifests(@pq_glob_with_extension).should == %w{foo bar}
end
it "should not return directories" do
Dir.expects(:glob).with(@fq_glob_with_extension).returns(%w{foo bar})
FileTest.expects(:directory?).with("foo").returns false
FileTest.expects(:directory?).with("bar").returns true
@mod.match_manifests(@pq_glob_with_extension).should == %w{foo}
end
it "should default to the 'init' file if no glob pattern is specified" do
Dir.expects(:glob).with("/a/manifests/init.{pp,rb}").returns(%w{/a/manifests/init.pp})
@mod.match_manifests(nil).should == %w{/a/manifests/init.pp}
end
it "should return all manifests matching the glob pattern in all existing paths" do
Dir.expects(:glob).with(@fq_glob_with_extension).returns(%w{a b})
@mod.match_manifests(@pq_glob_with_extension).should == %w{a b}
end
it "should match the glob pattern plus '.{pp,rb}' if no extention is specified" do
Dir.expects(:glob).with("/a/manifests/yay/foo.{pp,rb}").returns(%w{yay})
@mod.match_manifests("yay/foo").should == %w{yay}
end
it "should return an empty array if no manifests matched" do
Dir.expects(:glob).with(@fq_glob_with_extension).returns([])
@mod.match_manifests(@pq_glob_with_extension).should == []
end
end
describe Puppet::Module do
before do
Puppet::Module.any_instance.stubs(:path).returns "/my/mod/path"
@module = Puppet::Module.new("foo")
end
it "should use 'License' in its current path as its metadata file" do
@module.license_file.should == "/my/mod/path/License"
end
it "should return nil as its license file when the module has no path" do
Puppet::Module.any_instance.stubs(:path).returns nil
Puppet::Module.new("foo").license_file.should be_nil
end
it "should cache the license file" do
Puppet::Module.any_instance.expects(:path).once.returns nil
mod = Puppet::Module.new("foo")
mod.license_file.should == mod.license_file
end
it "should use 'metadata.json' in its current path as its metadata file" do
@module.metadata_file.should == "/my/mod/path/metadata.json"
end
it "should return nil as its metadata file when the module has no path" do
Puppet::Module.any_instance.stubs(:path).returns nil
Puppet::Module.new("foo").metadata_file.should be_nil
end
it "should cache the metadata file" do
Puppet::Module.any_instance.expects(:path).once.returns nil
mod = Puppet::Module.new("foo")
mod.metadata_file.should == mod.metadata_file
end
it "should know if it has a metadata file" do
FileTest.expects(:exist?).with(@module.metadata_file).returns true
@module.should be_has_metadata
end
it "should know if it is missing a metadata file" do
FileTest.expects(:exist?).with(@module.metadata_file).returns false
@module.should_not be_has_metadata
end
it "should be able to parse its metadata file" do
@module.should respond_to(:load_metadata)
end
it "should parse its metadata file on initialization if it is present" do
Puppet::Module.any_instance.expects(:has_metadata?).returns true
Puppet::Module.any_instance.expects(:load_metadata)
Puppet::Module.new("yay")
end
- describe "when loading the medatada file" do
- confine "Cannot test module metadata without json" => Puppet.features.json?
-
+ describe "when loading the medatada file", :if => Puppet.features.json? do
before do
@data = {
:license => "GPL2",
:author => "luke",
:version => "1.0",
:source => "http://foo/",
:puppetversion => "0.25"
}
@text = @data.to_json
@module = Puppet::Module.new("foo")
@module.stubs(:metadata_file).returns "/my/file"
File.stubs(:read).with("/my/file").returns @text
end
%w{source author version license}.each do |attr|
it "should set #{attr} if present in the metadata file" do
@module.load_metadata
@module.send(attr).should == @data[attr.to_sym]
end
it "should fail if #{attr} is not present in the metadata file" do
@data.delete(attr.to_sym)
@text = @data.to_json
File.stubs(:read).with("/my/file").returns @text
lambda { @module.load_metadata }.should raise_error(Puppet::Module::MissingMetadata)
end
end
it "should set puppetversion if present in the metadata file" do
@module.load_metadata
@module.puppetversion.should == @data[:puppetversion]
end
it "should fail if the discovered name is different than the metadata name"
end
end
diff --git a/spec/unit/network/formats_spec.rb b/spec/unit/network/formats_spec.rb
index 7c8e7b1f4..2c58a0534 100755
--- a/spec/unit/network/formats_spec.rb
+++ b/spec/unit/network/formats_spec.rb
@@ -1,337 +1,334 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/network/formats'
class PsonTest
attr_accessor :string
def ==(other)
string == other.string
end
def self.from_pson(data)
new(data)
end
def initialize(string)
@string = string
end
def to_pson(*args)
{
'type' => self.class.name,
'data' => @string
}.to_pson(*args)
end
end
describe "Puppet Network Format" do
it "should include a yaml format" do
Puppet::Network::FormatHandler.format(:yaml).should_not be_nil
end
describe "yaml" do
before do
@yaml = Puppet::Network::FormatHandler.format(:yaml)
end
it "should have its mime type set to text/yaml" do
@yaml.mime.should == "text/yaml"
end
it "should be supported on Strings" do
@yaml.should be_supported(String)
end
it "should render by calling 'to_yaml' on the instance" do
instance = mock 'instance'
instance.expects(:to_yaml).returns "foo"
@yaml.render(instance).should == "foo"
end
it "should render multiple instances by calling 'to_yaml' on the array" do
instances = [mock('instance')]
instances.expects(:to_yaml).returns "foo"
@yaml.render_multiple(instances).should == "foo"
end
it "should intern by calling 'YAML.load'" do
text = "foo"
YAML.expects(:load).with("foo").returns "bar"
@yaml.intern(String, text).should == "bar"
end
it "should intern multiples by calling 'YAML.load'" do
text = "foo"
YAML.expects(:load).with("foo").returns "bar"
@yaml.intern_multiple(String, text).should == "bar"
end
end
- describe "base64 compressed yaml" do
+ describe "base64 compressed yaml", :if => Puppet.features.zlib? do
yaml = Puppet::Network::FormatHandler.format(:b64_zlib_yaml)
- confine "We must have zlib" => Puppet.features.zlib?
before do
@yaml = Puppet::Network::FormatHandler.format(:b64_zlib_yaml)
end
it "should have its mime type set to text/b64_zlib_yaml" do
@yaml.mime.should == "text/b64_zlib_yaml"
end
it "should render by calling 'to_yaml' on the instance" do
instance = mock 'instance'
instance.expects(:to_yaml).returns "foo"
@yaml.render(instance)
end
it "should encode generated yaml on render" do
instance = mock 'instance', :to_yaml => "foo"
@yaml.expects(:encode).with("foo").returns "bar"
@yaml.render(instance).should == "bar"
end
it "should render multiple instances by calling 'to_yaml' on the array" do
instances = [mock('instance')]
instances.expects(:to_yaml).returns "foo"
@yaml.render_multiple(instances)
end
it "should encode generated yaml on render" do
instances = [mock('instance')]
instances.stubs(:to_yaml).returns "foo"
@yaml.expects(:encode).with("foo").returns "bar"
@yaml.render(instances).should == "bar"
end
it "should intern by calling decode" do
text = "foo"
@yaml.expects(:decode).with("foo").returns "bar"
@yaml.intern(String, text).should == "bar"
end
it "should intern multiples by calling 'decode'" do
text = "foo"
@yaml.expects(:decode).with("foo").returns "bar"
@yaml.intern_multiple(String, text).should == "bar"
end
it "should decode by base64 decoding, uncompressing and Yaml loading" do
Base64.expects(:decode64).with("zorg").returns "foo"
Zlib::Inflate.expects(:inflate).with("foo").returns "baz"
YAML.expects(:load).with("baz").returns "bar"
@yaml.decode("zorg").should == "bar"
end
it "should encode by compressing and base64 encoding" do
Zlib::Deflate.expects(:deflate).with("foo", Zlib::BEST_COMPRESSION).returns "bar"
Base64.expects(:encode64).with("bar").returns "baz"
@yaml.encode("foo").should == "baz"
end
describe "when zlib is disabled" do
before do
Puppet[:zlib] = false
end
it "use_zlib? should return false" do
@yaml.use_zlib?.should == false
end
it "should refuse to encode" do
lambda{ @yaml.encode("foo") }.should raise_error
end
it "should refuse to decode" do
lambda{ @yaml.decode("foo") }.should raise_error
end
end
describe "when zlib is not installed" do
it "use_zlib? should return false" do
Puppet[:zlib] = true
Puppet.features.expects(:zlib?).returns(false)
@yaml.use_zlib?.should == false
end
end
end
it "should include a marshal format" do
Puppet::Network::FormatHandler.format(:marshal).should_not be_nil
end
describe "marshal" do
before do
@marshal = Puppet::Network::FormatHandler.format(:marshal)
end
it "should have its mime type set to text/marshal" do
Puppet::Network::FormatHandler.format(:marshal).mime.should == "text/marshal"
end
it "should be supported on Strings" do
@marshal.should be_supported(String)
end
it "should render by calling 'Marshal.dump' on the instance" do
instance = mock 'instance'
Marshal.expects(:dump).with(instance).returns "foo"
@marshal.render(instance).should == "foo"
end
it "should render multiple instances by calling 'to_marshal' on the array" do
instances = [mock('instance')]
Marshal.expects(:dump).with(instances).returns "foo"
@marshal.render_multiple(instances).should == "foo"
end
it "should intern by calling 'Marshal.load'" do
text = "foo"
Marshal.expects(:load).with("foo").returns "bar"
@marshal.intern(String, text).should == "bar"
end
it "should intern multiples by calling 'Marshal.load'" do
text = "foo"
Marshal.expects(:load).with("foo").returns "bar"
@marshal.intern_multiple(String, text).should == "bar"
end
end
describe "plaintext" do
before do
@text = Puppet::Network::FormatHandler.format(:s)
end
it "should have its mimetype set to text/plain" do
@text.mime.should == "text/plain"
end
it "should use 'txt' as its extension" do
@text.extension.should == "txt"
end
end
describe "dot" do
before do
@dot = Puppet::Network::FormatHandler.format(:dot)
end
it "should have its mimetype set to text/dot" do
@dot.mime.should == "text/dot"
end
end
describe Puppet::Network::FormatHandler.format(:raw) do
before do
@format = Puppet::Network::FormatHandler.format(:raw)
end
it "should exist" do
@format.should_not be_nil
end
it "should have its mimetype set to application/x-raw" do
@format.mime.should == "application/x-raw"
end
it "should always be supported" do
@format.should be_supported(String)
end
it "should fail if its multiple_render method is used" do
lambda { @format.render_multiple("foo") }.should raise_error(NotImplementedError)
end
it "should fail if its multiple_intern method is used" do
lambda { @format.intern_multiple(String, "foo") }.should raise_error(NotImplementedError)
end
it "should have a weight of 1" do
@format.weight.should == 1
end
end
it "should include a pson format" do
Puppet::Network::FormatHandler.format(:pson).should_not be_nil
end
- describe "pson" do
- confine "Missing 'pson' library" => Puppet.features.pson?
-
+ describe "pson", :if => Puppet.features.pson? do
before do
@pson = Puppet::Network::FormatHandler.format(:pson)
end
it "should have its mime type set to text/pson" do
Puppet::Network::FormatHandler.format(:pson).mime.should == "text/pson"
end
it "should require the :render_method" do
Puppet::Network::FormatHandler.format(:pson).required_methods.should be_include(:render_method)
end
it "should require the :intern_method" do
Puppet::Network::FormatHandler.format(:pson).required_methods.should be_include(:intern_method)
end
it "should have a weight of 10" do
@pson.weight.should == 10
end
describe "when supported" do
it "should render by calling 'to_pson' on the instance" do
instance = PsonTest.new("foo")
instance.expects(:to_pson).returns "foo"
@pson.render(instance).should == "foo"
end
it "should render multiple instances by calling 'to_pson' on the array" do
instances = [mock('instance')]
instances.expects(:to_pson).returns "foo"
@pson.render_multiple(instances).should == "foo"
end
it "should intern by calling 'PSON.parse' on the text and then using from_pson to convert the data into an instance" do
text = "foo"
PSON.expects(:parse).with("foo").returns("type" => "PsonTest", "data" => "foo")
PsonTest.expects(:from_pson).with("foo").returns "parsed_pson"
@pson.intern(PsonTest, text).should == "parsed_pson"
end
it "should not render twice if 'PSON.parse' creates the appropriate instance" do
text = "foo"
instance = PsonTest.new("foo")
PSON.expects(:parse).with("foo").returns(instance)
PsonTest.expects(:from_pson).never
@pson.intern(PsonTest, text).should equal(instance)
end
it "should intern by calling 'PSON.parse' on the text and then using from_pson to convert the actual into an instance if the pson has no class/data separation" do
text = "foo"
PSON.expects(:parse).with("foo").returns("foo")
PsonTest.expects(:from_pson).with("foo").returns "parsed_pson"
@pson.intern(PsonTest, text).should == "parsed_pson"
end
it "should intern multiples by parsing the text and using 'class.intern' on each resulting data structure" do
text = "foo"
PSON.expects(:parse).with("foo").returns ["bar", "baz"]
PsonTest.expects(:from_pson).with("bar").returns "BAR"
PsonTest.expects(:from_pson).with("baz").returns "BAZ"
@pson.intern_multiple(PsonTest, text).should == %w{BAR BAZ}
end
end
end
end
diff --git a/spec/unit/network/http/compression_spec.rb b/spec/unit/network/http/compression_spec.rb
index b46941f46..c5bbbb064 100644
--- a/spec/unit/network/http/compression_spec.rb
+++ b/spec/unit/network/http/compression_spec.rb
@@ -1,199 +1,197 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../spec_helper'
describe "http compression" do
describe "when zlib is not available" do
before(:each) do
Puppet.features.stubs(:zlib?).returns false
require 'puppet/network/http/compression'
class HttpUncompressor
include Puppet::Network::HTTP::Compression::None
end
@uncompressor = HttpUncompressor.new
end
it "should have a module function that returns the None underlying module" do
Puppet::Network::HTTP::Compression.module.should == Puppet::Network::HTTP::Compression::None
end
it "should not add any Accept-Encoding header" do
@uncompressor.add_accept_encoding({}).should == {}
end
it "should not tamper the body" do
response = stub 'response', :body => "data"
@uncompressor.uncompress_body(response).should == "data"
end
it "should yield an identity uncompressor" do
response = stub 'response'
@uncompressor.uncompress(response) { |u|
u.should be_instance_of(Puppet::Network::HTTP::Compression::IdentityAdapter)
}
end
end
- describe "when zlib is available" do
- confine "Zlib is missing" => Puppet.features.zlib?
-
+ describe "when zlib is available", :if => Puppet.features.zlib? do
before(:each) do
Puppet.features.stubs(:zlib?).returns true
require 'puppet/network/http/compression'
class HttpUncompressor
include Puppet::Network::HTTP::Compression::Active
end
@uncompressor = HttpUncompressor.new
end
it "should have a module function that returns the Active underlying module" do
Puppet::Network::HTTP::Compression.module.should == Puppet::Network::HTTP::Compression::Active
end
it "should add an Accept-Encoding header when http compression is available" do
Puppet.settings.expects(:[]).with(:http_compression).returns(true)
headers = @uncompressor.add_accept_encoding({})
headers.should have_key('accept-encoding')
headers['accept-encoding'].should =~ /gzip/
headers['accept-encoding'].should =~ /deflate/
headers['accept-encoding'].should =~ /identity/
end
it "should not add Accept-Encoding header if http compression is not available" do
Puppet.settings.stubs(:[]).with(:http_compression).returns(false)
@uncompressor.add_accept_encoding({}).should == {}
end
describe "when uncompressing response body" do
before do
@response = stub 'response'
@response.stubs(:[]).with('content-encoding')
@response.stubs(:body).returns("mydata")
end
it "should return untransformed response body with no content-encoding" do
@uncompressor.uncompress_body(@response).should == "mydata"
end
it "should return untransformed response body with 'identity' content-encoding" do
@response.stubs(:[]).with('content-encoding').returns('identity')
@uncompressor.uncompress_body(@response).should == "mydata"
end
it "should use a Zlib inflater with 'deflate' content-encoding" do
@response.stubs(:[]).with('content-encoding').returns('deflate')
inflater = stub 'inflater'
Zlib::Inflate.expects(:new).returns(inflater)
inflater.expects(:inflate).with("mydata").returns "uncompresseddata"
@uncompressor.uncompress_body(@response).should == "uncompresseddata"
end
it "should use a GzipReader with 'gzip' content-encoding" do
@response.stubs(:[]).with('content-encoding').returns('gzip')
io = stub 'io'
StringIO.expects(:new).with("mydata").returns io
reader = stub 'gzip reader'
Zlib::GzipReader.expects(:new).with(io).returns(reader)
reader.expects(:read).returns "uncompresseddata"
@uncompressor.uncompress_body(@response).should == "uncompresseddata"
end
end
describe "when uncompressing by chunk" do
before do
@response = stub 'response'
@response.stubs(:[]).with('content-encoding')
@inflater = stub_everything 'inflater'
Zlib::Inflate.stubs(:new).returns(@inflater)
end
it "should yield an identity uncompressor with no content-encoding" do
@uncompressor.uncompress(@response) { |u|
u.should be_instance_of(Puppet::Network::HTTP::Compression::IdentityAdapter)
}
end
it "should yield an identity uncompressor with 'identity' content-encoding" do
@response.stubs(:[]).with('content-encoding').returns 'identity'
@uncompressor.uncompress(@response) { |u|
u.should be_instance_of(Puppet::Network::HTTP::Compression::IdentityAdapter)
}
end
%w{gzip deflate}.each do |c|
it "should yield a Zlib uncompressor with '#{c}' content-encoding" do
@response.stubs(:[]).with('content-encoding').returns c
@uncompressor.uncompress(@response) { |u|
u.should be_instance_of(Puppet::Network::HTTP::Compression::Active::ZlibAdapter)
}
end
end
it "should close the underlying adapter" do
adapter = stub_everything 'adapter'
Puppet::Network::HTTP::Compression::IdentityAdapter.expects(:new).returns(adapter)
adapter.expects(:close)
@uncompressor.uncompress(@response) { |u| }
end
end
describe "zlib adapter" do
before do
@inflater = stub_everything 'inflater'
Zlib::Inflate.stubs(:new).returns(@inflater)
@adapter = Puppet::Network::HTTP::Compression::Active::ZlibAdapter.new
end
it "should initialize the underlying inflater with gzip/zlib header parsing" do
Zlib::Inflate.expects(:new).with(15+32)
Puppet::Network::HTTP::Compression::Active::ZlibAdapter.new
end
it "should inflate the given chunk" do
@inflater.expects(:inflate).with("chunk")
@adapter.uncompress("chunk")
end
it "should return the inflated chunk" do
@inflater.stubs(:inflate).with("chunk").returns("uncompressed")
@adapter.uncompress("chunk").should == "uncompressed"
end
it "should try a 'regular' inflater on Zlib::DataError" do
@inflater.expects(:inflate).raises(Zlib::DataError.new("not a zlib stream"))
inflater = stub_everything 'inflater2'
inflater.expects(:inflate).with("chunk").returns("uncompressed")
Zlib::Inflate.expects(:new).with.returns(inflater)
@adapter.uncompress("chunk")
end
it "should raise the error the second time" do
@inflater.expects(:inflate).raises(Zlib::DataError.new("not a zlib stream"))
Zlib::Inflate.expects(:new).with.returns(@inflater)
lambda { @adapter.uncompress("chunk") }.should raise_error
end
it "should finish the stream on close" do
@inflater.expects(:finish)
@adapter.close
end
it "should close the stream on close" do
@inflater.expects(:close)
@adapter.close
end
end
end
end
diff --git a/spec/unit/network/http/mongrel/rest_spec.rb b/spec/unit/network/http/mongrel/rest_spec.rb
index 92a81a10b..fb24521d5 100755
--- a/spec/unit/network/http/mongrel/rest_spec.rb
+++ b/spec/unit/network/http/mongrel/rest_spec.rb
@@ -1,249 +1,248 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../../spec_helper'
require 'puppet/network/http'
-describe "Puppet::Network::HTTP::MongrelREST" do
- confine "Mongrel is not available" => Puppet.features.mongrel?
+describe "Puppet::Network::HTTP::MongrelREST", :if => Puppet.features.mongrel? do
before do
require 'puppet/network/http/mongrel/rest'
end
it "should include the Puppet::Network::HTTP::Handler module" do
Puppet::Network::HTTP::MongrelREST.ancestors.should be_include(Puppet::Network::HTTP::Handler)
end
describe "when initializing" do
it "should call the Handler's initialization hook with its provided arguments as the server and handler" do
Puppet::Network::HTTP::MongrelREST.any_instance.expects(:initialize_for_puppet).with(:server => "my", :handler => "arguments")
Puppet::Network::HTTP::MongrelREST.new(:server => "my", :handler => "arguments")
end
end
describe "when receiving a request" do
before do
@params = {}
@request = stub('mongrel http request', :params => @params)
@head = stub('response head')
@body = stub('response body', :write => true)
@response = stub('mongrel http response')
@response.stubs(:start).yields(@head, @body)
@model_class = stub('indirected model class')
@mongrel = stub('mongrel http server', :register => true)
Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@model_class)
@handler = Puppet::Network::HTTP::MongrelREST.new(:server => @mongrel, :handler => :foo)
end
describe "and using the HTTP Handler interface" do
it "should return the HTTP_ACCEPT parameter as the accept header" do
@params.expects(:[]).with("HTTP_ACCEPT").returns "myaccept"
@handler.accept_header(@request).should == "myaccept"
end
it "should return the Content-Type parameter as the Content-Type header" do
@params.expects(:[]).with("HTTP_CONTENT_TYPE").returns "mycontent"
@handler.content_type_header(@request).should == "mycontent"
end
it "should use the REQUEST_METHOD as the http method" do
@params.expects(:[]).with(Mongrel::Const::REQUEST_METHOD).returns "mymethod"
@handler.http_method(@request).should == "mymethod"
end
it "should return the request path as the path" do
@params.expects(:[]).with(Mongrel::Const::REQUEST_PATH).returns "/foo/bar"
@handler.path(@request).should == "/foo/bar"
end
it "should return the request body as the body" do
@request.expects(:body).returns StringIO.new("mybody")
@handler.body(@request).should == "mybody"
end
it "should set the response's content-type header when setting the content type" do
@header = mock 'header'
@response.expects(:header).returns @header
@header.expects(:[]=).with('Content-Type', "mytype")
@handler.set_content_type(@response, "mytype")
end
it "should set the status and write the body when setting the response for a successful request" do
head = mock 'head'
body = mock 'body'
@response.expects(:start).with(200).yields(head, body)
body.expects(:write).with("mybody")
@handler.set_response(@response, "mybody", 200)
end
describe "when the result is a File" do
it "should use response send_file" do
head = mock 'head'
body = mock 'body'
stat = stub 'stat', :size => 100
file = stub 'file', :stat => stat, :path => "/tmp/path"
file.stubs(:is_a?).with(File).returns(true)
@response.expects(:start).with(200).yields(head, body)
@response.expects(:send_status).with(100)
@response.expects(:send_header)
@response.expects(:send_file).with("/tmp/path")
@handler.set_response(@response, file, 200)
end
end
it "should set the status and reason and write the body when setting the response for a successful request" do
head = mock 'head'
body = mock 'body'
@response.expects(:start).with(400, false, "mybody").yields(head, body)
body.expects(:write).with("mybody")
@handler.set_response(@response, "mybody", 400)
end
end
describe "and determining the request parameters" do
before do
@request.stubs(:params).returns({})
end
it "should skip empty parameter values" do
@request.expects(:params).returns('QUERY_STRING' => "&=")
lambda { @handler.params(@request) }.should_not raise_error
end
it "should include the HTTP request parameters, with the keys as symbols" do
@request.expects(:params).returns('QUERY_STRING' => 'foo=baz&bar=xyzzy')
result = @handler.params(@request)
result[:foo].should == "baz"
result[:bar].should == "xyzzy"
end
it "should CGI-decode the HTTP parameters" do
encoding = CGI.escape("foo bar")
@request.expects(:params).returns('QUERY_STRING' => "foo=#{encoding}")
result = @handler.params(@request)
result[:foo].should == "foo bar"
end
it "should convert the string 'true' to the boolean" do
@request.expects(:params).returns('QUERY_STRING' => 'foo=true')
result = @handler.params(@request)
result[:foo].should be_true
end
it "should convert the string 'false' to the boolean" do
@request.expects(:params).returns('QUERY_STRING' => 'foo=false')
result = @handler.params(@request)
result[:foo].should be_false
end
it "should convert integer arguments to Integers" do
@request.expects(:params).returns('QUERY_STRING' => 'foo=15')
result = @handler.params(@request)
result[:foo].should == 15
end
it "should convert floating point arguments to Floats" do
@request.expects(:params).returns('QUERY_STRING' => 'foo=1.5')
result = @handler.params(@request)
result[:foo].should == 1.5
end
it "should YAML-load and URI-decode values that are YAML-encoded" do
escaping = CGI.escape(YAML.dump(%w{one two}))
@request.expects(:params).returns('QUERY_STRING' => "foo=#{escaping}")
result = @handler.params(@request)
result[:foo].should == %w{one two}
end
it "should not allow the client to set the node via the query string" do
@request.stubs(:params).returns('QUERY_STRING' => "node=foo")
@handler.params(@request)[:node].should be_nil
end
it "should not allow the client to set the IP address via the query string" do
@request.stubs(:params).returns('QUERY_STRING' => "ip=foo")
@handler.params(@request)[:ip].should be_nil
end
it "should pass the client's ip address to model find" do
@request.stubs(:params).returns("REMOTE_ADDR" => "ipaddress")
@handler.params(@request)[:ip].should == "ipaddress"
end
it "should pass the client's provided X-Forwared-For value as the ip" do
@request.stubs(:params).returns("HTTP_X_FORWARDED_FOR" => "ipaddress")
@handler.params(@request)[:ip].should == "ipaddress"
end
it "should pass the client's provided X-Forwared-For first value as the ip" do
@request.stubs(:params).returns("HTTP_X_FORWARDED_FOR" => "ipproxy1,ipproxy2,ipaddress")
@handler.params(@request)[:ip].should == "ipaddress"
end
it "should pass the client's provided X-Forwared-For value as the ip instead of the REMOTE_ADDR" do
@request.stubs(:params).returns("REMOTE_ADDR" => "remote_addr")
@request.stubs(:params).returns("HTTP_X_FORWARDED_FOR" => "ipaddress")
@handler.params(@request)[:ip].should == "ipaddress"
end
it "should use the :ssl_client_header to determine the parameter when looking for the certificate" do
Puppet.settings.stubs(:value).returns "eh"
Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader"
@request.stubs(:params).returns("myheader" => "/CN=host.domain.com")
@handler.params(@request)
end
it "should retrieve the hostname by matching the certificate parameter" do
Puppet.settings.stubs(:value).returns "eh"
Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader"
@request.stubs(:params).returns("myheader" => "/CN=host.domain.com")
@handler.params(@request)[:node].should == "host.domain.com"
end
it "should use the :ssl_client_header to determine the parameter for checking whether the host certificate is valid" do
Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
Puppet.settings.expects(:value).with(:ssl_client_verify_header).returns "myheader"
@request.stubs(:params).returns("myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com")
@handler.params(@request)
end
it "should consider the host authenticated if the validity parameter contains 'SUCCESS'" do
Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader"
@request.stubs(:params).returns("myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com")
@handler.params(@request)[:authenticated].should be_true
end
it "should consider the host unauthenticated if the validity parameter does not contain 'SUCCESS'" do
Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader"
@request.stubs(:params).returns("myheader" => "whatever", "certheader" => "/CN=host.domain.com")
@handler.params(@request)[:authenticated].should be_false
end
it "should consider the host unauthenticated if no certificate information is present" do
Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader"
@request.stubs(:params).returns("myheader" => nil, "certheader" => "SUCCESS")
@handler.params(@request)[:authenticated].should be_false
end
it "should resolve the node name with an ip address look-up if no certificate is present" do
Puppet.settings.stubs(:value).returns "eh"
Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader"
@request.stubs(:params).returns("myheader" => nil)
@handler.expects(:resolve_node).returns("host.domain.com")
@handler.params(@request)[:node].should == "host.domain.com"
end
end
end
end
diff --git a/spec/unit/network/http/mongrel_spec.rb b/spec/unit/network/http/mongrel_spec.rb
index ac3d72bae..1e24be0c6 100755
--- a/spec/unit/network/http/mongrel_spec.rb
+++ b/spec/unit/network/http/mongrel_spec.rb
@@ -1,131 +1,125 @@
#!/usr/bin/env ruby
#
# Created by Rick Bradley on 2007-10-15.
# Copyright (c) 2007. All rights reserved.
require File.dirname(__FILE__) + '/../../../spec_helper'
require 'puppet/network/http'
-describe "Puppet::Network::HTTP::Mongrel", "after initializing" do
- confine "Mongrel is not available" => Puppet.features.mongrel?
-
+describe "Puppet::Network::HTTP::Mongrel", "after initializing", :if => Puppet.features.mongrel? do
it "should not be listening" do
require 'puppet/network/http/mongrel'
Puppet::Network::HTTP::Mongrel.new.should_not be_listening
end
end
-describe "Puppet::Network::HTTP::Mongrel", "when turning on listening" do
- confine "Mongrel is not available" => Puppet.features.mongrel?
-
+describe "Puppet::Network::HTTP::Mongrel", "when turning on listening", :if => Puppet.features.mongrel? do
before do
require 'puppet/network/http/mongrel'
@server = Puppet::Network::HTTP::Mongrel.new
@mock_mongrel = mock('mongrel')
@mock_mongrel.stubs(:run)
@mock_mongrel.stubs(:register)
Mongrel::HttpServer.stubs(:new).returns(@mock_mongrel)
@mock_puppet_mongrel = mock('puppet_mongrel')
Puppet::Network::HTTPServer::Mongrel.stubs(:new).returns(@mock_puppet_mongrel)
@listen_params = { :address => "127.0.0.1", :port => 31337, :protocols => [ :rest, :xmlrpc ], :xmlrpc_handlers => [ :status, :fileserver ] }
end
it "should fail if already listening" do
@server.listen(@listen_params)
Proc.new { @server.listen(@listen_params) }.should raise_error(RuntimeError)
end
it "should require at least one protocol" do
Proc.new { @server.listen(@listen_params.delete_if {|k,v| :protocols == k}) }.should raise_error(ArgumentError)
end
it "should require a listening address to be specified" do
Proc.new { @server.listen(@listen_params.delete_if {|k,v| :address == k})}.should raise_error(ArgumentError)
end
it "should require a listening port to be specified" do
Proc.new { @server.listen(@listen_params.delete_if {|k,v| :port == k})}.should raise_error(ArgumentError)
end
it "should order a mongrel server to start" do
@mock_mongrel.expects(:run)
@server.listen(@listen_params)
end
it "should tell mongrel to listen on the specified address and port" do
Mongrel::HttpServer.expects(:new).with("127.0.0.1", 31337).returns(@mock_mongrel)
@server.listen(@listen_params)
end
it "should be listening" do
Mongrel::HttpServer.expects(:new).returns(@mock_mongrel)
@server.listen(@listen_params)
@server.should be_listening
end
describe "when providing REST services" do
it "should instantiate a handler at / for handling REST calls" do
Puppet::Network::HTTP::MongrelREST.expects(:new).returns "myhandler"
@mock_mongrel.expects(:register).with("/", "myhandler")
@server.listen(@listen_params)
end
it "should use a Mongrel + REST class to configure Mongrel when REST services are requested" do
@server.expects(:class_for_protocol).with(:rest).at_least_once.returns(Puppet::Network::HTTP::MongrelREST)
@server.listen(@listen_params)
end
end
describe "when providing XMLRPC services" do
it "should do nothing if no xmlrpc handlers have been provided" do
Puppet::Network::HTTPServer::Mongrel.expects(:new).never
@server.listen(@listen_params.merge(:xmlrpc_handlers => []))
end
it "should create an instance of the existing Mongrel http server with the right handlers" do
Puppet::Network::HTTPServer::Mongrel.expects(:new).with([:status, :master]).returns(@mock_puppet_mongrel)
@server.listen(@listen_params.merge(:xmlrpc_handlers => [:status, :master]))
end
it "should register the Mongrel server instance at /RPC2" do
@mock_mongrel.expects(:register).with("/RPC2", @mock_puppet_mongrel)
@server.listen(@listen_params.merge(:xmlrpc_handlers => [:status, :master]))
end
end
end
-describe "Puppet::Network::HTTP::Mongrel", "when turning off listening" do
- confine "Mongrel is not available" => Puppet.features.mongrel?
-
+describe "Puppet::Network::HTTP::Mongrel", "when turning off listening", :if => Puppet.features.mongrel? do
before do
@mock_mongrel = mock('mongrel httpserver')
@mock_mongrel.stubs(:run)
@mock_mongrel.stubs(:register)
Mongrel::HttpServer.stubs(:new).returns(@mock_mongrel)
@server = Puppet::Network::HTTP::Mongrel.new
@listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :catalog ], :protocols => [ :rest ] }
end
it "should fail unless listening" do
Proc.new { @server.unlisten }.should raise_error(RuntimeError)
end
it "should order mongrel server to stop" do
@server.listen(@listen_params)
@mock_mongrel.expects(:stop)
@server.unlisten
end
it "should not be listening" do
@server.listen(@listen_params)
@mock_mongrel.stubs(:stop)
@server.unlisten
@server.should_not be_listening
end
end
diff --git a/spec/unit/network/http/rack/rest_spec.rb b/spec/unit/network/http/rack/rest_spec.rb
index 96cf84c37..3eed4a2cb 100755
--- a/spec/unit/network/http/rack/rest_spec.rb
+++ b/spec/unit/network/http/rack/rest_spec.rb
@@ -1,249 +1,247 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../../spec_helper'
require 'puppet/network/http/rack' if Puppet.features.rack?
require 'puppet/network/http/rack/rest'
-describe "Puppet::Network::HTTP::RackREST" do
- confine "Rack is not available" => Puppet.features.rack?
-
+describe "Puppet::Network::HTTP::RackREST", :if => Puppet.features.rack? do
it "should include the Puppet::Network::HTTP::Handler module" do
Puppet::Network::HTTP::RackREST.ancestors.should be_include(Puppet::Network::HTTP::Handler)
end
describe "when initializing" do
it "should call the Handler's initialization hook with its provided arguments" do
Puppet::Network::HTTP::RackREST.any_instance.expects(:initialize_for_puppet).with(:server => "my", :handler => "arguments")
Puppet::Network::HTTP::RackREST.new(:server => "my", :handler => "arguments")
end
end
describe "when serving a request" do
before :all do
@model_class = stub('indirected model class')
Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@model_class)
@handler = Puppet::Network::HTTP::RackREST.new(:handler => :foo)
end
before :each do
@response = Rack::Response.new
end
def mk_req(uri, opts = {})
env = Rack::MockRequest.env_for(uri, opts)
Rack::Request.new(env)
end
describe "and using the HTTP Handler interface" do
it "should return the HTTP_ACCEPT parameter as the accept header" do
req = mk_req('/', 'HTTP_ACCEPT' => 'myaccept')
@handler.accept_header(req).should == "myaccept"
end
it "should return the CONTENT_TYPE parameter as the content type header" do
req = mk_req('/', 'CONTENT_TYPE' => 'mycontent')
@handler.content_type_header(req).should == "mycontent"
end
it "should use the REQUEST_METHOD as the http method" do
req = mk_req('/', :method => 'MYMETHOD')
@handler.http_method(req).should == "MYMETHOD"
end
it "should return the request path as the path" do
req = mk_req('/foo/bar')
@handler.path(req).should == "/foo/bar"
end
it "should return the request body as the body" do
req = mk_req('/foo/bar', :input => 'mybody')
@handler.body(req).should == "mybody"
end
it "should set the response's content-type header when setting the content type" do
@header = mock 'header'
@response.expects(:header).returns @header
@header.expects(:[]=).with('Content-Type', "mytype")
@handler.set_content_type(@response, "mytype")
end
it "should set the status and write the body when setting the response for a request" do
@response.expects(:status=).with(400)
@response.expects(:write).with("mybody")
@handler.set_response(@response, "mybody", 400)
end
describe "when result is a File" do
before :each do
stat = stub 'stat', :size => 100
@file = stub 'file', :stat => stat, :path => "/tmp/path"
@file.stubs(:is_a?).with(File).returns(true)
end
it "should set the Content-Length header as a string" do
@response.expects(:[]=).with("Content-Length", '100')
@handler.set_response(@response, @file, 200)
end
it "should return a RackFile adapter as body" do
@response.expects(:body=).with { |val| val.is_a?(Puppet::Network::HTTP::RackREST::RackFile) }
@handler.set_response(@response, @file, 200)
end
end
end
describe "and determining the request parameters" do
it "should include the HTTP request parameters, with the keys as symbols" do
req = mk_req('/?foo=baz&bar=xyzzy')
result = @handler.params(req)
result[:foo].should == "baz"
result[:bar].should == "xyzzy"
end
it "should CGI-decode the HTTP parameters" do
encoding = CGI.escape("foo bar")
req = mk_req("/?foo=#{encoding}")
result = @handler.params(req)
result[:foo].should == "foo bar"
end
it "should convert the string 'true' to the boolean" do
req = mk_req("/?foo=true")
result = @handler.params(req)
result[:foo].should be_true
end
it "should convert the string 'false' to the boolean" do
req = mk_req("/?foo=false")
result = @handler.params(req)
result[:foo].should be_false
end
it "should convert integer arguments to Integers" do
req = mk_req("/?foo=15")
result = @handler.params(req)
result[:foo].should == 15
end
it "should convert floating point arguments to Floats" do
req = mk_req("/?foo=1.5")
result = @handler.params(req)
result[:foo].should == 1.5
end
it "should YAML-load and CGI-decode values that are YAML-encoded" do
escaping = CGI.escape(YAML.dump(%w{one two}))
req = mk_req("/?foo=#{escaping}")
result = @handler.params(req)
result[:foo].should == %w{one two}
end
it "should not allow the client to set the node via the query string" do
req = mk_req("/?node=foo")
@handler.params(req)[:node].should be_nil
end
it "should not allow the client to set the IP address via the query string" do
req = mk_req("/?ip=foo")
@handler.params(req)[:ip].should be_nil
end
it "should pass the client's ip address to model find" do
req = mk_req("/", 'REMOTE_ADDR' => 'ipaddress')
@handler.params(req)[:ip].should == "ipaddress"
end
it "should set 'authenticated' to false if no certificate is present" do
req = mk_req('/')
@handler.params(req)[:authenticated].should be_false
end
end
describe "with pre-validated certificates" do
it "should use the :ssl_client_header to determine the parameter when looking for the certificate" do
Puppet.settings.stubs(:value).returns "eh"
Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader"
req = mk_req('/', "myheader" => "/CN=host.domain.com")
@handler.params(req)
end
it "should retrieve the hostname by matching the certificate parameter" do
Puppet.settings.stubs(:value).returns "eh"
Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader"
req = mk_req('/', "myheader" => "/CN=host.domain.com")
@handler.params(req)[:node].should == "host.domain.com"
end
it "should use the :ssl_client_header to determine the parameter for checking whether the host certificate is valid" do
Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
Puppet.settings.expects(:value).with(:ssl_client_verify_header).returns "myheader"
req = mk_req('/', "myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com")
@handler.params(req)
end
it "should consider the host authenticated if the validity parameter contains 'SUCCESS'" do
Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader"
req = mk_req('/', "myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com")
@handler.params(req)[:authenticated].should be_true
end
it "should consider the host unauthenticated if the validity parameter does not contain 'SUCCESS'" do
Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader"
req = mk_req('/', "myheader" => "whatever", "certheader" => "/CN=host.domain.com")
@handler.params(req)[:authenticated].should be_false
end
it "should consider the host unauthenticated if no certificate information is present" do
Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader"
req = mk_req('/', "myheader" => nil, "certheader" => "/CN=host.domain.com")
@handler.params(req)[:authenticated].should be_false
end
it "should resolve the node name with an ip address look-up if no certificate is present" do
Puppet.settings.stubs(:value).returns "eh"
Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader"
req = mk_req('/', "myheader" => nil)
@handler.expects(:resolve_node).returns("host.domain.com")
@handler.params(req)[:node].should == "host.domain.com"
end
end
end
end
describe Puppet::Network::HTTP::RackREST::RackFile do
before(:each) do
stat = stub 'stat', :size => 100
@file = stub 'file', :stat => stat, :path => "/tmp/path"
@rackfile = Puppet::Network::HTTP::RackREST::RackFile.new(@file)
end
it "should have an each method" do
@rackfile.should be_respond_to(:each)
end
it "should yield file chunks by chunks" do
@file.expects(:read).times(3).with(8192).returns("1", "2", nil)
i = 1
@rackfile.each do |chunk|
chunk.to_i.should == i
i += 1
end
end
it "should have a close method" do
@rackfile.should be_respond_to(:close)
end
it "should delegate close to File close" do
@file.expects(:close)
@rackfile.close
end
end
diff --git a/spec/unit/network/http/rack/xmlrpc_spec.rb b/spec/unit/network/http/rack/xmlrpc_spec.rb
index 870438f2c..e6411524e 100755
--- a/spec/unit/network/http/rack/xmlrpc_spec.rb
+++ b/spec/unit/network/http/rack/xmlrpc_spec.rb
@@ -1,158 +1,156 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../../spec_helper'
require 'puppet/network/handler'
require 'puppet/network/http/rack' if Puppet.features.rack?
require 'puppet/network/http/rack/xmlrpc' if Puppet.features.rack?
-describe "Puppet::Network::HTTP::RackXMLRPC" do
- confine "Rack is not available" => Puppet.features.rack?
-
+describe "Puppet::Network::HTTP::RackXMLRPC", :if => Puppet.features.rack? do
describe "when initializing" do
it "should create an Puppet::Network::XMLRPCServer" do
Puppet::Network::XMLRPCServer.expects(:new).returns stub_everything
Puppet::Network::HTTP::RackXMLRPC.new([])
end
it "should create each handler" do
handler = stub_everything 'handler'
Puppet::Network::XMLRPCServer.any_instance.stubs(:add_handler)
Puppet::Network::Handler.expects(:handler).returns(handler).times(2)
Puppet::Network::HTTP::RackXMLRPC.new([:foo, :bar])
end
it "should add each handler to the XMLRPCserver" do
handler = stub_everything 'handler'
Puppet::Network::Handler.stubs(:handler).returns(handler)
Puppet::Network::XMLRPCServer.any_instance.expects(:add_handler).times(2)
Puppet::Network::HTTP::RackXMLRPC.new([:foo, :bar])
end
end
describe "when serving a request" do
before :each do
foo_handler = stub_everything 'foo_handler'
Puppet::Network::Handler.stubs(:handler).with(:foo).returns foo_handler
Puppet::Network::XMLRPCServer.any_instance.stubs(:add_handler)
Puppet::Network::XMLRPCServer.any_instance.stubs(:process).returns('')
@handler = Puppet::Network::HTTP::RackXMLRPC.new([:foo])
end
before :each do
@response = Rack::Response.new
end
def mk_req(opts = {})
opts[:method] = 'POST' if !opts[:method]
opts['CONTENT_TYPE'] = 'text/xml; foo=bar' if !opts['CONTENT_TYPE']
env = Rack::MockRequest.env_for('/RPC2', opts)
Rack::Request.new(env)
end
it "should reject non-POST requests" do
req = mk_req :method => 'PUT'
@handler.process(req, @response)
@response.status.should == 405
end
it "should reject non text/xml requests" do
req = mk_req 'CONTENT_TYPE' => 'yadda/plain'
end
it "should create a ClientRequest" do
cr = Puppet::Network::ClientRequest.new(nil, '127.0.0.1', false)
Puppet::Network::ClientRequest.expects(:new).returns cr
req = mk_req
@handler.process(req, @response)
end
it "should let xmlrpcserver process the request" do
Puppet::Network::XMLRPCServer.any_instance.expects(:process).returns('yay')
req = mk_req
@handler.process(req, @response)
end
it "should report the response as OK" do
req = mk_req
@handler.process(req, @response)
@response.status.should == 200
end
it "should report the response with the correct content type" do
req = mk_req
@handler.process(req, @response)
@response['Content-Type'].should == 'text/xml; charset=utf-8'
end
it "should set 'authenticated' to false if no certificate is present" do
req = mk_req
Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| authenticated == false }
@handler.process(req, @response)
end
it "should use the client's ip address" do
req = mk_req 'REMOTE_ADDR' => 'ipaddress'
Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| ip == 'ipaddress' }
@handler.process(req, @response)
end
describe "with pre-validated certificates" do
it "should use the :ssl_client_header to determine the parameter when looking for the certificate" do
Puppet.settings.stubs(:value).returns "eh"
Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader"
req = mk_req "myheader" => "/CN=host.domain.com"
@handler.process(req, @response)
end
it "should retrieve the hostname by matching the certificate parameter" do
Puppet.settings.stubs(:value).returns "eh"
Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader"
Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| node == "host.domain.com" }
req = mk_req "myheader" => "/CN=host.domain.com"
@handler.process(req, @response)
end
it "should use the :ssl_client_header to determine the parameter for checking whether the host certificate is valid" do
Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
Puppet.settings.expects(:value).with(:ssl_client_verify_header).returns "myheader"
req = mk_req "myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com"
@handler.process(req, @response)
end
it "should consider the host authenticated if the validity parameter contains 'SUCCESS'" do
Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader"
Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| authenticated == true }
req = mk_req "myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com"
@handler.process(req, @response)
end
it "should consider the host unauthenticated if the validity parameter does not contain 'SUCCESS'" do
Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader"
Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| authenticated == false }
req = mk_req "myheader" => "whatever", "certheader" => "/CN=host.domain.com"
@handler.process(req, @response)
end
it "should consider the host unauthenticated if no certificate information is present" do
Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader"
Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader"
Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| authenticated == false }
req = mk_req "myheader" => nil, "certheader" => "/CN=host.domain.com"
@handler.process(req, @response)
end
it "should resolve the node name with an ip address look-up if no certificate is present" do
Puppet.settings.stubs(:value).returns "eh"
Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader"
Resolv.any_instance.expects(:getname).returns("host.domain.com")
Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| node == "host.domain.com" }
req = mk_req "myheader" => nil
@handler.process(req, @response)
end
end
end
end
diff --git a/spec/unit/network/http/rack_spec.rb b/spec/unit/network/http/rack_spec.rb
index 8be9ccb50..434294ce8 100755
--- a/spec/unit/network/http/rack_spec.rb
+++ b/spec/unit/network/http/rack_spec.rb
@@ -1,103 +1,101 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../spec_helper'
require 'puppet/network/handler'
require 'puppet/network/http/rack' if Puppet.features.rack?
-describe "Puppet::Network::HTTP::Rack" do
- confine "Rack is not available" => Puppet.features.rack?
-
+describe "Puppet::Network::HTTP::Rack", :if => Puppet.features.rack? do
describe "while initializing" do
it "should require a protocol specification" do
Proc.new { Puppet::Network::HTTP::Rack.new({}) }.should raise_error(ArgumentError)
end
it "should not accept imaginary protocols" do
Proc.new { Puppet::Network::HTTP::Rack.new({:protocols => [:foo]}) }.should raise_error(ArgumentError)
end
it "should accept the REST protocol" do
Proc.new { Puppet::Network::HTTP::Rack.new({:protocols => [:rest]}) }.should_not raise_error(ArgumentError)
end
it "should create a RackREST instance" do
Puppet::Network::HTTP::RackREST.expects(:new)
Puppet::Network::HTTP::Rack.new({:protocols => [:rest]})
end
describe "with XMLRPC enabled" do
it "should require XMLRPC handlers" do
Proc.new { Puppet::Network::HTTP::Rack.new({:protocols => [:xmlrpc]}) }.should raise_error(ArgumentError)
end
it "should create a RackXMLRPC instance" do
Puppet::Network::HTTP::RackXMLRPC.expects(:new)
Puppet::Network::HTTP::Rack.new({:protocols => [:xmlrpc], :xmlrpc_handlers => [:Status]})
end
end
end
describe "when called" do
before :all do
@app = Puppet::Network::HTTP::Rack.new({:protocols => [:rest]})
# let's use Rack::Lint to verify that we're OK with the rack specification
@linted = Rack::Lint.new(@app)
end
before :each do
@env = Rack::MockRequest.env_for('/')
end
it "should create a Request object" do
request = Rack::Request.new(@env)
Rack::Request.expects(:new).returns request
@linted.call(@env)
end
it "should create a Response object" do
Rack::Response.expects(:new).returns stub_everything
@app.call(@env) # can't lint when Rack::Response is a stub
end
it "should let RackREST process the request" do
Puppet::Network::HTTP::RackREST.any_instance.expects(:process).once
@linted.call(@env)
end
it "should catch unhandled exceptions from RackREST" do
Puppet::Network::HTTP::RackREST.any_instance.expects(:process).raises(ArgumentError, 'test error')
Proc.new { @linted.call(@env) }.should_not raise_error
end
it "should finish() the Response" do
Rack::Response.any_instance.expects(:finish).once
@app.call(@env) # can't lint when finish is a stub
end
end
describe "when serving XMLRPC" do
before :all do
@app = Puppet::Network::HTTP::Rack.new({:protocols => [:rest, :xmlrpc], :xmlrpc_handlers => [:Status]})
@linted = Rack::Lint.new(@app)
end
before :each do
@env = Rack::MockRequest.env_for('/RPC2', :method => 'POST')
end
it "should use RackXMLRPC to serve /RPC2 requests" do
Puppet::Network::HTTP::RackXMLRPC.any_instance.expects(:process).once
@linted.call(@env)
end
end
end
diff --git a/spec/unit/parameter_spec.rb b/spec/unit/parameter_spec.rb
index 966bbfb81..f8ab05d62 100755
--- a/spec/unit/parameter_spec.rb
+++ b/spec/unit/parameter_spec.rb
@@ -1,172 +1,171 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet/parameter'
describe Puppet::Parameter do
before do
@class = Class.new(Puppet::Parameter) do
@name = :foo
end
@class.initvars
@resource = mock 'resource'
@resource.stub_everything
@parameter = @class.new :resource => @resource
end
it "should create a value collection" do
@class = Class.new(Puppet::Parameter)
@class.value_collection.should be_nil
@class.initvars
@class.value_collection.should be_instance_of(Puppet::Parameter::ValueCollection)
end
it "should return its name as a string when converted to a string" do
@parameter.to_s.should == @parameter.name.to_s
end
it "should be able to use cached attributes" do
Puppet::Parameter.ancestors.should be_include(Puppet::Util::Cacher)
end
it "should use the resource catalog for expiration" do
catalog = mock 'catalog'
@resource.stubs(:catalog).returns catalog
@parameter.expirer.should equal(catalog)
end
[:line, :file, :version].each do |data|
it "should return its resource's #{data} as its #{data}" do
@resource.expects(data).returns "foo"
@parameter.send(data).should == "foo"
end
end
it "should return the resource's tags plus its name as its tags" do
@resource.expects(:tags).returns %w{one two}
@parameter.tags.should == %w{one two foo}
end
it "should provide source_descriptors" do
@resource.expects(:line).returns 10
@resource.expects(:file).returns "file"
@resource.expects(:tags).returns %w{one two}
- @resource.expects(:version).returns 50
- @parameter.source_descriptors.should == {:tags=>["one", "two", "foo"], :path=>"//foo", :version=>50, :file => "file", :line => 10}
+ @parameter.source_descriptors.should == {:tags=>["one", "two", "foo"], :path=>"//foo", :file => "file", :line => 10}
end
describe "when returning the value" do
it "should return nil if no value is set" do
@parameter.value.should be_nil
end
it "should validate the value" do
@parameter.expects(:validate).with("foo")
@parameter.value = "foo"
end
it "should munge the value and use any result as the actual value" do
@parameter.expects(:munge).with("foo").returns "bar"
@parameter.value = "foo"
@parameter.value.should == "bar"
end
it "should unmunge the value when accessing the actual value" do
@parameter.class.unmunge do |value| value.to_sym end
@parameter.value = "foo"
@parameter.value.should == :foo
end
it "should return the actual value by default when unmunging" do
@parameter.unmunge("bar").should == "bar"
end
it "should return any set value" do
@parameter.value = "foo"
@parameter.value.should == "foo"
end
end
describe "when validating values" do
it "should do nothing if no values or regexes have been defined" do
@parameter.validate("foo")
end
it "should catch abnormal failures thrown during validation" do
@class.validate { |v| raise "This is broken" }
lambda { @parameter.validate("eh") }.should raise_error(Puppet::DevError)
end
it "should fail if the value is not a defined value or alias and does not match a regex" do
@class.newvalues :foo
lambda { @parameter.validate("bar") }.should raise_error(Puppet::Error)
end
it "should succeed if the value is one of the defined values" do
@class.newvalues :foo
lambda { @parameter.validate(:foo) }.should_not raise_error(ArgumentError)
end
it "should succeed if the value is one of the defined values even if the definition uses a symbol and the validation uses a string" do
@class.newvalues :foo
lambda { @parameter.validate("foo") }.should_not raise_error(ArgumentError)
end
it "should succeed if the value is one of the defined values even if the definition uses a string and the validation uses a symbol" do
@class.newvalues "foo"
lambda { @parameter.validate(:foo) }.should_not raise_error(ArgumentError)
end
it "should succeed if the value is one of the defined aliases" do
@class.newvalues :foo
@class.aliasvalue :bar, :foo
lambda { @parameter.validate("bar") }.should_not raise_error(ArgumentError)
end
it "should succeed if the value matches one of the regexes" do
@class.newvalues %r{\d}
lambda { @parameter.validate("10") }.should_not raise_error(ArgumentError)
end
end
describe "when munging values" do
it "should do nothing if no values or regexes have been defined" do
@parameter.munge("foo").should == "foo"
end
it "should catch abnormal failures thrown during munging" do
@class.munge { |v| raise "This is broken" }
lambda { @parameter.munge("eh") }.should raise_error(Puppet::DevError)
end
it "should return return any matching defined values" do
@class.newvalues :foo, :bar
@parameter.munge("foo").should == :foo
end
it "should return any matching aliases" do
@class.newvalues :foo
@class.aliasvalue :bar, :foo
@parameter.munge("bar").should == :foo
end
it "should return the value if it matches a regex" do
@class.newvalues %r{\w}
@parameter.munge("bar").should == "bar"
end
it "should return the value if no other option is matched" do
@class.newvalues :foo
@parameter.munge("bar").should == "bar"
end
end
describe "when logging" do
it "should use its resource's log level and the provided message" do
@resource.expects(:[]).with(:loglevel).returns :notice
@parameter.expects(:send_log).with(:notice, "mymessage")
@parameter.log "mymessage"
end
end
end
diff --git a/spec/unit/parser/collector_spec.rb b/spec/unit/parser/collector_spec.rb
index 908cda63a..4cab26c44 100755
--- a/spec/unit/parser/collector_spec.rb
+++ b/spec/unit/parser/collector_spec.rb
@@ -1,560 +1,556 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/rails'
require 'puppet/parser/collector'
describe Puppet::Parser::Collector, "when initializing" do
before do
@scope = mock 'scope'
@resource_type = 'resource_type'
@form = :exported
@vquery = mock 'vquery'
@equery = mock 'equery'
@collector = Puppet::Parser::Collector.new(@scope, @resource_type, @equery, @vquery, @form)
end
it "should require a scope" do
@collector.scope.should equal(@scope)
end
it "should require a resource type" do
@collector.type.should == 'Resource_type'
end
it "should only accept :virtual or :exported as the collector form" do
proc { @collector = Puppet::Parser::Collector.new(@scope, @resource_type, @vquery, @equery, :other) }.should raise_error(ArgumentError)
end
it "should accept an optional virtual query" do
@collector.vquery.should equal(@vquery)
end
it "should accept an optional exported query" do
@collector.equery.should equal(@equery)
end
it "should canonize the type name" do
@collector = Puppet::Parser::Collector.new(@scope, "resource::type", @equery, @vquery, @form)
@collector.type.should == "Resource::Type"
end
it "should accept an optional resource override" do
@collector = Puppet::Parser::Collector.new(@scope, "resource::type", @equery, @vquery, @form)
override = { :parameters => "whatever" }
@collector.add_override(override)
@collector.overrides.should equal(override)
end
end
describe Puppet::Parser::Collector, "when collecting specific virtual resources" do
before do
@scope = mock 'scope'
@vquery = mock 'vquery'
@equery = mock 'equery'
@collector = Puppet::Parser::Collector.new(@scope, "resource_type", @equery, @vquery, :virtual)
end
it "should not fail when it does not find any resources to collect" do
@collector.resources = ["File[virtual1]", "File[virtual2]"]
@scope.stubs(:findresource).returns(false)
proc { @collector.evaluate }.should_not raise_error
end
it "should mark matched resources as non-virtual" do
@collector.resources = ["File[virtual1]", "File[virtual2]"]
one = stub_everything 'one'
one.expects(:virtual=).with(false)
@scope.stubs(:findresource).with("File[virtual1]").returns(one)
@scope.stubs(:findresource).with("File[virtual2]").returns(nil)
@collector.evaluate
end
it "should return matched resources" do
@collector.resources = ["File[virtual1]", "File[virtual2]"]
one = stub_everything 'one'
@scope.stubs(:findresource).with("File[virtual1]").returns(one)
@scope.stubs(:findresource).with("File[virtual2]").returns(nil)
@collector.evaluate.should == [one]
end
it "should delete itself from the compile's collection list if it has found all of its resources" do
@collector.resources = ["File[virtual1]"]
one = stub_everything 'one'
@compiler.expects(:delete_collection).with(@collector)
@scope.expects(:compiler).returns(@compiler)
@scope.stubs(:findresource).with("File[virtual1]").returns(one)
@collector.evaluate
end
it "should not delete itself from the compile's collection list if it has unfound resources" do
@collector.resources = ["File[virtual1]"]
one = stub_everything 'one'
@compiler.expects(:delete_collection).never
@scope.stubs(:findresource).with("File[virtual1]").returns(nil)
@collector.evaluate
end
end
describe Puppet::Parser::Collector, "when collecting virtual and catalog resources" do
before do
@scope = mock 'scope'
@compiler = mock 'compile'
@scope.stubs(:compiler).returns(@compiler)
@resource_type = "Mytype"
@vquery = proc { |res| true }
@collector = Puppet::Parser::Collector.new(@scope, @resource_type, nil, @vquery, :virtual)
end
it "should find all virtual resources matching the vquery" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true
two = stub_everything 'two', :type => "Mytype", :virtual? => true
@compiler.expects(:resources).returns([one, two])
@collector.evaluate.should == [one, two]
end
it "should find all non-virtual resources matching the vquery" do
one = stub_everything 'one', :type => "Mytype", :virtual? => false
two = stub_everything 'two', :type => "Mytype", :virtual? => false
@compiler.expects(:resources).returns([one, two])
@collector.evaluate.should == [one, two]
end
it "should mark all matched resources as non-virtual" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true
one.expects(:virtual=).with(false)
@compiler.expects(:resources).returns([one])
@collector.evaluate
end
it "should return matched resources" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true
two = stub_everything 'two', :type => "Mytype", :virtual? => true
@compiler.expects(:resources).returns([one, two])
@collector.evaluate.should == [one, two]
end
it "should return all resources of the correct type if there is no virtual query" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true
two = stub_everything 'two', :type => "Mytype", :virtual? => true
one.expects(:virtual=).with(false)
two.expects(:virtual=).with(false)
@compiler.expects(:resources).returns([one, two])
@collector = Puppet::Parser::Collector.new(@scope, @resource_type, nil, nil, :virtual)
@collector.evaluate.should == [one, two]
end
it "should not return or mark resources of a different type" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true
two = stub_everything 'two', :type => :other, :virtual? => true
one.expects(:virtual=).with(false)
two.expects(:virtual=).never
@compiler.expects(:resources).returns([one, two])
@collector.evaluate.should == [one]
end
it "should create a resource with overridden parameters" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true, :title => "test"
param = stub 'param'
@compiler.stubs(:add_override)
@compiler.expects(:resources).returns([one])
@collector.add_override(:parameters => param )
Puppet::Parser::Resource.expects(:new).with { |type, title, h|
h[:parameters] == param
}
@collector.evaluate
end
it "should define a new allow all child_of? on overriden resource" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true, :title => "test"
param = stub 'param'
source = stub 'source'
@compiler.stubs(:add_override)
@compiler.expects(:resources).returns([one])
@collector.add_override(:parameters => param, :source => source )
Puppet::Parser::Resource.stubs(:new)
source.expects(:meta_def).with { |name,block| name == :child_of? }
@collector.evaluate
end
it "should not override already overriden resources for this same collection in a previous run" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true, :title => "test"
param = stub 'param'
@compiler.stubs(:add_override)
@compiler.expects(:resources).at_least(2).returns([one])
@collector.add_override(:parameters => param )
Puppet::Parser::Resource.expects(:new).once.with { |type, title, h|
h[:parameters] == param
}
@collector.evaluate
@collector.evaluate
end
it "should not return resources that were collected in a previous run of this collector" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true, :title => "test"
@compiler.stubs(:resources).returns([one])
@collector.evaluate
@collector.evaluate.should be_false
end
it "should tell the compiler about the overriden resources" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true, :title => "test"
param = stub 'param'
one.expects(:virtual=).with(false)
@compiler.expects(:resources).returns([one])
@collector.add_override(:parameters => param )
Puppet::Parser::Resource.stubs(:new).returns("whatever")
@compiler.expects(:add_override).with("whatever")
@collector.evaluate
end
it "should not return or mark non-matching resources" do
@collector.vquery = proc { |res| res.name == :one }
one = stub_everything 'one', :name => :one, :type => "Mytype", :virtual? => true
two = stub_everything 'two', :name => :two, :type => "Mytype", :virtual? => true
one.expects(:virtual=).with(false)
two.expects(:virtual=).never
@compiler.expects(:resources).returns([one, two])
@collector.evaluate.should == [one]
end
end
-describe Puppet::Parser::Collector, "when collecting exported resources" do
- confine "Cannot test Rails integration without ActiveRecord" => Puppet.features.rails?
-
+describe Puppet::Parser::Collector, "when collecting exported resources", :if => Puppet.features.rails? do
before do
@compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("mynode"))
@scope = Puppet::Parser::Scope.new :compiler => @compiler
@resource_type = "Mytype"
@equery = "test = true"
@vquery = proc { |r| true }
res = stub("resource 1")
res.stubs(:type).returns @resource_type
Puppet::Resource.stubs(:new).returns res
Puppet.settings.stubs(:value).with(:storeconfigs).returns true
Puppet.settings.stubs(:value).with(:environment).returns "production"
@collector = Puppet::Parser::Collector.new(@scope, @resource_type, @equery, @vquery, :exported)
end
# Stub most of our interface to Rails.
def stub_rails(everything = false)
ActiveRecord::Base.stubs(:connected?).returns(false)
Puppet::Rails.stubs(:init)
if everything
Puppet::Rails::Host.stubs(:find_by_name).returns(nil)
Puppet::Rails::Resource.stubs(:find).returns([])
end
end
it "should just return false if :storeconfigs is not enabled" do
Puppet.settings.expects(:value).with(:storeconfigs).returns false
@collector.evaluate.should be_false
end
it "should use initialize the Rails support if ActiveRecord is not connected" do
@compiler.stubs(:resources).returns([])
ActiveRecord::Base.expects(:connected?).returns(false)
Puppet::Rails.expects(:init)
Puppet::Rails::Host.stubs(:find_by_name).returns(nil)
Puppet::Rails::Resource.stubs(:find).returns([])
@collector.evaluate
end
it "should return all matching resources from the current compile and mark them non-virtual and non-exported" do
stub_rails(true)
one = stub 'one', :type => "Mytype", :virtual? => true, :exported? => true, :ref => "one"
two = stub 'two', :type => "Mytype", :virtual? => true, :exported? => true, :ref => "two"
one.stubs(:exported=)
one.stubs(:virtual=)
two.stubs(:exported=)
two.stubs(:virtual=)
@compiler.expects(:resources).returns([one, two])
@collector.evaluate.should == [one, two]
end
it "should mark all returned resources as not virtual" do
stub_rails(true)
one = stub 'one', :type => "Mytype", :virtual? => true, :exported? => true, :ref => "one"
one.stubs(:exported=)
one.expects(:virtual=).with(false)
@compiler.expects(:resources).returns([one])
@collector.evaluate.should == [one]
end
it "should convert all found resources into parser resources" do
stub_rails
Puppet::Rails::Host.stubs(:find_by_name).returns(nil)
one = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :ref => "one"
Puppet::Rails::Resource.stubs(:find).returns([one])
resource = mock 'resource'
one.expects(:to_resource).with(@scope).returns(resource)
resource.stubs(:exported=)
resource.stubs(:virtual=)
resource.stubs(:ref)
@compiler.stubs(:resources).returns([])
@scope.stubs(:findresource).returns(nil)
@compiler.stubs(:add_resource)
@collector.evaluate.should == [resource]
end
it "should override all exported collected resources if collector has an override" do
stub_rails
Puppet::Rails::Host.stubs(:find_by_name).returns(nil)
one = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :ref => "one"
Puppet::Rails::Resource.stubs(:find).returns([one])
resource = mock 'resource', :type => "Mytype"
one.expects(:to_resource).with(@scope).returns(resource)
resource.stubs(:exported=)
resource.stubs(:virtual=)
resource.stubs(:ref)
resource.stubs(:title)
@compiler.stubs(:resources).returns([])
@scope.stubs(:findresource).returns(nil)
param = stub 'param'
@compiler.stubs(:add_override)
@compiler.stubs(:add_resource)
@collector.add_override(:parameters => param )
Puppet::Parser::Resource.expects(:new).once.with { |type, title, h|
h[:parameters] == param
}
@collector.evaluate
end
it "should store converted resources in the compile's resource list" do
stub_rails
Puppet::Rails::Host.stubs(:find_by_name).returns(nil)
one = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :ref => "one"
Puppet::Rails::Resource.stubs(:find).returns([one])
resource = mock 'resource'
one.expects(:to_resource).with(@scope).returns(resource)
resource.stubs(:exported=)
resource.stubs(:virtual=)
resource.stubs(:ref)
@compiler.stubs(:resources).returns([])
@scope.stubs(:findresource).returns(nil)
@compiler.expects(:add_resource).with(@scope, resource)
@collector.evaluate.should == [resource]
end
# This way one host doesn't store another host's resources as exported.
it "should mark resources collected from the database as not exported" do
stub_rails
Puppet::Rails::Host.stubs(:find_by_name).returns(nil)
one = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :ref => "one"
Puppet::Rails::Resource.stubs(:find).returns([one])
resource = mock 'resource'
one.expects(:to_resource).with(@scope).returns(resource)
resource.expects(:exported=).with(false)
resource.stubs(:virtual=)
resource.stubs(:ref)
@compiler.stubs(:resources).returns([])
@scope.stubs(:findresource).returns(nil)
@compiler.stubs(:add_resource)
@collector.evaluate
end
it "should fail if an equivalent resource already exists in the compile" do
stub_rails
Puppet::Rails::Host.stubs(:find_by_name).returns(nil)
rails = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :id => 1, :ref => "yay"
inmemory = stub 'one', :type => "Mytype", :virtual? => true, :exported? => true, :rails_id => 2
Puppet::Rails::Resource.stubs(:find).returns([rails])
resource = mock 'resource'
@compiler.stubs(:resources).returns([])
@scope.stubs(:findresource).returns(inmemory)
@compiler.stubs(:add_resource)
proc { @collector.evaluate }.should raise_error(Puppet::ParseError)
end
it "should ignore exported resources that match already-collected resources" do
stub_rails
Puppet::Rails::Host.stubs(:find_by_name).returns(nil)
rails = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :id => 1, :ref => "yay"
inmemory = stub 'one', :type => "Mytype", :virtual? => true, :exported? => true, :rails_id => 1
Puppet::Rails::Resource.stubs(:find).returns([rails])
resource = mock 'resource'
@compiler.stubs(:resources).returns([])
@scope.stubs(:findresource).returns(inmemory)
@compiler.stubs(:add_resource)
proc { @collector.evaluate }.should_not raise_error(Puppet::ParseError)
end
end
-describe Puppet::Parser::Collector, "when building its ActiveRecord query for collecting exported resources" do
- confine "Cannot test Rails integration without ActiveRecord" => Puppet.features.rails?
-
+describe Puppet::Parser::Collector, "when building its ActiveRecord query for collecting exported resources", :if => Puppet.features.rails? do
before do
@scope = stub 'scope', :host => "myhost", :debug => nil
@compiler = mock 'compile'
@scope.stubs(:compiler).returns(@compiler)
@resource_type = "Mytype"
@equery = nil
@vquery = proc { |r| true }
@resource = stub_everything 'collected'
@collector = Puppet::Parser::Collector.new(@scope, @resource_type, @equery, @vquery, :exported)
@collector.stubs(:exported_resource).with(@resource).returns(@resource)
@compiler.stubs(:resources).returns([])
ActiveRecord::Base.stubs(:connected?).returns(false)
Puppet::Rails.stubs(:init)
Puppet::Rails::Host.stubs(:find_by_name).returns(nil)
Puppet::Rails::Resource.stubs(:find).returns([])
Puppet.settings.stubs(:value).with(:storeconfigs).returns true
end
it "should exclude all resources from the host if ActiveRecord contains information for this host" do
@host = mock 'host'
@host.stubs(:id).returns 5
Puppet::Rails::Host.expects(:find_by_name).with(@scope.host).returns(@host)
Puppet::Rails::Resource.stubs(:find).with { |*arguments|
options = arguments[1]
options[:conditions][0] =~ /^host_id != \?/ and options[:conditions][1] == 5
}.returns([@resource])
@collector.evaluate.should == [@resource]
end
it "should join with parameter names, parameter values when querying ActiveRecord" do
@collector.equery = "param_names.name = title"
Puppet::Rails::Resource.stubs(:find).with { |*arguments|
options = arguments[1]
options[:joins] == {:param_values => :param_name}
}.returns([@resource])
@collector.evaluate.should == [@resource]
end
it "should join with tag tables when querying ActiveRecord with a tag exported query" do
@collector.equery = "puppet_tags.name = test"
Puppet::Rails::Resource.stubs(:find).with { |*arguments|
options = arguments[1]
options[:joins] == {:resource_tags => :puppet_tag}
}.returns([@resource])
@collector.evaluate.should == [@resource]
end
it "should not join parameters when querying ActiveRecord with a tag exported query" do
@collector.equery = "puppet_tags.name = test"
Puppet::Rails::Resource.stubs(:find).with { |*arguments|
options = arguments[1]
options[:joins] == {:param_values => :param_name}
}.returns([@resource])
@collector.evaluate.should be_false
end
it "should only search for exported resources with the matching type" do
Puppet::Rails::Resource.stubs(:find).with { |*arguments|
options = arguments[1]
options[:conditions][0].include?("(exported=? AND restype=?)") and options[:conditions][1] == true and options[:conditions][2] == "Mytype"
}.returns([@resource])
@collector.evaluate.should == [@resource]
end
it "should include the export query if one is provided" do
@collector.equery = "test = true"
Puppet::Rails::Resource.stubs(:find).with { |*arguments|
options = arguments[1]
options[:conditions][0].include?("test = true")
}.returns([@resource])
@collector.evaluate.should == [@resource]
end
end
diff --git a/spec/unit/parser/lexer_spec.rb b/spec/unit/parser/lexer_spec.rb
index d52add399..860326973 100755
--- a/spec/unit/parser/lexer_spec.rb
+++ b/spec/unit/parser/lexer_spec.rb
@@ -1,664 +1,665 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/parser/lexer'
# This is a special matcher to match easily lexer output
-Spec::Matchers.define :be_like do |*expected|
+RSpec::Matchers.define :be_like do |*expected|
match do |actual|
expected.zip(actual).all? { |e,a| !e or a[0] == e or (e.is_a? Array and a[0] == e[0] and (a[1] == e[1] or (a[1].is_a?(Hash) and a[1][:value] == e[1]))) }
end
end
__ = nil
describe Puppet::Parser::Lexer do
describe "when reading strings" do
before { @lexer = Puppet::Parser::Lexer.new }
it "should increment the line count for every carriage return in the string" do
@lexer.line = 10
@lexer.string = "this\nis\natest'"
@lexer.slurpstring("'")
@lexer.line.should == 12
end
it "should not increment the line count for escapes in the string" do
@lexer.line = 10
@lexer.string = "this\\nis\\natest'"
@lexer.slurpstring("'")
@lexer.line.should == 10
end
it "should not think the terminator is escaped, when preceeded by an even number of backslashes" do
@lexer.line = 10
@lexer.string = "here\nis\nthe\nstring\\\\'with\nextra\njunk"
@lexer.slurpstring("'")
@lexer.line.should == 13
end
end
end
describe Puppet::Parser::Lexer::Token do
before do
@token = Puppet::Parser::Lexer::Token.new(%r{something}, :NAME)
end
[:regex, :name, :string, :skip, :incr_line, :skip_text, :accumulate].each do |param|
it "should have a #{param.to_s} reader" do
@token.should be_respond_to(param)
end
it "should have a #{param.to_s} writer" do
@token.should be_respond_to(param.to_s + "=")
end
end
end
describe Puppet::Parser::Lexer::Token, "when initializing" do
it "should create a regex if the first argument is a string" do
Puppet::Parser::Lexer::Token.new("something", :NAME).regex.should == %r{something}
end
it "should set the string if the first argument is one" do
Puppet::Parser::Lexer::Token.new("something", :NAME).string.should == "something"
end
it "should set the regex if the first argument is one" do
Puppet::Parser::Lexer::Token.new(%r{something}, :NAME).regex.should == %r{something}
end
end
describe Puppet::Parser::Lexer::TokenList do
before do
@list = Puppet::Parser::Lexer::TokenList.new
end
it "should have a method for retrieving tokens by the name" do
token = @list.add_token :name, "whatever"
@list[:name].should equal(token)
end
it "should have a method for retrieving string tokens by the string" do
token = @list.add_token :name, "whatever"
@list.lookup("whatever").should equal(token)
end
it "should add tokens to the list when directed" do
token = @list.add_token :name, "whatever"
@list[:name].should equal(token)
end
it "should have a method for adding multiple tokens at once" do
@list.add_tokens "whatever" => :name, "foo" => :bar
@list[:name].should_not be_nil
@list[:bar].should_not be_nil
end
it "should fail to add tokens sharing a name with an existing token" do
@list.add_token :name, "whatever"
lambda { @list.add_token :name, "whatever" }.should raise_error(ArgumentError)
end
it "should set provided options on tokens being added" do
token = @list.add_token :name, "whatever", :skip_text => true
token.skip_text.should == true
end
it "should define any provided blocks as a :convert method" do
token = @list.add_token(:name, "whatever") do "foo" end
token.convert.should == "foo"
end
it "should store all string tokens in the :string_tokens list" do
one = @list.add_token(:name, "1")
@list.string_tokens.should be_include(one)
end
it "should store all regex tokens in the :regex_tokens list" do
one = @list.add_token(:name, %r{one})
@list.regex_tokens.should be_include(one)
end
it "should not store string tokens in the :regex_tokens list" do
one = @list.add_token(:name, "1")
@list.regex_tokens.should_not be_include(one)
end
it "should not store regex tokens in the :string_tokens list" do
one = @list.add_token(:name, %r{one})
@list.string_tokens.should_not be_include(one)
end
it "should sort the string tokens inversely by length when asked" do
one = @list.add_token(:name, "1")
two = @list.add_token(:other, "12")
@list.sort_tokens
@list.string_tokens.should == [two, one]
end
end
describe Puppet::Parser::Lexer::TOKENS do
before do
@lexer = Puppet::Parser::Lexer.new
end
{
:LBRACK => '[',
:RBRACK => ']',
:LBRACE => '{',
:RBRACE => '}',
:LPAREN => '(',
:RPAREN => ')',
:EQUALS => '=',
:ISEQUAL => '==',
:GREATEREQUAL => '>=',
:GREATERTHAN => '>',
:LESSTHAN => '<',
:LESSEQUAL => '<=',
:NOTEQUAL => '!=',
:NOT => '!',
:COMMA => ',',
:DOT => '.',
:COLON => ':',
:AT => '@',
:LLCOLLECT => '<<|',
:RRCOLLECT => '|>>',
:LCOLLECT => '<|',
:RCOLLECT => '|>',
:SEMIC => ';',
:QMARK => '?',
:BACKSLASH => '\\',
:FARROW => '=>',
:PARROW => '+>',
:APPENDS => '+=',
:PLUS => '+',
:MINUS => '-',
:DIV => '/',
:TIMES => '*',
:LSHIFT => '<<',
:RSHIFT => '>>',
:MATCH => '=~',
:NOMATCH => '!~',
:IN_EDGE => '->',
:OUT_EDGE => '<-',
:IN_EDGE_SUB => '~>',
:OUT_EDGE_SUB => '<~',
}.each do |name, string|
it "should have a token named #{name.to_s}" do
Puppet::Parser::Lexer::TOKENS[name].should_not be_nil
end
it "should match '#{string}' for the token #{name.to_s}" do
Puppet::Parser::Lexer::TOKENS[name].string.should == string
end
end
{
"case" => :CASE,
"class" => :CLASS,
"default" => :DEFAULT,
"define" => :DEFINE,
"import" => :IMPORT,
"if" => :IF,
"elsif" => :ELSIF,
"else" => :ELSE,
"inherits" => :INHERITS,
"node" => :NODE,
"and" => :AND,
"or" => :OR,
"undef" => :UNDEF,
"false" => :FALSE,
"true" => :TRUE,
"in" => :IN,
}.each do |string, name|
it "should have a keyword named #{name.to_s}" do
Puppet::Parser::Lexer::KEYWORDS[name].should_not be_nil
end
it "should have the keyword for #{name.to_s} set to #{string}" do
Puppet::Parser::Lexer::KEYWORDS[name].string.should == string
end
end
# These tokens' strings don't matter, just that the tokens exist.
[:STRING, :DQPRE, :DQMID, :DQPOST, :BOOLEAN, :NAME, :NUMBER, :COMMENT, :MLCOMMENT, :RETURN, :SQUOTE, :DQUOTE, :VARIABLE].each do |name|
it "should have a token named #{name.to_s}" do
Puppet::Parser::Lexer::TOKENS[name].should_not be_nil
end
end
end
describe Puppet::Parser::Lexer::TOKENS[:CLASSNAME] do
before { @token = Puppet::Parser::Lexer::TOKENS[:CLASSNAME] }
it "should match against lower-case alpha-numeric terms separated by double colons" do
@token.regex.should =~ "one::two"
end
it "should match against many lower-case alpha-numeric terms separated by double colons" do
@token.regex.should =~ "one::two::three::four::five"
end
it "should match against lower-case alpha-numeric terms prefixed by double colons" do
@token.regex.should =~ "::one"
end
end
describe Puppet::Parser::Lexer::TOKENS[:CLASSREF] do
before { @token = Puppet::Parser::Lexer::TOKENS[:CLASSREF] }
it "should match against single upper-case alpha-numeric terms" do
@token.regex.should =~ "One"
end
it "should match against upper-case alpha-numeric terms separated by double colons" do
@token.regex.should =~ "One::Two"
end
it "should match against many upper-case alpha-numeric terms separated by double colons" do
@token.regex.should =~ "One::Two::Three::Four::Five"
end
it "should match against upper-case alpha-numeric terms prefixed by double colons" do
@token.regex.should =~ "::One"
end
end
describe Puppet::Parser::Lexer::TOKENS[:NAME] do
before { @token = Puppet::Parser::Lexer::TOKENS[:NAME] }
it "should match against lower-case alpha-numeric terms" do
@token.regex.should =~ "one-two"
end
it "should return itself and the value if the matched term is not a keyword" do
Puppet::Parser::Lexer::KEYWORDS.expects(:lookup).returns(nil)
@token.convert(stub("lexer"), "myval").should == [Puppet::Parser::Lexer::TOKENS[:NAME], "myval"]
end
it "should return the keyword token and the value if the matched term is a keyword" do
keyword = stub 'keyword', :name => :testing
Puppet::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword)
@token.convert(stub("lexer"), "myval").should == [keyword, "myval"]
end
it "should return the BOOLEAN token and 'true' if the matched term is the string 'true'" do
keyword = stub 'keyword', :name => :TRUE
Puppet::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword)
@token.convert(stub('lexer'), "true").should == [Puppet::Parser::Lexer::TOKENS[:BOOLEAN], true]
end
it "should return the BOOLEAN token and 'false' if the matched term is the string 'false'" do
keyword = stub 'keyword', :name => :FALSE
Puppet::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword)
@token.convert(stub('lexer'), "false").should == [Puppet::Parser::Lexer::TOKENS[:BOOLEAN], false]
end
end
describe Puppet::Parser::Lexer::TOKENS[:NUMBER] do
before do
@token = Puppet::Parser::Lexer::TOKENS[:NUMBER]
@regex = @token.regex
end
it "should match against numeric terms" do
@regex.should =~ "2982383139"
end
it "should match against float terms" do
@regex.should =~ "29823.235"
end
it "should match against hexadecimal terms" do
@regex.should =~ "0xBEEF0023"
end
it "should match against float with exponent terms" do
@regex.should =~ "10e23"
end
it "should match against float terms with negative exponents" do
@regex.should =~ "10e-23"
end
it "should match against float terms with fractional parts and exponent" do
@regex.should =~ "1.234e23"
end
it "should return the NAME token and the value" do
@token.convert(stub("lexer"), "myval").should == [Puppet::Parser::Lexer::TOKENS[:NAME], "myval"]
end
end
describe Puppet::Parser::Lexer::TOKENS[:COMMENT] do
before { @token = Puppet::Parser::Lexer::TOKENS[:COMMENT] }
it "should match against lines starting with '#'" do
@token.regex.should =~ "# this is a comment"
end
it "should be marked to get skipped" do
@token.skip?.should be_true
end
it "should be marked to accumulate" do
@token.accumulate?.should be_true
end
it "'s block should return the comment without the #" do
@token.convert(@lexer,"# this is a comment")[1].should == "this is a comment"
end
end
describe Puppet::Parser::Lexer::TOKENS[:MLCOMMENT] do
before do
@token = Puppet::Parser::Lexer::TOKENS[:MLCOMMENT]
@lexer = stub 'lexer', :line => 0
end
it "should match against lines enclosed with '/*' and '*/'" do
@token.regex.should =~ "/* this is a comment */"
end
it "should match multiple lines enclosed with '/*' and '*/'" do
@token.regex.should =~ """/*
this is a comment
*/"""
end
it "should increase the lexer current line number by the amount of lines spanned by the comment" do
@lexer.expects(:line=).with(2)
@token.convert(@lexer, "1\n2\n3")
end
it "should not greedily match comments" do
match = @token.regex.match("/* first */ word /* second */")
match[1].should == " first "
end
it "should be marked to accumulate" do
@token.accumulate?.should be_true
end
it "'s block should return the comment without the comment marks" do
@lexer.stubs(:line=).with(0)
@token.convert(@lexer,"/* this is a comment */")[1].should == "this is a comment"
end
end
describe Puppet::Parser::Lexer::TOKENS[:RETURN] do
before { @token = Puppet::Parser::Lexer::TOKENS[:RETURN] }
it "should match against carriage returns" do
@token.regex.should =~ "\n"
end
it "should be marked to initiate text skipping" do
@token.skip_text.should be_true
end
it "should be marked to increment the line" do
@token.incr_line.should be_true
end
end
def tokens_scanned_from(s)
lexer = Puppet::Parser::Lexer.new
lexer.string = s
lexer.fullscan[0..-2]
end
describe Puppet::Parser::Lexer,"when lexing strings" do
{
%q{'single quoted string')} => [[:STRING,'single quoted string']],
%q{"double quoted string"} => [[:STRING,'double quoted string']],
%q{'single quoted string with an escaped "\\'"'} => [[:STRING,'single quoted string with an escaped "\'"']],
%q{'single quoted string with an escaped "\$"'} => [[:STRING,'single quoted string with an escaped "\$"']],
%q{'single quoted string with an escaped "\."'} => [[:STRING,'single quoted string with an escaped "\."']],
%q{'single quoted string with an escaped "\n"'} => [[:STRING,'single quoted string with an escaped "\n"']],
%q{'single quoted string with an escaped "\\\\"'} => [[:STRING,'single quoted string with an escaped "\\\\"']],
%q{"string with an escaped '\\"'"} => [[:STRING,"string with an escaped '\"'"]],
%q{"string with an escaped '\\$'"} => [[:STRING,"string with an escaped '$'"]],
%Q{"string with a line ending with a backslash: \\\nfoo"} => [[:STRING,"string with a line ending with a backslash: foo"]],
%q{"string with $v (but no braces)"} => [[:DQPRE,"string with "],[:VARIABLE,'v'],[:DQPOST,' (but no braces)']],
%q["string with ${v} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,'v'],[:DQPOST,' in braces']],
%q["string with ${qualified::var} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,'qualified::var'],[:DQPOST,' in braces']],
%q{"string with $v and $v (but no braces)"} => [[:DQPRE,"string with "],[:VARIABLE,"v"],[:DQMID," and "],[:VARIABLE,"v"],[:DQPOST," (but no braces)"]],
%q["string with ${v} and ${v} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,"v"],[:DQMID," and "],[:VARIABLE,"v"],[:DQPOST," in braces"]],
%q["string with ${'a nested single quoted string'} inside it."] => [[:DQPRE,"string with "],[:STRING,'a nested single quoted string'],[:DQPOST,' inside it.']],
%q["string with ${['an array ',$v2]} in it."] => [[:DQPRE,"string with "],:LBRACK,[:STRING,"an array "],:COMMA,[:VARIABLE,"v2"],:RBRACK,[:DQPOST," in it."]],
%q{a simple "scanner" test} => [[:NAME,"a"],[:NAME,"simple"], [:STRING,"scanner"],[:NAME,"test"]],
%q{a simple 'single quote scanner' test} => [[:NAME,"a"],[:NAME,"simple"], [:STRING,"single quote scanner"],[:NAME,"test"]],
%q{a harder 'a $b \c"'} => [[:NAME,"a"],[:NAME,"harder"], [:STRING,'a $b \c"']],
%q{a harder "scanner test"} => [[:NAME,"a"],[:NAME,"harder"], [:STRING,"scanner test"]],
%q{a hardest "scanner \"test\""} => [[:NAME,"a"],[:NAME,"hardest"],[:STRING,'scanner "test"']],
%Q{a hardestest "scanner \\"test\\"\n"} => [[:NAME,"a"],[:NAME,"hardestest"],[:STRING,%Q{scanner "test"\n}]],
%q{function("call")} => [[:NAME,"function"],[:LPAREN,"("],[:STRING,'call'],[:RPAREN,")"]],
%q["string with ${(3+5)/4} nested math."] => [[:DQPRE,"string with "],:LPAREN,[:NAME,"3"],:PLUS,[:NAME,"5"],:RPAREN,:DIV,[:NAME,"4"],[:DQPOST," nested math."]],
%q["$$$$"] => [[:STRING,"$$$$"]],
%q["$variable"] => [[:DQPRE,""],[:VARIABLE,"variable"],[:DQPOST,""]],
%q["$var$other"] => [[:DQPRE,""],[:VARIABLE,"var"],[:DQMID,""],[:VARIABLE,"other"],[:DQPOST,""]],
%q["foo$bar$"] => [[:DQPRE,"foo"],[:VARIABLE,"bar"],[:DQPOST,"$"]],
%q["foo$$bar"] => [[:DQPRE,"foo$"],[:VARIABLE,"bar"],[:DQPOST,""]],
%q[""] => [[:STRING,""]],
}.each { |src,expected_result|
it "should handle #{src} correctly" do
tokens_scanned_from(src).should be_like(*expected_result)
end
}
end
describe Puppet::Parser::Lexer::TOKENS[:DOLLAR_VAR] do
before { @token = Puppet::Parser::Lexer::TOKENS[:DOLLAR_VAR] }
it "should match against alpha words prefixed with '$'" do
@token.regex.should =~ '$this_var'
end
it "should return the VARIABLE token and the variable name stripped of the '$'" do
@token.convert(stub("lexer"), "$myval").should == [Puppet::Parser::Lexer::TOKENS[:VARIABLE], "myval"]
end
end
describe Puppet::Parser::Lexer::TOKENS[:REGEX] do
before { @token = Puppet::Parser::Lexer::TOKENS[:REGEX] }
it "should match against any expression enclosed in //" do
@token.regex.should =~ '/this is a regex/'
end
it 'should not match if there is \n in the regex' do
@token.regex.should_not =~ "/this is \n a regex/"
end
describe "when scanning" do
it "should not consider escaped slashes to be the end of a regex" do
tokens_scanned_from("$x =~ /this \\/ foo/").should be_like(__,__,[:REGEX,%r{this / foo}])
end
it "should not lex chained division as a regex" do
tokens_scanned_from("$x = $a/$b/$c").collect { |name, data| name }.should_not be_include( :REGEX )
end
it "should accept a regular expression after NODE" do
tokens_scanned_from("node /www.*\.mysite\.org/").should be_like(__,[:REGEX,Regexp.new("www.*\.mysite\.org")])
end
it "should accept regular expressions in a CASE" do
s = %q{case $variable {
"something": {$othervar = 4096 / 2}
/regex/: {notice("this notably sucks")}
}
}
tokens_scanned_from(s).should be_like(
:CASE,:VARIABLE,:LBRACE,:STRING,:COLON,:LBRACE,:VARIABLE,:EQUALS,:NAME,:DIV,:NAME,:RBRACE,[:REGEX,/regex/],:COLON,:LBRACE,:NAME,:LPAREN,:STRING,:RPAREN,:RBRACE,:RBRACE
)
end
end
it "should return the REGEX token and a Regexp" do
@token.convert(stub("lexer"), "/myregex/").should == [Puppet::Parser::Lexer::TOKENS[:REGEX], Regexp.new(/myregex/)]
end
end
describe Puppet::Parser::Lexer, "when lexing comments" do
before { @lexer = Puppet::Parser::Lexer.new }
it "should accumulate token in munge_token" do
token = stub 'token', :skip => true, :accumulate? => true, :incr_line => nil, :skip_text => false
token.stubs(:convert).with(@lexer, "# this is a comment").returns([token, " this is a comment"])
@lexer.munge_token(token, "# this is a comment")
@lexer.munge_token(token, "# this is a comment")
@lexer.getcomment.should == " this is a comment\n this is a comment\n"
end
it "should add a new comment stack level on LBRACE" do
@lexer.string = "{"
@lexer.expects(:commentpush)
@lexer.fullscan
end
it "should return the current comments on getcomment" do
@lexer.string = "# comment"
@lexer.fullscan
@lexer.getcomment.should == "comment\n"
end
it "should discard the previous comments on blank line" do
@lexer.string = "# 1\n\n# 2"
@lexer.fullscan
@lexer.getcomment.should == "2\n"
end
it "should skip whitespace before lexing the next token after a non-token" do
tokens_scanned_from("/* 1\n\n */ \ntest").should be_like([:NAME, "test"])
end
it "should not return comments seen after the current line" do
@lexer.string = "# 1\n\n# 2"
@lexer.fullscan
@lexer.getcomment(1).should == ""
end
it "should return a comment seen before the current line" do
@lexer.string = "# 1\n# 2"
@lexer.fullscan
@lexer.getcomment(2).should == "1\n2\n"
end
end
# FIXME: We need to rewrite all of these tests, but I just don't want to take the time right now.
describe "Puppet::Parser::Lexer in the old tests" do
before { @lexer = Puppet::Parser::Lexer.new }
it "should do simple lexing" do
{
%q{\\} => [[:BACKSLASH,"\\"]],
%q{simplest scanner test} => [[:NAME,"simplest"],[:NAME,"scanner"],[:NAME,"test"]],
%Q{returned scanner test\n} => [[:NAME,"returned"],[:NAME,"scanner"],[:NAME,"test"]]
}.each { |source,expected|
tokens_scanned_from(source).should be_like(*expected)
}
end
it "should fail usefully" do
lambda { tokens_scanned_from('^') }.should raise_error(RuntimeError)
end
it "should fail if the string is not set" do
lambda { @lexer.fullscan }.should raise_error(Puppet::LexError)
end
it "should correctly identify keywords" do
tokens_scanned_from("case").should be_like([:CASE, "case"])
end
it "should correctly parse class references" do
%w{Many Different Words A Word}.each { |t| tokens_scanned_from(t).should be_like([:CLASSREF,t])}
end
# #774
it "should correctly parse namespaced class refernces token" do
%w{Foo ::Foo Foo::Bar ::Foo::Bar}.each { |t| tokens_scanned_from(t).should be_like([:CLASSREF, t]) }
end
it "should correctly parse names" do
%w{this is a bunch of names}.each { |t| tokens_scanned_from(t).should be_like([:NAME,t]) }
end
it "should correctly parse names with numerals" do
%w{1name name1 11names names11}.each { |t| tokens_scanned_from(t).should be_like([:NAME,t]) }
end
it "should correctly parse empty strings" do
lambda { tokens_scanned_from('$var = ""') }.should_not raise_error
end
it "should correctly parse virtual resources" do
tokens_scanned_from("@type {").should be_like([:AT, "@"], [:NAME, "type"], [:LBRACE, "{"])
end
it "should correctly deal with namespaces" do
@lexer.string = %{class myclass}
@lexer.fullscan
@lexer.namespace.should == "myclass"
@lexer.namepop
@lexer.namespace.should == ""
@lexer.string = "class base { class sub { class more"
@lexer.fullscan
@lexer.namespace.should == "base::sub::more"
@lexer.namepop
@lexer.namespace.should == "base::sub"
end
it "should not put class instantiation on the namespace" do
@lexer.string = "class base { class sub { class { mode"
@lexer.fullscan
@lexer.namespace.should == "base::sub"
end
it "should correctly handle fully qualified names" do
@lexer.string = "class base { class sub::more {"
@lexer.fullscan
@lexer.namespace.should == "base::sub::more"
@lexer.namepop
@lexer.namespace.should == "base"
end
it "should correctly lex variables" do
["$variable", "$::variable", "$qualified::variable", "$further::qualified::variable"].each do |string|
tokens_scanned_from(string).should be_like([:VARIABLE,string.sub(/^\$/,'')])
end
end
end
-require 'puppettest/support/utils'
+require File.dirname(__FILE__) + '/../../../test/lib/puppettest'
+require File.dirname(__FILE__) + '/../../../test/lib/puppettest/support/utils'
describe "Puppet::Parser::Lexer in the old tests when lexing example files" do
extend PuppetTest::Support::Utils
textfiles do |file|
it "should correctly lex #{file}" do
lexer = Puppet::Parser::Lexer.new
lexer.file = file
lambda { lexer.fullscan }.should_not raise_error
end
end
end
diff --git a/spec/unit/provider/mount/parsed_spec.rb b/spec/unit/provider/mount/parsed_spec.rb
index 7d2e8a84c..5a1c986b1 100755
--- a/spec/unit/provider/mount/parsed_spec.rb
+++ b/spec/unit/provider/mount/parsed_spec.rb
@@ -1,193 +1,192 @@
#!/usr/bin/env ruby
#
# Created by Luke Kanies on 2007-9-12.
# Copyright (c) 2006. All rights reserved.
require File.dirname(__FILE__) + '/../../../spec_helper'
require 'puppettest/support/utils'
require 'puppettest/fileparsing'
module ParsedMountTesting
include PuppetTest::Support::Utils
include PuppetTest::FileParsing
def fake_fstab
os = Facter['operatingsystem']
if os == "Solaris"
name = "solaris.fstab"
elsif os == "FreeBSD"
name = "freebsd.fstab"
else
# Catchall for other fstabs
name = "linux.fstab"
end
oldpath = @provider_class.default_target
fakefile(File::join("data/types/mount", name))
end
def mkmountargs
mount = nil
if defined?(@pcount)
@pcount += 1
else
@pcount = 1
end
args = {
:name => "/fspuppet#{@pcount}",
:device => "/dev/dsk#{@pcount}",
}
@provider_class.fields(:parsed).each do |field|
args[field] = "fake#{field}#{@pcount}" unless args.include? field
end
args
end
def mkmount
hash = mkmountargs
#hash[:provider] = @provider_class.name
fakeresource = stub :type => :mount, :name => hash[:name]
fakeresource.stubs(:[]).with(:name).returns(hash[:name])
fakeresource.stubs(:should).with(:target).returns(nil)
mount = @provider_class.new(fakeresource)
hash[:record_type] = :parsed
hash[:ensure] = :present
mount.property_hash = hash
mount
end
# Here we just create a fake host type that answers to all of the methods
# but does not modify our actual system.
def mkfaketype
@provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
end
end
provider_class = Puppet::Type.type(:mount).provider(:parsed)
describe provider_class do
before :each do
@mount_class = Puppet::Type.type(:mount)
@provider_class = @mount_class.provider(:parsed)
end
describe provider_class do
include ParsedMountTesting
it "should be able to parse all of the example mount tabs" do
tab = fake_fstab
@provider = @provider_class
# LAK:FIXME Again, a relatively bad test, but I don't know how to rspec-ify this.
# I suppose this is more of an integration test? I dunno.
fakedataparse(tab) do
# Now just make we've got some mounts we know will be there
hashes = @provider_class.target_records(tab).find_all { |i| i.is_a? Hash }
(hashes.length > 0).should be_true
root = hashes.find { |i| i[:name] == "/" }
proc { @provider_class.to_file(hashes) }.should_not raise_error
end
end
# LAK:FIXME I can't mock Facter because this test happens at parse-time.
it "should default to /etc/vfstab on Solaris and /etc/fstab everywhere else" do
should = case Facter.value(:operatingsystem)
when "Solaris"; "/etc/vfstab"
else
"/etc/fstab"
end
Puppet::Type.type(:mount).provider(:parsed).default_target.should == should
end
it "should not crash on incomplete lines in fstab" do
parse = @provider_class.parse <<-FSTAB
/dev/incomplete
/dev/device name
FSTAB
lambda{ @provider_class.to_line(parse[0]) }.should_not raise_error
end
end
describe provider_class, " when mounting an absent filesystem" do
include ParsedMountTesting
# #730 - Make sure 'flush' is called when a mount is moving from absent to mounted
it "should flush the fstab to disk" do
mount = mkmount
# Mark the mount as absent
mount.property_hash[:ensure] = :absent
mount.stubs(:mountcmd) # just so we don't actually try to mount anything
mount.expects(:flush)
mount.mount
end
end
describe provider_class, " when modifying the filesystem tab" do
include ParsedMountTesting
before do
Puppet.settings.stubs(:use)
# Never write to disk, only to RAM.
#@provider_class.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
@provider_class.stubs(:target_object).returns(Puppet::Util::FileType.filetype(:ram).new("eh"))
@provider_class.clear
@mount = mkmount
@target = @provider_class.default_target
end
it "should write the mount to disk when :flush is called" do
old_text = @provider_class.target_object(@provider_class.default_target).read
@mount.flush
text = @provider_class.target_object(@provider_class.default_target).read
text.should == old_text + @mount.class.to_line(@mount.property_hash) + "\n"
end
end
- describe provider_class, " when parsing information about the root filesystem" do
- confine "Mount type not tested on Darwin" => Facter["operatingsystem"].value != "Darwin"
+ describe provider_class, " when parsing information about the root filesystem", :if => Facter["operatingsystem"].value != "Darwin" do
include ParsedMountTesting
before do
@mount = @mount_class.new :name => "/"
@provider = @mount.provider
end
it "should have a filesystem tab" do
FileTest.should be_exist(@provider_class.default_target)
end
it "should find the root filesystem" do
@provider_class.prefetch("/" => @mount)
@mount.provider.property_hash[:ensure].should == :present
end
it "should determine that the root fs is mounted" do
@provider_class.prefetch("/" => @mount)
@mount.provider.should be_mounted
end
end
describe provider_class, " when mounting and unmounting" do
include ParsedMountTesting
it "should call the 'mount' command to mount the filesystem"
it "should call the 'unmount' command to unmount the filesystem"
it "should specify the filesystem when remounting a filesystem"
end
end
diff --git a/spec/unit/rails/host_spec.rb b/spec/unit/rails/host_spec.rb
index 324a673a9..4244f117f 100755
--- a/spec/unit/rails/host_spec.rb
+++ b/spec/unit/rails/host_spec.rb
@@ -1,163 +1,161 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
-describe "Puppet::Rails::Host" do
- confine "Cannot test without ActiveRecord" => Puppet.features.rails?
-
+describe "Puppet::Rails::Host", :if => Puppet.features.rails? do
def column(name, type)
ActiveRecord::ConnectionAdapters::Column.new(name, nil, type, false)
end
before do
require 'puppet/rails/host'
# Stub this so we don't need access to the DB.
Puppet::Rails::Host.stubs(:columns).returns([column("name", "string"), column("environment", "string"), column("ip", "string")])
@node = Puppet::Node.new("foo")
@node.environment = "production"
@node.ipaddress = "127.0.0.1"
@host = stub 'host', :environment= => nil, :ip= => nil
end
describe "when converting a Puppet::Node instance into a Rails instance" do
it "should modify any existing instance in the database" do
Puppet::Rails::Host.expects(:find_by_name).with("foo").returns @host
Puppet::Rails::Host.from_puppet(@node)
end
it "should create a new instance in the database if none can be found" do
Puppet::Rails::Host.expects(:find_by_name).with("foo").returns nil
Puppet::Rails::Host.expects(:new).with(:name => "foo").returns @host
Puppet::Rails::Host.from_puppet(@node)
end
it "should copy the environment from the Puppet instance" do
Puppet::Rails::Host.expects(:find_by_name).with("foo").returns @host
@node.environment = "production"
@host.expects(:environment=).with {|x| x.name.to_s == 'production' }
Puppet::Rails::Host.from_puppet(@node)
end
it "should copy the ipaddress from the Puppet instance" do
Puppet::Rails::Host.expects(:find_by_name).with("foo").returns @host
@node.ipaddress = "192.168.0.1"
@host.expects(:ip=).with "192.168.0.1"
Puppet::Rails::Host.from_puppet(@node)
end
it "should not save the Rails instance" do
Puppet::Rails::Host.expects(:find_by_name).with("foo").returns @host
@host.expects(:save).never
Puppet::Rails::Host.from_puppet(@node)
end
end
describe "when converting a Puppet::Rails::Host instance into a Puppet::Node instance" do
before do
@host = Puppet::Rails::Host.new(:name => "foo", :environment => "production", :ip => "127.0.0.1")
@node = Puppet::Node.new("foo")
Puppet::Node.stubs(:new).with("foo").returns @node
end
it "should create a new instance with the correct name" do
Puppet::Node.expects(:new).with("foo").returns @node
@host.to_puppet
end
it "should copy the environment from the Rails instance" do
@host.environment = "prod"
@node.expects(:environment=).with "prod"
@host.to_puppet
end
it "should copy the ipaddress from the Rails instance" do
@host.ip = "192.168.0.1"
@node.expects(:ipaddress=).with "192.168.0.1"
@host.to_puppet
end
end
describe "when merging catalog resources and database resources" do
before :each do
Puppet.settings.stubs(:[]).with(:thin_storeconfigs).returns(false)
@resource1 = stub_everything 'res1'
@resource2 = stub_everything 'res2'
@resources = [ @resource1, @resource2 ]
@dbresource1 = stub_everything 'dbres1'
@dbresource2 = stub_everything 'dbres2'
@dbresources = { 1 => @dbresource1, 2 => @dbresource2 }
@host = Puppet::Rails::Host.new(:name => "foo", :environment => "production", :ip => "127.0.0.1")
@host.stubs(:find_resources).returns(@dbresources)
@host.stubs(:find_resources_parameters_tags)
@host.stubs(:compare_to_catalog)
@host.stubs(:id).returns(1)
end
it "should find all database resources" do
@host.expects(:find_resources)
@host.merge_resources(@resources)
end
it "should find all paramaters and tags for those database resources" do
@host.expects(:find_resources_parameters_tags).with(@dbresources)
@host.merge_resources(@resources)
end
it "should compare all database resources to catalog" do
@host.expects(:compare_to_catalog).with(@dbresources, @resources)
@host.merge_resources(@resources)
end
it "should compare only exported resources in thin_storeconfigs mode" do
Puppet.settings.stubs(:[]).with(:thin_storeconfigs).returns(true)
@resource1.stubs(:exported?).returns(true)
@host.expects(:compare_to_catalog).with(@dbresources, [ @resource1 ])
@host.merge_resources(@resources)
end
end
describe "when searching the database for host resources" do
before :each do
Puppet.settings.stubs(:[]).with(:thin_storeconfigs).returns(false)
@resource1 = stub_everything 'res1', :id => 1
@resource2 = stub_everything 'res2', :id => 2
@resources = [ @resource1, @resource2 ]
@dbresources = stub 'resources'
@dbresources.stubs(:find).returns(@resources)
@host = Puppet::Rails::Host.new(:name => "foo", :environment => "production", :ip => "127.0.0.1")
@host.stubs(:resources).returns(@dbresources)
end
it "should return a hash keyed by id of all resources" do
@host.find_resources.should == { 1 => @resource1, 2 => @resource2 }
end
it "should return a hash keyed by id of only exported resources in thin_storeconfigs mode" do
Puppet.settings.stubs(:[]).with(:thin_storeconfigs).returns(true)
@dbresources.expects(:find).with { |*h| h[1][:conditions] == { :exported => true } }.returns([])
@host.find_resources
end
end
end
diff --git a/spec/unit/rails/param_value_spec.rb b/spec/unit/rails/param_value_spec.rb
index 243456e89..f67022a14 100755
--- a/spec/unit/rails/param_value_spec.rb
+++ b/spec/unit/rails/param_value_spec.rb
@@ -1,50 +1,48 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/rails'
-describe "Puppet::Rails::ParamValue" do
- confine "Cannot test without ActiveRecord" => Puppet.features.rails?
-
+describe "Puppet::Rails::ParamValue", :if => Puppet.features.rails? do
def column(name, type)
ActiveRecord::ConnectionAdapters::Column.new(name, nil, type, false)
end
before do
require 'puppet/rails/param_value'
name = stub 'param_name', :name => "foo"
# Stub this so we don't need access to the DB.
Puppet::Rails::ParamValue.stubs(:columns).returns([column("value", "string")])
Puppet::Rails::ParamName.stubs(:find_or_create_by_name).returns(name)
end
describe "when creating initial parameter values" do
it "should return an array of hashes" do
Puppet::Rails::ParamValue.from_parser_param(:myparam, %w{a b})[0].should be_instance_of(Hash)
end
it "should return hashes for each value with the parameter name set as the ParamName instance" do
name = stub 'param_name', :name => "foo"
Puppet::Rails::ParamName.expects(:find_or_create_by_name).returns(name)
result = Puppet::Rails::ParamValue.from_parser_param(:myparam, "a")[0]
result[:value].should == "a"
result[:param_name].should == name
end
it "should return an array of hashes even when only one parameter is provided" do
Puppet::Rails::ParamValue.from_parser_param(:myparam, "a")[0].should be_instance_of(Hash)
end
it "should convert all arguments into strings" do
Puppet::Rails::ParamValue.from_parser_param(:myparam, 50)[0][:value].should == "50"
end
it "should not convert Resource References into strings" do
ref = Puppet::Resource.new(:file, "/file")
Puppet::Rails::ParamValue.from_parser_param(:myparam, ref)[0][:value].should == ref
end
end
end
diff --git a/spec/unit/rails/resource_spec.rb b/spec/unit/rails/resource_spec.rb
index 6e23d2020..e5bd8a6c9 100755
--- a/spec/unit/rails/resource_spec.rb
+++ b/spec/unit/rails/resource_spec.rb
@@ -1,124 +1,122 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/rails'
-describe "Puppet::Rails::Resource" do
- confine "Cannot test without ActiveRecord" => Puppet.features.rails?
-
+describe "Puppet::Rails::Resource", :if => Puppet.features.rails? do
def column(name, type)
ActiveRecord::ConnectionAdapters::Column.new(name, nil, type, false)
end
before do
require 'puppet/rails/resource'
# Stub this so we don't need access to the DB.
Puppet::Rails::Resource.stubs(:columns).returns([column("title", "string"), column("restype", "string"), column("exported", "boolean")])
end
describe "when creating initial resource arguments" do
it "should set the restype to the resource's type" do
Puppet::Rails::Resource.rails_resource_initial_args(Puppet::Resource.new(:file, "/file"))[:restype].should == "File"
end
it "should set the title to the resource's title" do
Puppet::Rails::Resource.rails_resource_initial_args(Puppet::Resource.new(:file, "/file"))[:title].should == "/file"
end
it "should set the line to the resource's line if one is available" do
resource = Puppet::Resource.new(:file, "/file")
resource.line = 50
Puppet::Rails::Resource.rails_resource_initial_args(resource)[:line].should == 50
end
it "should set 'exported' to true of the resource is exported" do
resource = Puppet::Resource.new(:file, "/file")
resource.exported = true
Puppet::Rails::Resource.rails_resource_initial_args(resource)[:exported].should be_true
end
it "should set 'exported' to false of the resource is not exported" do
resource = Puppet::Resource.new(:file, "/file")
resource.exported = false
Puppet::Rails::Resource.rails_resource_initial_args(resource)[:exported].should be_false
resource = Puppet::Resource.new(:file, "/file")
resource.exported = nil
Puppet::Rails::Resource.rails_resource_initial_args(resource)[:exported].should be_false
end
end
describe "when merging in a parser resource" do
before do
@parser = mock 'parser resource'
@resource = Puppet::Rails::Resource.new
[:merge_attributes, :merge_parameters, :merge_tags, :save].each { |m| @resource.stubs(m) }
end
it "should merge the attributes" do
@resource.expects(:merge_attributes).with(@parser)
@resource.merge_parser_resource(@parser)
end
it "should merge the parameters" do
@resource.expects(:merge_parameters).with(@parser)
@resource.merge_parser_resource(@parser)
end
it "should merge the tags" do
@resource.expects(:merge_tags).with(@parser)
@resource.merge_parser_resource(@parser)
end
it "should save itself" do
@resource.expects(:save)
@resource.merge_parser_resource(@parser)
end
end
describe "merge_parameters" do
it "should replace values that have changed" do
@resource = Puppet::Rails::Resource.new
@resource.params_list = [{"name" => "replace", "value" => 1, "id" => 100 }]
Puppet::Rails::ParamValue.expects(:delete).with([100])
param_values = stub "param_values"
param_values.expects(:build).with({:value=>nil, :param_name=>nil, :line=>{"replace"=>2}})
@resource.stubs(:param_values).returns(param_values)
Puppet::Rails::ParamName.stubs(:accumulate_by_name)
merge_resource = stub "merge_resource"
merge_resource.expects(:line).returns({ "replace" => 2 })
merge_resource.stubs(:each).yields([["replace", 2]])
@resource.merge_parameters(merge_resource)
end
end
describe "#to_resource" do
it "should instantiate a Puppet::Parser::Resource" do
scope = stub "scope", :source => nil, :environment => nil, :namespaces => nil
@resource = Puppet::Rails::Resource.new
@resource.stubs(:attributes).returns({
"restype" => 'notify',
"title" => 'hello'
})
@resource.stubs(:param_names).returns([])
@resource.to_resource(scope).should be_a(Puppet::Parser::Resource)
end
end
end
diff --git a/spec/unit/rails_spec.rb b/spec/unit/rails_spec.rb
index 24248e622..02b54efef 100755
--- a/spec/unit/rails_spec.rb
+++ b/spec/unit/rails_spec.rb
@@ -1,324 +1,314 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet/rails'
-describe Puppet::Rails, "when initializing any connection" do
- confine "Cannot test without ActiveRecord" => Puppet.features.rails?
-
+describe Puppet::Rails, "when initializing any connection", :if => Puppet.features.rails? do
before do
Puppet.settings.stubs(:use)
@logger = mock 'logger'
@logger.stub_everything
Logger.stubs(:new).returns(@logger)
ActiveRecord::Base.stubs(:logger).returns(@logger)
ActiveRecord::Base.stubs(:connected?).returns(false)
end
it "should use settings" do
Puppet.settings.expects(:use).with(:main, :rails, :master)
Puppet::Rails.connect
end
it "should set up a logger with the appropriate Rails log file" do
logger = mock 'logger'
Logger.expects(:new).with(Puppet[:railslog]).returns(logger)
ActiveRecord::Base.expects(:logger=).with(logger)
Puppet::Rails.connect
end
it "should set the log level to whatever the value is in the settings" do
Puppet.settings.stubs(:use)
Puppet.settings.stubs(:value).with(:rails_loglevel).returns("debug")
Puppet.settings.stubs(:value).with(:railslog).returns("/my/file")
logger = mock 'logger'
Logger.stubs(:new).returns(logger)
ActiveRecord::Base.stubs(:logger).returns(logger)
logger.expects(:level=).with(Logger::DEBUG)
ActiveRecord::Base.stubs(:allow_concurrency=)
ActiveRecord::Base.stubs(:verify_active_connections!)
ActiveRecord::Base.stubs(:establish_connection)
Puppet::Rails.stubs(:database_arguments).returns({})
Puppet::Rails.connect
end
describe "ActiveRecord Version" do
it "should set ActiveRecord::Base.allow_concurrency if ActiveRecord is 2.1" do
Puppet::Util.stubs(:activerecord_version).returns(2.1)
ActiveRecord::Base.expects(:allow_concurrency=).with(true)
Puppet::Rails.connect
end
it "should not set ActiveRecord::Base.allow_concurrency if ActiveRecord is >= 2.2" do
Puppet::Util.stubs(:activerecord_version).returns(2.2)
ActiveRecord::Base.expects(:allow_concurrency=).never
Puppet::Rails.connect
end
end
it "should call ActiveRecord::Base.verify_active_connections!" do
ActiveRecord::Base.expects(:verify_active_connections!)
Puppet::Rails.connect
end
it "should call ActiveRecord::Base.establish_connection with database_arguments" do
Puppet::Rails.expects(:database_arguments).returns({})
ActiveRecord::Base.expects(:establish_connection)
Puppet::Rails.connect
end
end
-describe Puppet::Rails, "when initializing a sqlite3 connection" do
- confine "Cannot test without ActiveRecord" => Puppet.features.rails?
-
+describe Puppet::Rails, "when initializing a sqlite3 connection", :if => Puppet.features.rails? do
it "should provide the adapter, log_level, and database arguments" do
Puppet.settings.expects(:value).with(:dbadapter).returns("sqlite3")
Puppet.settings.expects(:value).with(:rails_loglevel).returns("testlevel")
Puppet.settings.expects(:value).with(:dblocation).returns("testlocation")
Puppet::Rails.database_arguments.should == {
:adapter => "sqlite3",
:log_level => "testlevel",
:database => "testlocation"
}
end
end
-describe Puppet::Rails, "when initializing a mysql connection" do
- confine "Cannot test without ActiveRecord" => Puppet.features.rails?
-
+describe Puppet::Rails, "when initializing a mysql connection", :if => Puppet.features.rails? do
it "should provide the adapter, log_level, and host, port, username, password, database, and reconnect arguments" do
Puppet.settings.stubs(:value).with(:dbadapter).returns("mysql")
Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel")
Puppet.settings.stubs(:value).with(:dbserver).returns("testserver")
Puppet.settings.stubs(:value).with(:dbport).returns("")
Puppet.settings.stubs(:value).with(:dbuser).returns("testuser")
Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword")
Puppet.settings.stubs(:value).with(:dbconnections).returns((pool_size = 45).to_s)
Puppet.settings.stubs(:value).with(:dbname).returns("testname")
Puppet.settings.stubs(:value).with(:dbsocket).returns("")
Puppet::Rails.database_arguments.should == {
:adapter => "mysql",
:log_level => "testlevel",
:host => "testserver",
:username => "testuser",
:password => "testpassword",
:pool => pool_size,
:database => "testname",
:reconnect => true
}
end
it "should provide the adapter, log_level, and host, port, username, password, database, socket, connections, and reconnect arguments" do
Puppet.settings.stubs(:value).with(:dbadapter).returns("mysql")
Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel")
Puppet.settings.stubs(:value).with(:dbserver).returns("testserver")
Puppet.settings.stubs(:value).with(:dbport).returns("9999")
Puppet.settings.stubs(:value).with(:dbuser).returns("testuser")
Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword")
Puppet.settings.stubs(:value).with(:dbconnections).returns((pool_size = 12).to_s)
Puppet.settings.stubs(:value).with(:dbname).returns("testname")
Puppet.settings.stubs(:value).with(:dbsocket).returns("testsocket")
Puppet::Rails.database_arguments.should == {
:adapter => "mysql",
:log_level => "testlevel",
:host => "testserver",
:port => "9999",
:username => "testuser",
:password => "testpassword",
:pool => pool_size,
:database => "testname",
:socket => "testsocket",
:reconnect => true
}
end
it "should provide the adapter, log_level, and host, port, username, password, database, socket, and connections arguments" do
Puppet.settings.stubs(:value).with(:dbadapter).returns("mysql")
Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel")
Puppet.settings.stubs(:value).with(:dbserver).returns("testserver")
Puppet.settings.stubs(:value).with(:dbport).returns("9999")
Puppet.settings.stubs(:value).with(:dbuser).returns("testuser")
Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword")
Puppet.settings.stubs(:value).with(:dbconnections).returns((pool_size = 23).to_s)
Puppet.settings.stubs(:value).with(:dbname).returns("testname")
Puppet.settings.stubs(:value).with(:dbsocket).returns("testsocket")
Puppet::Rails.database_arguments.should == {
:adapter => "mysql",
:log_level => "testlevel",
:host => "testserver",
:port => "9999",
:username => "testuser",
:password => "testpassword",
:pool => pool_size,
:database => "testname",
:socket => "testsocket",
:reconnect => true
}
end
it "should not provide the pool if dbconnections is 0, '0', or ''" do
Puppet.settings.stubs(:value).with(:dbadapter).returns("mysql")
Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel")
Puppet.settings.stubs(:value).with(:dbserver).returns("testserver")
Puppet.settings.stubs(:value).with(:dbport).returns("9999")
Puppet.settings.stubs(:value).with(:dbuser).returns("testuser")
Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword")
Puppet.settings.stubs(:value).with(:dbname).returns("testname")
Puppet.settings.stubs(:value).with(:dbsocket).returns("testsocket")
Puppet.settings.stubs(:value).with(:dbconnections).returns(0)
Puppet::Rails.database_arguments.should_not be_include(:pool)
Puppet.settings.stubs(:value).with(:dbconnections).returns('0')
Puppet::Rails.database_arguments.should_not be_include(:pool)
Puppet.settings.stubs(:value).with(:dbconnections).returns('')
Puppet::Rails.database_arguments.should_not be_include(:pool)
end
end
-describe Puppet::Rails, "when initializing a postgresql connection" do
- confine "Cannot test without ActiveRecord" => Puppet.features.rails?
-
+describe Puppet::Rails, "when initializing a postgresql connection", :if => Puppet.features.rails? do
it "should provide the adapter, log_level, and host, port, username, password, connections, and database arguments" do
Puppet.settings.stubs(:value).with(:dbadapter).returns("postgresql")
Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel")
Puppet.settings.stubs(:value).with(:dbserver).returns("testserver")
Puppet.settings.stubs(:value).with(:dbport).returns("9999")
Puppet.settings.stubs(:value).with(:dbuser).returns("testuser")
Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword")
Puppet.settings.stubs(:value).with(:dbconnections).returns((pool_size = 200).to_s)
Puppet.settings.stubs(:value).with(:dbname).returns("testname")
Puppet.settings.stubs(:value).with(:dbsocket).returns("")
Puppet::Rails.database_arguments.should == {
:adapter => "postgresql",
:log_level => "testlevel",
:host => "testserver",
:port => "9999",
:username => "testuser",
:password => "testpassword",
:pool => pool_size,
:database => "testname",
:reconnect => true
}
end
it "should provide the adapter, log_level, and host, port, username, password, database, connections, and socket arguments" do
Puppet.settings.stubs(:value).with(:dbadapter).returns("postgresql")
Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel")
Puppet.settings.stubs(:value).with(:dbserver).returns("testserver")
Puppet.settings.stubs(:value).with(:dbport).returns("9999")
Puppet.settings.stubs(:value).with(:dbuser).returns("testuser")
Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword")
Puppet.settings.stubs(:value).with(:dbconnections).returns((pool_size = 122).to_s)
Puppet.settings.stubs(:value).with(:dbname).returns("testname")
Puppet.settings.stubs(:value).with(:dbsocket).returns("testsocket")
Puppet::Rails.database_arguments.should == {
:adapter => "postgresql",
:log_level => "testlevel",
:host => "testserver",
:port => "9999",
:username => "testuser",
:password => "testpassword",
:pool => pool_size,
:database => "testname",
:socket => "testsocket",
:reconnect => true
}
end
it "should not provide the pool if dbconnections is 0, '0', or ''" do
Puppet.settings.stubs(:value).with(:dbadapter).returns("mysql")
Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel")
Puppet.settings.stubs(:value).with(:dbserver).returns("testserver")
Puppet.settings.stubs(:value).with(:dbport).returns("9999")
Puppet.settings.stubs(:value).with(:dbuser).returns("testuser")
Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword")
Puppet.settings.stubs(:value).with(:dbname).returns("testname")
Puppet.settings.stubs(:value).with(:dbsocket).returns("testsocket")
Puppet.settings.stubs(:value).with(:dbconnections).returns(0)
Puppet::Rails.database_arguments.should_not be_include(:pool)
Puppet.settings.stubs(:value).with(:dbconnections).returns('0')
Puppet::Rails.database_arguments.should_not be_include(:pool)
Puppet.settings.stubs(:value).with(:dbconnections).returns('')
Puppet::Rails.database_arguments.should_not be_include(:pool)
end
end
-describe Puppet::Rails, "when initializing an Oracle connection" do
- confine "Cannot test without ActiveRecord" => Puppet.features.rails?
-
+describe Puppet::Rails, "when initializing an Oracle connection", :if => Puppet.features.rails? do
it "should provide the adapter, log_level, and username, password, and database arguments" do
Puppet.settings.stubs(:value).with(:dbadapter).returns("oracle_enhanced")
Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel")
Puppet.settings.stubs(:value).with(:dbuser).returns("testuser")
Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword")
Puppet.settings.stubs(:value).with(:dbconnections).returns((pool_size = 123).to_s)
Puppet.settings.stubs(:value).with(:dbname).returns("testname")
Puppet::Rails.database_arguments.should == {
:adapter => "oracle_enhanced",
:log_level => "testlevel",
:username => "testuser",
:password => "testpassword",
:pool => pool_size,
:database => "testname"
}
end
it "should provide the adapter, log_level, and host, username, password, database and socket arguments" do
Puppet.settings.stubs(:value).with(:dbadapter).returns("oracle_enhanced")
Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel")
Puppet.settings.stubs(:value).with(:dbuser).returns("testuser")
Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword")
Puppet.settings.stubs(:value).with(:dbconnections).returns((pool_size = 124).to_s)
Puppet.settings.stubs(:value).with(:dbname).returns("testname")
Puppet::Rails.database_arguments.should == {
:adapter => "oracle_enhanced",
:log_level => "testlevel",
:username => "testuser",
:password => "testpassword",
:pool => pool_size,
:database => "testname"
}
end
it "should not provide the pool if dbconnections is 0, '0', or ''" do
Puppet.settings.stubs(:value).with(:dbadapter).returns("mysql")
Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel")
Puppet.settings.stubs(:value).with(:dbserver).returns("testserver")
Puppet.settings.stubs(:value).with(:dbport).returns("9999")
Puppet.settings.stubs(:value).with(:dbuser).returns("testuser")
Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword")
Puppet.settings.stubs(:value).with(:dbname).returns("testname")
Puppet.settings.stubs(:value).with(:dbsocket).returns("testsocket")
Puppet.settings.stubs(:value).with(:dbconnections).returns(0)
Puppet::Rails.database_arguments.should_not be_include(:pool)
Puppet.settings.stubs(:value).with(:dbconnections).returns('0')
Puppet::Rails.database_arguments.should_not be_include(:pool)
Puppet.settings.stubs(:value).with(:dbconnections).returns('')
Puppet::Rails.database_arguments.should_not be_include(:pool)
end
end
diff --git a/spec/unit/relationship_spec.rb b/spec/unit/relationship_spec.rb
index 4586cd0f3..9ce6c56e7 100755
--- a/spec/unit/relationship_spec.rb
+++ b/spec/unit/relationship_spec.rb
@@ -1,236 +1,232 @@
#!/usr/bin/env ruby
#
# Created by Luke Kanies on 2007-11-1.
# Copyright (c) 2006. All rights reserved.
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet/relationship'
describe Puppet::Relationship do
before do
@edge = Puppet::Relationship.new(:a, :b)
end
it "should have a :source attribute" do
@edge.should respond_to(:source)
end
it "should have a :target attribute" do
@edge.should respond_to(:target)
end
it "should have a :callback attribute" do
@edge.callback = :foo
@edge.callback.should == :foo
end
it "should have an :event attribute" do
@edge.event = :NONE
@edge.event.should == :NONE
end
it "should require a callback if a non-NONE event is specified" do
proc { @edge.event = :something }.should raise_error(ArgumentError)
end
it "should have a :label attribute" do
@edge.should respond_to(:label)
end
it "should provide a :ref method that describes the edge" do
@edge = Puppet::Relationship.new("a", "b")
@edge.ref.should == "a => b"
end
it "should be able to produce a label as a hash with its event and callback" do
@edge.callback = :foo
@edge.event = :bar
@edge.label.should == {:callback => :foo, :event => :bar}
end
it "should work if nil options are provided" do
lambda { Puppet::Relationship.new("a", "b", nil) }.should_not raise_error
end
end
describe Puppet::Relationship, " when initializing" do
before do
@edge = Puppet::Relationship.new(:a, :b)
end
it "should use the first argument as the source" do
@edge.source.should == :a
end
it "should use the second argument as the target" do
@edge.target.should == :b
end
it "should set the rest of the arguments as the event and callback" do
@edge = Puppet::Relationship.new(:a, :b, :callback => :foo, :event => :bar)
@edge.callback.should == :foo
@edge.event.should == :bar
end
it "should accept events specified as strings" do
@edge = Puppet::Relationship.new(:a, :b, "event" => :NONE)
@edge.event.should == :NONE
end
it "should accept callbacks specified as strings" do
@edge = Puppet::Relationship.new(:a, :b, "callback" => :foo)
@edge.callback.should == :foo
end
end
describe Puppet::Relationship, " when matching edges with no specified event" do
before do
@edge = Puppet::Relationship.new(:a, :b)
end
it "should not match :NONE" do
@edge.should_not be_match(:NONE)
end
it "should not match :ALL_EVENTS" do
@edge.should_not be_match(:NONE)
end
it "should not match any other events" do
@edge.should_not be_match(:whatever)
end
end
describe Puppet::Relationship, " when matching edges with :NONE as the event" do
before do
@edge = Puppet::Relationship.new(:a, :b, :event => :NONE)
end
it "should not match :NONE" do
@edge.should_not be_match(:NONE)
end
it "should not match :ALL_EVENTS" do
@edge.should_not be_match(:ALL_EVENTS)
end
it "should not match other events" do
@edge.should_not be_match(:yayness)
end
end
describe Puppet::Relationship, " when matching edges with :ALL as the event" do
before do
@edge = Puppet::Relationship.new(:a, :b, :event => :ALL_EVENTS, :callback => :whatever)
end
it "should not match :NONE" do
@edge.should_not be_match(:NONE)
end
it "should match :ALL_EVENTS" do
@edge.should be_match(:ALLEVENTS)
end
it "should match all other events" do
@edge.should be_match(:foo)
end
end
describe Puppet::Relationship, " when matching edges with a non-standard event" do
before do
@edge = Puppet::Relationship.new(:a, :b, :event => :random, :callback => :whatever)
end
it "should not match :NONE" do
@edge.should_not be_match(:NONE)
end
it "should not match :ALL_EVENTS" do
@edge.should_not be_match(:ALL_EVENTS)
end
it "should match events with the same name" do
@edge.should be_match(:random)
end
end
-describe Puppet::Relationship, "when converting to pson" do
- confine "Missing 'pson' library" => Puppet.features.pson?
-
+describe Puppet::Relationship, "when converting to pson", :if => Puppet.features.pson? do
before do
@edge = Puppet::Relationship.new(:a, :b, :event => :random, :callback => :whatever)
end
it "should store the stringified source as the source in the data" do
PSON.parse(@edge.to_pson)["source"].should == "a"
end
it "should store the stringified target as the target in the data" do
PSON.parse(@edge.to_pson)['target'].should == "b"
end
it "should store the psonified event as the event in the data" do
PSON.parse(@edge.to_pson)["event"].should == "random"
end
it "should not store an event when none is set" do
@edge.event = nil
PSON.parse(@edge.to_pson)["event"].should be_nil
end
it "should store the psonified callback as the callback in the data" do
@edge.callback = "whatever"
PSON.parse(@edge.to_pson)["callback"].should == "whatever"
end
it "should not store a callback when none is set in the edge" do
@edge.callback = nil
PSON.parse(@edge.to_pson)["callback"].should be_nil
end
end
-describe Puppet::Relationship, "when converting from pson" do
- confine "Missing 'pson' library" => Puppet.features.pson?
-
+describe Puppet::Relationship, "when converting from pson", :if => Puppet.features.pson? do
before do
@event = "random"
@callback = "whatever"
@data = {
"source" => "mysource",
"target" => "mytarget",
"event" => @event,
"callback" => @callback
}
@pson = {
"type" => "Puppet::Relationship",
"data" => @data
}
end
def pson_result_should
Puppet::Relationship.expects(:new).with { |*args| yield args }
end
it "should be extended with the PSON utility module" do
Puppet::Relationship.singleton_class.ancestors.should be_include(Puppet::Util::Pson)
end
# LAK:NOTE For all of these tests, we convert back to the edge so we can
# trap the actual data structure then.
it "should pass the source in as the first argument" do
Puppet::Relationship.from_pson("source" => "mysource", "target" => "mytarget").source.should == "mysource"
end
it "should pass the target in as the second argument" do
Puppet::Relationship.from_pson("source" => "mysource", "target" => "mytarget").target.should == "mytarget"
end
it "should pass the event as an argument if it's provided" do
Puppet::Relationship.from_pson("source" => "mysource", "target" => "mytarget", "event" => "myevent", "callback" => "eh").event.should == "myevent"
end
it "should pass the callback as an argument if it's provided" do
Puppet::Relationship.from_pson("source" => "mysource", "target" => "mytarget", "callback" => "mycallback").callback.should == "mycallback"
end
end
diff --git a/spec/unit/reports/http_spec.rb b/spec/unit/reports/http_spec.rb
index c814975df..70742f7dc 100644
--- a/spec/unit/reports/http_spec.rb
+++ b/spec/unit/reports/http_spec.rb
@@ -1,56 +1,56 @@
#!/usr/bin/env ruby
Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
require 'puppet/reports'
# FakeHTTP fakes the behavior of Net::HTTP#request and acts as a sensor for an
# otherwise difficult to trace method call.
#
class FakeHTTP
REQUESTS = {}
def self.request(req)
REQUESTS[req.path] = req
end
end
processor = Puppet::Reports.report(:http)
describe processor do
before { Net::HTTP.any_instance.stubs(:start).yields(FakeHTTP) }
- subject { Puppet::Transaction::Report.new.extend(processor) }
+ subject { Puppet::Transaction::Report.new("apply").extend(processor) }
it { should respond_to(:process) }
it "should use the reporturl setting's host and port" do
uri = URI.parse(Puppet[:reporturl])
Net::HTTP.expects(:new).with(uri.host, uri.port).returns(stub_everything('http'))
subject.process
end
describe "request" do
before { subject.process }
describe "path" do
it "should use the path specified by the 'reporturl' setting" do
reports_request.path.should == URI.parse(Puppet[:reporturl]).path
end
end
describe "body" do
it "should be the report as YAML" do
reports_request.body.should == subject.to_yaml
end
end
describe "content type" do
it "should be 'application/x-yaml'" do
reports_request.content_type.should == "application/x-yaml"
end
end
end
private
def reports_request; FakeHTTP::REQUESTS[URI.parse(Puppet[:reporturl]).path] end
end
diff --git a/spec/unit/reports/tagmail_spec.rb b/spec/unit/reports/tagmail_spec.rb
index bdb16600e..1dadfc7cd 100755
--- a/spec/unit/reports/tagmail_spec.rb
+++ b/spec/unit/reports/tagmail_spec.rb
@@ -1,95 +1,95 @@
#!/usr/bin/env ruby
Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
require 'puppet/reports'
require 'puppettest/support/utils'
tagmail = Puppet::Reports.report(:tagmail)
describe tagmail do
extend PuppetTest::Support::Utils
before do
- @processor = Puppet::Transaction::Report.new
+ @processor = Puppet::Transaction::Report.new("apply")
@processor.extend(Puppet::Reports.report(:tagmail))
end
passers = File.join(datadir, "reports", "tagmail_passers.conf")
File.readlines(passers).each do |line|
it "should be able to parse '#{line.inspect}'" do
@processor.parse(line)
end
end
failers = File.join(datadir, "reports", "tagmail_failers.conf")
File.readlines(failers).each do |line|
it "should not be able to parse '#{line.inspect}'" do
lambda { @processor.parse(line) }.should raise_error(ArgumentError)
end
end
{
"tag: abuse@domain.com" => [%w{abuse@domain.com}, %w{tag}, []],
"tag.localhost: abuse@domain.com" => [%w{abuse@domain.com}, %w{tag.localhost}, []],
"tag, other: abuse@domain.com" => [%w{abuse@domain.com}, %w{tag other}, []],
"tag-other: abuse@domain.com" => [%w{abuse@domain.com}, %w{tag-other}, []],
"tag, !other: abuse@domain.com" => [%w{abuse@domain.com}, %w{tag}, %w{other}],
"tag, !other, one, !two: abuse@domain.com" => [%w{abuse@domain.com}, %w{tag one}, %w{other two}],
"tag: abuse@domain.com, other@domain.com" => [%w{abuse@domain.com other@domain.com}, %w{tag}, []]
}.each do |line, results|
it "should parse '#{line}' as #{results.inspect}" do
@processor.parse(line).shift.should == results
end
end
describe "when matching logs" do
before do
@processor << Puppet::Util::Log.new(:level => :notice, :message => "first", :tags => %w{one})
@processor << Puppet::Util::Log.new(:level => :notice, :message => "second", :tags => %w{one two})
@processor << Puppet::Util::Log.new(:level => :notice, :message => "third", :tags => %w{one two three})
end
def match(pos = [], neg = [])
pos = Array(pos)
neg = Array(neg)
result = @processor.match([[%w{abuse@domain.com}, pos, neg]])
actual_result = result.shift
if actual_result
actual_result[1]
else
nil
end
end
it "should match all messages when provided the 'all' tag as a positive matcher" do
results = match("all")
%w{first second third}.each do |str|
results.should be_include(str)
end
end
it "should remove messages that match a negated tag" do
match("all", "three").should_not be_include("third")
end
it "should find any messages tagged with a provided tag" do
results = match("two")
results.should be_include("second")
results.should be_include("third")
results.should_not be_include("first")
end
it "should allow negation of specific tags from a specific tag list" do
results = match("two", "three")
results.should be_include("second")
results.should_not be_include("third")
end
it "should allow a tag to negate all matches" do
results = match([], "one")
results.should be_nil
end
end
end
diff --git a/spec/unit/resource/catalog_spec.rb b/spec/unit/resource/catalog_spec.rb
index 2b6beb5e9..942721464 100755
--- a/spec/unit/resource/catalog_spec.rb
+++ b/spec/unit/resource/catalog_spec.rb
@@ -1,1078 +1,1074 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
describe Puppet::Resource::Catalog, "when compiling" do
before do
@basepath = Puppet.features.posix? ? "/somepath" : "C:/somepath"
# stub this to not try to create state.yaml
Puppet::Util::Storage.stubs(:store)
end
it "should be an Expirer" do
Puppet::Resource::Catalog.ancestors.should be_include(Puppet::Util::Cacher::Expirer)
end
it "should always be expired if it's not applying" do
@catalog = Puppet::Resource::Catalog.new("host")
@catalog.expects(:applying?).returns false
@catalog.should be_dependent_data_expired(Time.now)
end
it "should not be expired if it's applying and the timestamp is late enough" do
@catalog = Puppet::Resource::Catalog.new("host")
@catalog.expire
@catalog.expects(:applying?).returns true
@catalog.should_not be_dependent_data_expired(Time.now)
end
it "should be able to write its list of classes to the class file" do
@catalog = Puppet::Resource::Catalog.new("host")
@catalog.add_class "foo", "bar"
Puppet.settings.expects(:value).with(:classfile).returns "/class/file"
fh = mock 'filehandle'
File.expects(:open).with("/class/file", "w").yields fh
fh.expects(:puts).with "foo\nbar"
@catalog.write_class_file
end
it "should have a client_version attribute" do
@catalog = Puppet::Resource::Catalog.new("host")
@catalog.client_version = 5
@catalog.client_version.should == 5
end
it "should have a server_version attribute" do
@catalog = Puppet::Resource::Catalog.new("host")
@catalog.server_version = 5
@catalog.server_version.should == 5
end
describe "when compiling" do
it "should accept tags" do
config = Puppet::Resource::Catalog.new("mynode")
config.tag("one")
config.tags.should == %w{one}
end
it "should accept multiple tags at once" do
config = Puppet::Resource::Catalog.new("mynode")
config.tag("one", "two")
config.tags.should == %w{one two}
end
it "should convert all tags to strings" do
config = Puppet::Resource::Catalog.new("mynode")
config.tag("one", :two)
config.tags.should == %w{one two}
end
it "should tag with both the qualified name and the split name" do
config = Puppet::Resource::Catalog.new("mynode")
config.tag("one::two")
config.tags.include?("one").should be_true
config.tags.include?("one::two").should be_true
end
it "should accept classes" do
config = Puppet::Resource::Catalog.new("mynode")
config.add_class("one")
config.classes.should == %w{one}
config.add_class("two", "three")
config.classes.should == %w{one two three}
end
it "should tag itself with passed class names" do
config = Puppet::Resource::Catalog.new("mynode")
config.add_class("one")
config.tags.should == %w{one}
end
end
describe "when extracting transobjects" do
def mkscope
@node = Puppet::Node.new("mynode")
@compiler = Puppet::Parser::Compiler.new(@node)
# XXX This is ridiculous.
@compiler.send(:evaluate_main)
@scope = @compiler.topscope
end
def mkresource(type, name)
Puppet::Parser::Resource.new(type, name, :source => @source, :scope => @scope)
end
it "should fail if no 'main' stage can be found" do
lambda { Puppet::Resource::Catalog.new("mynode").extract }.should raise_error(Puppet::DevError)
end
it "should warn if any non-main stages are present" do
config = Puppet::Resource::Catalog.new("mynode")
@scope = mkscope
@source = mock 'source'
main = mkresource("stage", "main")
config.add_resource(main)
other = mkresource("stage", "other")
config.add_resource(other)
Puppet.expects(:warning)
config.extract
end
it "should always create a TransBucket for the 'main' stage" do
config = Puppet::Resource::Catalog.new("mynode")
@scope = mkscope
@source = mock 'source'
main = mkresource("stage", "main")
config.add_resource(main)
result = config.extract
result.type.should == "Stage"
result.name.should == "main"
end
# Now try it with a more complicated graph -- a three tier graph, each tier
it "should transform arbitrarily deep graphs into isomorphic trees" do
config = Puppet::Resource::Catalog.new("mynode")
@scope = mkscope
@scope.stubs(:tags).returns([])
@source = mock 'source'
# Create our scopes.
top = mkresource "stage", "main"
config.add_resource top
topbucket = []
topbucket.expects(:classes=).with([])
top.expects(:to_trans).returns(topbucket)
topres = mkresource "file", "/top"
topres.expects(:to_trans).returns(:topres)
config.add_edge top, topres
middle = mkresource "class", "middle"
middle.expects(:to_trans).returns([])
config.add_edge top, middle
midres = mkresource "file", "/mid"
midres.expects(:to_trans).returns(:midres)
config.add_edge middle, midres
bottom = mkresource "class", "bottom"
bottom.expects(:to_trans).returns([])
config.add_edge middle, bottom
botres = mkresource "file", "/bot"
botres.expects(:to_trans).returns(:botres)
config.add_edge bottom, botres
toparray = config.extract
# This is annoying; it should look like:
# [[[:botres], :midres], :topres]
# but we can't guarantee sort order.
toparray.include?(:topres).should be_true
midarray = toparray.find { |t| t.is_a?(Array) }
midarray.include?(:midres).should be_true
botarray = midarray.find { |t| t.is_a?(Array) }
botarray.include?(:botres).should be_true
end
end
describe " when converting to a Puppet::Resource catalog" do
before do
@original = Puppet::Resource::Catalog.new("mynode")
@original.tag(*%w{one two three})
@original.add_class *%w{four five six}
@top = Puppet::TransObject.new 'top', "class"
@topobject = Puppet::TransObject.new '/topobject', "file"
@middle = Puppet::TransObject.new 'middle', "class"
@middleobject = Puppet::TransObject.new '/middleobject', "file"
@bottom = Puppet::TransObject.new 'bottom', "class"
@bottomobject = Puppet::TransObject.new '/bottomobject', "file"
@resources = [@top, @topobject, @middle, @middleobject, @bottom, @bottomobject]
@original.add_resource(*@resources)
@original.add_edge(@top, @topobject)
@original.add_edge(@top, @middle)
@original.add_edge(@middle, @middleobject)
@original.add_edge(@middle, @bottom)
@original.add_edge(@bottom, @bottomobject)
@catalog = @original.to_resource
end
it "should copy over the version" do
@original.version = "foo"
@original.to_resource.version.should == "foo"
end
it "should convert parser resources to plain resources" do
resource = Puppet::Parser::Resource.new(:file, "foo", :scope => stub("scope", :environment => nil, :namespaces => nil), :source => stub("source"))
catalog = Puppet::Resource::Catalog.new("whev")
catalog.add_resource(resource)
new = catalog.to_resource
new.resource(:file, "foo").class.should == Puppet::Resource
end
it "should add all resources as Puppet::Resource instances" do
@resources.each { |resource| @catalog.resource(resource.ref).should be_instance_of(Puppet::Resource) }
end
it "should copy the tag list to the new catalog" do
@catalog.tags.sort.should == @original.tags.sort
end
it "should copy the class list to the new catalog" do
@catalog.classes.should == @original.classes
end
it "should duplicate the original edges" do
@original.edges.each do |edge|
@catalog.edge?(@catalog.resource(edge.source.ref), @catalog.resource(edge.target.ref)).should be_true
end
end
it "should set itself as the catalog for each converted resource" do
@catalog.vertices.each { |v| v.catalog.object_id.should equal(@catalog.object_id) }
end
end
describe "when converting to a RAL catalog" do
before do
@original = Puppet::Resource::Catalog.new("mynode")
@original.tag(*%w{one two three})
@original.add_class *%w{four five six}
@top = Puppet::Resource.new :class, 'top'
@topobject = Puppet::Resource.new :file, @basepath+'/topobject'
@middle = Puppet::Resource.new :class, 'middle'
@middleobject = Puppet::Resource.new :file, @basepath+'/middleobject'
@bottom = Puppet::Resource.new :class, 'bottom'
@bottomobject = Puppet::Resource.new :file, @basepath+'/bottomobject'
@resources = [@top, @topobject, @middle, @middleobject, @bottom, @bottomobject]
@original.add_resource(*@resources)
@original.add_edge(@top, @topobject)
@original.add_edge(@top, @middle)
@original.add_edge(@middle, @middleobject)
@original.add_edge(@middle, @bottom)
@original.add_edge(@bottom, @bottomobject)
@catalog = @original.to_ral
end
it "should add all resources as RAL instances" do
@resources.each { |resource| @catalog.resource(resource.ref).should be_instance_of(Puppet::Type) }
end
it "should copy the tag list to the new catalog" do
@catalog.tags.sort.should == @original.tags.sort
end
it "should copy the class list to the new catalog" do
@catalog.classes.should == @original.classes
end
it "should duplicate the original edges" do
@original.edges.each do |edge|
@catalog.edge?(@catalog.resource(edge.source.ref), @catalog.resource(edge.target.ref)).should be_true
end
end
it "should set itself as the catalog for each converted resource" do
@catalog.vertices.each { |v| v.catalog.object_id.should equal(@catalog.object_id) }
end
# This tests #931.
it "should not lose track of resources whose names vary" do
changer = Puppet::TransObject.new 'changer', 'test'
config = Puppet::Resource::Catalog.new('test')
config.add_resource(changer)
config.add_resource(@top)
config.add_edge(@top, changer)
resource = stub 'resource', :name => "changer2", :title => "changer2", :ref => "Test[changer2]", :catalog= => nil, :remove => nil
#changer is going to get duplicated as part of a fix for aliases 1094
changer.expects(:dup).returns(changer)
changer.expects(:to_ral).returns(resource)
newconfig = nil
proc { @catalog = config.to_ral }.should_not raise_error
@catalog.resource("Test[changer2]").should equal(resource)
end
after do
# Remove all resource instances.
@catalog.clear(true)
end
end
describe "when filtering" do
before :each do
@original = Puppet::Resource::Catalog.new("mynode")
@original.tag(*%w{one two three})
@original.add_class *%w{four five six}
@r1 = stub_everything 'r1', :ref => "File[/a]"
@r1.stubs(:respond_to?).with(:ref).returns(true)
@r1.stubs(:dup).returns(@r1)
@r1.stubs(:is_a?).returns(Puppet::Resource).returns(true)
@r2 = stub_everything 'r2', :ref => "File[/b]"
@r2.stubs(:respond_to?).with(:ref).returns(true)
@r2.stubs(:dup).returns(@r2)
@r2.stubs(:is_a?).returns(Puppet::Resource).returns(true)
@resources = [@r1,@r2]
@original.add_resource(@r1,@r2)
end
it "should transform the catalog to a resource catalog" do
@original.expects(:to_catalog).with { |h,b| h == :to_resource }
@original.filter
end
it "should scan each catalog resource in turn and apply filtering block" do
@resources.each { |r| r.expects(:test?) }
@original.filter do |r|
r.test?
end
end
it "should filter out resources which produce true when the filter block is evaluated" do
@original.filter do |r|
r == @r1
end.resource("File[/a]").should be_nil
end
it "should not consider edges against resources that were filtered out" do
@original.add_edge(@r1,@r2)
@original.filter do |r|
r == @r1
end.edge(@r1,@r2).should be_empty
end
end
describe "when functioning as a resource container" do
before do
@catalog = Puppet::Resource::Catalog.new("host")
@one = Puppet::Type.type(:notify).new :name => "one"
@two = Puppet::Type.type(:notify).new :name => "two"
@dupe = Puppet::Type.type(:notify).new :name => "one"
end
it "should provide a method to add one or more resources" do
@catalog.add_resource @one, @two
@catalog.resource(@one.ref).should equal(@one)
@catalog.resource(@two.ref).should equal(@two)
end
it "should add resources to the relationship graph if it exists" do
relgraph = @catalog.relationship_graph
@catalog.add_resource @one
relgraph.should be_vertex(@one)
end
it "should yield added resources if a block is provided" do
yielded = []
@catalog.add_resource(@one, @two) { |r| yielded << r }
yielded.length.should == 2
end
it "should set itself as the resource's catalog if it is not a relationship graph" do
@one.expects(:catalog=).with(@catalog)
@catalog.add_resource @one
end
it "should make all vertices available by resource reference" do
@catalog.add_resource(@one)
@catalog.resource(@one.ref).should equal(@one)
@catalog.vertices.find { |r| r.ref == @one.ref }.should equal(@one)
end
it "should canonize how resources are referred to during retrieval when both type and title are provided" do
@catalog.add_resource(@one)
@catalog.resource("notify", "one").should equal(@one)
end
it "should canonize how resources are referred to during retrieval when just the title is provided" do
@catalog.add_resource(@one)
@catalog.resource("notify[one]", nil).should equal(@one)
end
it "should not allow two resources with the same resource reference" do
@catalog.add_resource(@one)
proc { @catalog.add_resource(@dupe) }.should raise_error(Puppet::Resource::Catalog::DuplicateResourceError)
end
it "should not store objects that do not respond to :ref" do
proc { @catalog.add_resource("thing") }.should raise_error(ArgumentError)
end
it "should remove all resources when asked" do
@catalog.add_resource @one
@catalog.add_resource @two
@one.expects :remove
@two.expects :remove
@catalog.clear(true)
end
it "should support a mechanism for finishing resources" do
@one.expects :finish
@two.expects :finish
@catalog.add_resource @one
@catalog.add_resource @two
@catalog.finalize
end
it "should make default resources when finalizing" do
@catalog.expects(:make_default_resources)
@catalog.finalize
end
it "should add default resources to the catalog upon creation" do
@catalog.make_default_resources
@catalog.resource(:schedule, "daily").should_not be_nil
end
it "should optionally support an initialization block and should finalize after such blocks" do
@one.expects :finish
@two.expects :finish
config = Puppet::Resource::Catalog.new("host") do |conf|
conf.add_resource @one
conf.add_resource @two
end
end
it "should inform the resource that it is the resource's catalog" do
@one.expects(:catalog=).with(@catalog)
@catalog.add_resource @one
end
it "should be able to find resources by reference" do
@catalog.add_resource @one
@catalog.resource(@one.ref).should equal(@one)
end
it "should be able to find resources by reference or by type/title tuple" do
@catalog.add_resource @one
@catalog.resource("notify", "one").should equal(@one)
end
it "should have a mechanism for removing resources" do
@catalog.add_resource @one
@one.expects :remove
@catalog.remove_resource(@one)
@catalog.resource(@one.ref).should be_nil
@catalog.vertex?(@one).should be_false
end
it "should have a method for creating aliases for resources" do
@catalog.add_resource @one
@catalog.alias(@one, "other")
@catalog.resource("notify", "other").should equal(@one)
end
it "should ignore conflicting aliases that point to the aliased resource" do
@catalog.alias(@one, "other")
lambda { @catalog.alias(@one, "other") }.should_not raise_error
end
it "should create aliases for resources isomorphic resources whose names do not match their titles" do
resource = Puppet::Type::File.new(:title => "testing", :path => @basepath+"/something")
@catalog.add_resource(resource)
@catalog.resource(:file, @basepath+"/something").should equal(resource)
end
it "should not create aliases for resources non-isomorphic resources whose names do not match their titles" do
resource = Puppet::Type.type(:exec).new(:title => "testing", :command => "echo", :path => %w{/bin /usr/bin /usr/local/bin})
@catalog.add_resource(resource)
# Yay, I've already got a 'should' method
@catalog.resource(:exec, "echo").object_id.should == nil.object_id
end
# This test is the same as the previous, but the behaviour should be explicit.
it "should alias using the class name from the resource reference, not the resource class name" do
@catalog.add_resource @one
@catalog.alias(@one, "other")
@catalog.resource("notify", "other").should equal(@one)
end
it "should ignore conflicting aliases that point to the aliased resource" do
@catalog.alias(@one, "other")
lambda { @catalog.alias(@one, "other") }.should_not raise_error
end
it "should fail to add an alias if the aliased name already exists" do
@catalog.add_resource @one
proc { @catalog.alias @two, "one" }.should raise_error(ArgumentError)
end
it "should not fail when a resource has duplicate aliases created" do
@catalog.add_resource @one
proc { @catalog.alias @one, "one" }.should_not raise_error
end
it "should not create aliases that point back to the resource" do
@catalog.alias(@one, "one")
@catalog.resource(:notify, "one").should be_nil
end
it "should be able to look resources up by their aliases" do
@catalog.add_resource @one
@catalog.alias @one, "two"
@catalog.resource(:notify, "two").should equal(@one)
end
it "should remove resource aliases when the target resource is removed" do
@catalog.add_resource @one
@catalog.alias(@one, "other")
@one.expects :remove
@catalog.remove_resource(@one)
@catalog.resource("notify", "other").should be_nil
end
it "should add an alias for the namevar when the title and name differ on isomorphic resource types" do
resource = Puppet::Type.type(:file).new :path => @basepath+"/something", :title => "other", :content => "blah"
resource.expects(:isomorphic?).returns(true)
@catalog.add_resource(resource)
@catalog.resource(:file, "other").should equal(resource)
@catalog.resource(:file, @basepath+"/something").ref.should == resource.ref
end
it "should not add an alias for the namevar when the title and name differ on non-isomorphic resource types" do
resource = Puppet::Type.type(:file).new :path => @basepath+"/something", :title => "other", :content => "blah"
resource.expects(:isomorphic?).returns(false)
@catalog.add_resource(resource)
@catalog.resource(:file, resource.title).should equal(resource)
# We can't use .should here, because the resources respond to that method.
raise "Aliased non-isomorphic resource" if @catalog.resource(:file, resource.name)
end
it "should provide a method to create additional resources that also registers the resource" do
args = {:name => "/yay", :ensure => :file}
resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog, :title => "/yay", :[] => "/yay"
Puppet::Type.type(:file).expects(:new).with(args).returns(resource)
@catalog.create_resource :file, args
@catalog.resource("File[/yay]").should equal(resource)
end
end
describe "when applying" do
before :each do
@catalog = Puppet::Resource::Catalog.new("host")
@transaction = mock 'transaction'
Puppet::Transaction.stubs(:new).returns(@transaction)
@transaction.stubs(:evaluate)
@transaction.stubs(:add_times)
Puppet.settings.stubs(:use)
end
it "should create and evaluate a transaction" do
@transaction.expects(:evaluate)
@catalog.apply
end
it "should provide the catalog retrieval time to the transaction" do
@catalog.retrieval_duration = 5
@transaction.expects(:add_times).with(:config_retrieval => 5)
@catalog.apply
end
it "should use a retrieval time of 0 if none is set in the catalog" do
@catalog.retrieval_duration = nil
@transaction.expects(:add_times).with(:config_retrieval => 0)
@catalog.apply
end
it "should return the transaction" do
@catalog.apply.should equal(@transaction)
end
it "should yield the transaction if a block is provided" do
@catalog.apply do |trans|
trans.should equal(@transaction)
end
end
it "should default to being a host catalog" do
@catalog.host_config.should be_true
end
it "should be able to be set to a non-host_config" do
@catalog.host_config = false
@catalog.host_config.should be_false
end
it "should pass supplied tags on to the transaction" do
@transaction.expects(:tags=).with(%w{one two})
@catalog.apply(:tags => %w{one two})
end
it "should set ignoreschedules on the transaction if specified in apply()" do
@transaction.expects(:ignoreschedules=).with(true)
@catalog.apply(:ignoreschedules => true)
end
it "should expire cached data in the resources both before and after the transaction" do
@catalog.expects(:expire).times(2)
@catalog.apply
end
describe "host catalogs" do
# super() doesn't work in the setup method for some reason
before do
@catalog.host_config = true
Puppet::Util::Storage.stubs(:store)
end
it "should initialize the state database before applying a catalog" do
Puppet::Util::Storage.expects(:load)
# Short-circuit the apply, so we know we're loading before the transaction
Puppet::Transaction.expects(:new).raises ArgumentError
proc { @catalog.apply }.should raise_error(ArgumentError)
end
it "should sync the state database after applying" do
Puppet::Util::Storage.expects(:store)
@transaction.stubs :any_failed? => false
@catalog.apply
end
after { Puppet.settings.clear }
end
describe "non-host catalogs" do
before do
@catalog.host_config = false
end
it "should never send reports" do
Puppet[:report] = true
Puppet[:summarize] = true
@catalog.apply
end
it "should never modify the state database" do
Puppet::Util::Storage.expects(:load).never
Puppet::Util::Storage.expects(:store).never
@catalog.apply
end
after { Puppet.settings.clear }
end
end
describe "when creating a relationship graph" do
before do
Puppet::Type.type(:component)
@catalog = Puppet::Resource::Catalog.new("host")
@compone = Puppet::Type::Component.new :name => "one"
@comptwo = Puppet::Type::Component.new :name => "two", :require => "Class[one]"
@file = Puppet::Type.type(:file)
@one = @file.new :path => @basepath+"/one"
@two = @file.new :path => @basepath+"/two"
@sub = @file.new :path => @basepath+"/two/subdir"
@catalog.add_edge @compone, @one
@catalog.add_edge @comptwo, @two
@three = @file.new :path => @basepath+"/three"
@four = @file.new :path => @basepath+"/four", :require => "File[#{@basepath}/three]"
@five = @file.new :path => @basepath+"/five"
@catalog.add_resource @compone, @comptwo, @one, @two, @three, @four, @five, @sub
@relationships = @catalog.relationship_graph
end
it "should be able to create a relationship graph" do
@relationships.should be_instance_of(Puppet::SimpleGraph)
end
it "should not have any components" do
@relationships.vertices.find { |r| r.instance_of?(Puppet::Type::Component) }.should be_nil
end
it "should have all non-component resources from the catalog" do
# The failures print out too much info, so i just do a class comparison
@relationships.vertex?(@five).should be_true
end
it "should have all resource relationships set as edges" do
@relationships.edge?(@three, @four).should be_true
end
it "should copy component relationships to all contained resources" do
@relationships.edge?(@one, @two).should be_true
end
it "should add automatic relationships to the relationship graph" do
@relationships.edge?(@two, @sub).should be_true
end
it "should get removed when the catalog is cleaned up" do
@relationships.expects(:clear)
@catalog.clear
@catalog.instance_variable_get("@relationship_graph").should be_nil
end
it "should write :relationships and :expanded_relationships graph files if the catalog is a host catalog" do
@catalog.clear
graph = Puppet::SimpleGraph.new
Puppet::SimpleGraph.expects(:new).returns graph
graph.expects(:write_graph).with(:relationships)
graph.expects(:write_graph).with(:expanded_relationships)
@catalog.host_config = true
@catalog.relationship_graph
end
it "should not write graph files if the catalog is not a host catalog" do
@catalog.clear
graph = Puppet::SimpleGraph.new
Puppet::SimpleGraph.expects(:new).returns graph
graph.expects(:write_graph).never
@catalog.host_config = false
@catalog.relationship_graph
end
it "should create a new relationship graph after clearing the old one" do
@relationships.expects(:clear)
@catalog.clear
@catalog.relationship_graph.should be_instance_of(Puppet::SimpleGraph)
end
it "should remove removed resources from the relationship graph if it exists" do
@catalog.remove_resource(@one)
@catalog.relationship_graph.vertex?(@one).should be_false
end
end
describe "when writing dot files" do
before do
@catalog = Puppet::Resource::Catalog.new("host")
@name = :test
@file = File.join(Puppet[:graphdir], @name.to_s + ".dot")
end
it "should only write when it is a host catalog" do
File.expects(:open).with(@file).never
@catalog.host_config = false
Puppet[:graph] = true
@catalog.write_graph(@name)
end
after do
Puppet.settings.clear
end
end
describe "when indirecting" do
before do
@real_indirection = Puppet::Resource::Catalog.indirection
@indirection = stub 'indirection', :name => :catalog
Puppet::Util::Cacher.expire
end
it "should redirect to the indirection for retrieval" do
Puppet::Resource::Catalog.stubs(:indirection).returns(@indirection)
@indirection.expects(:find)
Puppet::Resource::Catalog.find(:myconfig)
end
it "should use the value of the 'catalog_terminus' setting to determine its terminus class" do
# Puppet only checks the terminus setting the first time you ask
# so this returns the object to the clean state
# at the expense of making this test less pure
Puppet::Resource::Catalog.indirection.reset_terminus_class
Puppet.settings[:catalog_terminus] = "rest"
Puppet::Resource::Catalog.indirection.terminus_class.should == :rest
end
it "should allow the terminus class to be set manually" do
Puppet::Resource::Catalog.indirection.terminus_class = :rest
Puppet::Resource::Catalog.indirection.terminus_class.should == :rest
end
after do
Puppet::Util::Cacher.expire
@real_indirection.reset_terminus_class
end
end
describe "when converting to yaml" do
before do
@catalog = Puppet::Resource::Catalog.new("me")
@catalog.add_edge("one", "two")
end
it "should be able to be dumped to yaml" do
YAML.dump(@catalog).should be_instance_of(String)
end
end
describe "when converting from yaml" do
before do
@catalog = Puppet::Resource::Catalog.new("me")
@catalog.add_edge("one", "two")
text = YAML.dump(@catalog)
@newcatalog = YAML.load(text)
end
it "should get converted back to a catalog" do
@newcatalog.should be_instance_of(Puppet::Resource::Catalog)
end
it "should have all vertices" do
@newcatalog.vertex?("one").should be_true
@newcatalog.vertex?("two").should be_true
end
it "should have all edges" do
@newcatalog.edge?("one", "two").should be_true
end
end
end
-describe Puppet::Resource::Catalog, "when converting to pson" do
- confine "Missing 'pson' library" => Puppet.features.pson?
-
+describe Puppet::Resource::Catalog, "when converting to pson", :if => Puppet.features.pson? do
before do
@catalog = Puppet::Resource::Catalog.new("myhost")
end
def pson_output_should
@catalog.class.expects(:pson_create).with { |hash| yield hash }.returns(:something)
end
# LAK:NOTE For all of these tests, we convert back to the resource so we can
# trap the actual data structure then.
it "should set its document_type to 'Catalog'" do
pson_output_should { |hash| hash['document_type'] == "Catalog" }
PSON.parse @catalog.to_pson
end
it "should set its data as a hash" do
pson_output_should { |hash| hash['data'].is_a?(Hash) }
PSON.parse @catalog.to_pson
end
[:name, :version, :tags, :classes].each do |param|
it "should set its #{param} to the #{param} of the resource" do
@catalog.send(param.to_s + "=", "testing") unless @catalog.send(param)
pson_output_should { |hash| hash['data'][param.to_s] == @catalog.send(param) }
PSON.parse @catalog.to_pson
end
end
it "should convert its resources to a PSON-encoded array and store it as the 'resources' data" do
one = stub 'one', :to_pson_data_hash => "one_resource", :ref => "Foo[one]"
two = stub 'two', :to_pson_data_hash => "two_resource", :ref => "Foo[two]"
@catalog.add_resource(one)
@catalog.add_resource(two)
# TODO this should really guarantee sort order
PSON.parse(@catalog.to_pson,:create_additions => false)['data']['resources'].sort.should == ["one_resource", "two_resource"].sort
end
it "should convert its edges to a PSON-encoded array and store it as the 'edges' data" do
one = stub 'one', :to_pson_data_hash => "one_resource", :ref => 'Foo[one]'
two = stub 'two', :to_pson_data_hash => "two_resource", :ref => 'Foo[two]'
three = stub 'three', :to_pson_data_hash => "three_resource", :ref => 'Foo[three]'
@catalog.add_edge(one, two)
@catalog.add_edge(two, three)
@catalog.edge(one, two ).expects(:to_pson_data_hash).returns "one_two_pson"
@catalog.edge(two, three).expects(:to_pson_data_hash).returns "two_three_pson"
PSON.parse(@catalog.to_pson,:create_additions => false)['data']['edges'].sort.should == %w{one_two_pson two_three_pson}.sort
end
end
-describe Puppet::Resource::Catalog, "when converting from pson" do
- confine "Missing 'pson' library" => Puppet.features.pson?
-
+describe Puppet::Resource::Catalog, "when converting from pson", :if => Puppet.features.pson? do
def pson_result_should
Puppet::Resource::Catalog.expects(:new).with { |hash| yield hash }
end
before do
@data = {
'name' => "myhost"
}
@pson = {
'document_type' => 'Puppet::Resource::Catalog',
'data' => @data,
'metadata' => {}
}
@catalog = Puppet::Resource::Catalog.new("myhost")
Puppet::Resource::Catalog.stubs(:new).returns @catalog
end
it "should be extended with the PSON utility module" do
Puppet::Resource::Catalog.singleton_class.ancestors.should be_include(Puppet::Util::Pson)
end
it "should create it with the provided name" do
Puppet::Resource::Catalog.expects(:new).with('myhost').returns @catalog
PSON.parse @pson.to_pson
end
it "should set the provided version on the catalog if one is set" do
@data['version'] = 50
PSON.parse @pson.to_pson
@catalog.version.should == @data['version']
end
it "should set any provided tags on the catalog" do
@data['tags'] = %w{one two}
PSON.parse @pson.to_pson
@catalog.tags.should == @data['tags']
end
it "should set any provided classes on the catalog" do
@data['classes'] = %w{one two}
PSON.parse @pson.to_pson
@catalog.classes.should == @data['classes']
end
it 'should convert the resources list into resources and add each of them' do
@data['resources'] = [Puppet::Resource.new(:file, "/foo"), Puppet::Resource.new(:file, "/bar")]
@catalog.expects(:add_resource).times(2).with { |res| res.type == "File" }
PSON.parse @pson.to_pson
end
it 'should convert resources even if they do not include "type" information' do
@data['resources'] = [Puppet::Resource.new(:file, "/foo")]
@data['resources'][0].expects(:to_pson).returns '{"title":"/foo","tags":["file"],"type":"File"}'
@catalog.expects(:add_resource).with { |res| res.type == "File" }
PSON.parse @pson.to_pson
end
it 'should convert the edges list into edges and add each of them' do
one = Puppet::Relationship.new("osource", "otarget", :event => "one", :callback => "refresh")
two = Puppet::Relationship.new("tsource", "ttarget", :event => "two", :callback => "refresh")
@data['edges'] = [one, two]
@catalog.stubs(:resource).returns("eh")
@catalog.expects(:add_edge).with { |edge| edge.event == "one" }
@catalog.expects(:add_edge).with { |edge| edge.event == "two" }
PSON.parse @pson.to_pson
end
it "should be able to convert relationships that do not include 'type' information" do
one = Puppet::Relationship.new("osource", "otarget", :event => "one", :callback => "refresh")
one.expects(:to_pson).returns "{\"event\":\"one\",\"callback\":\"refresh\",\"source\":\"osource\",\"target\":\"otarget\"}"
@data['edges'] = [one]
@catalog.stubs(:resource).returns("eh")
@catalog.expects(:add_edge).with { |edge| edge.event == "one" }
PSON.parse @pson.to_pson
end
it "should set the source and target for each edge to the actual resource" do
edge = Puppet::Relationship.new("source", "target")
@data['edges'] = [edge]
@catalog.expects(:resource).with("source").returns("source_resource")
@catalog.expects(:resource).with("target").returns("target_resource")
@catalog.expects(:add_edge).with { |edge| edge.source == "source_resource" and edge.target == "target_resource" }
PSON.parse @pson.to_pson
end
it "should fail if the source resource cannot be found" do
edge = Puppet::Relationship.new("source", "target")
@data['edges'] = [edge]
@catalog.expects(:resource).with("source").returns(nil)
@catalog.stubs(:resource).with("target").returns("target_resource")
lambda { PSON.parse @pson.to_pson }.should raise_error(ArgumentError)
end
it "should fail if the target resource cannot be found" do
edge = Puppet::Relationship.new("source", "target")
@data['edges'] = [edge]
@catalog.stubs(:resource).with("source").returns("source_resource")
@catalog.expects(:resource).with("target").returns(nil)
lambda { PSON.parse @pson.to_pson }.should raise_error(ArgumentError)
end
describe "#title_key_for_ref" do
it "should parse a resource ref string into a pair" do
@catalog.title_key_for_ref("Title[name]").should == ["Title", "name"]
end
it "should parse a resource ref string into a pair, even if there's a newline inside the name" do
@catalog.title_key_for_ref("Title[na\nme]").should == ["Title", "na\nme"]
end
end
end
diff --git a/spec/unit/resource/status_spec.rb b/spec/unit/resource/status_spec.rb
index 425015a13..4e76fa463 100755
--- a/spec/unit/resource/status_spec.rb
+++ b/spec/unit/resource/status_spec.rb
@@ -1,103 +1,153 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/resource/status'
describe Puppet::Resource::Status do
before do
@resource = Puppet::Type.type(:file).new :path => "/my/file"
@status = Puppet::Resource::Status.new(@resource)
end
- [:node, :version, :file, :line, :current_values, :skipped_reason, :status, :evaluation_time, :change_count].each do |attr|
+ it "should compute type and title correctly" do
+ @status.resource_type.should == "File"
+ @status.title.should == "/my/file"
+ end
+
+ [:node, :file, :line, :current_values, :status, :evaluation_time].each do |attr|
it "should support #{attr}" do
@status.send(attr.to_s + "=", "foo")
@status.send(attr).should == "foo"
end
end
[:skipped, :failed, :restarted, :failed_to_restart, :changed, :out_of_sync, :scheduled].each do |attr|
it "should support #{attr}" do
@status.send(attr.to_s + "=", "foo")
@status.send(attr).should == "foo"
end
it "should have a boolean method for determining whehter it was #{attr}" do
@status.send(attr.to_s + "=", "foo")
@status.should send("be_#{attr}")
end
end
it "should accept a resource at initialization" do
Puppet::Resource::Status.new(@resource).resource.should_not be_nil
end
it "should set its source description to the resource's path" do
@resource.expects(:path).returns "/my/path"
Puppet::Resource::Status.new(@resource).source_description.should == "/my/path"
end
- [:file, :line, :version].each do |attr|
+ [:file, :line].each do |attr|
it "should copy the resource's #{attr}" do
@resource.expects(attr).returns "foo"
Puppet::Resource::Status.new(@resource).send(attr).should == "foo"
end
end
it "should copy the resource's tags" do
@resource.expects(:tags).returns %w{foo bar}
Puppet::Resource::Status.new(@resource).tags.should == %w{foo bar}
end
it "should always convert the resource to a string" do
@resource.expects(:to_s).returns "foo"
Puppet::Resource::Status.new(@resource).resource.should == "foo"
end
it "should support tags" do
Puppet::Resource::Status.ancestors.should include(Puppet::Util::Tagging)
end
it "should create a timestamp at its creation time" do
@status.time.should be_instance_of(Time)
end
describe "when sending logs" do
before do
Puppet::Util::Log.stubs(:new)
end
it "should set the tags to the event tags" do
Puppet::Util::Log.expects(:new).with { |args| args[:tags] == %w{one two} }
@status.stubs(:tags).returns %w{one two}
@status.send_log :notice, "my message"
end
- [:file, :line, :version].each do |attr|
+ [:file, :line].each do |attr|
it "should pass the #{attr}" do
Puppet::Util::Log.expects(:new).with { |args| args[attr] == "my val" }
@status.send(attr.to_s + "=", "my val")
@status.send_log :notice, "my message"
end
end
it "should use the source description as the source" do
Puppet::Util::Log.expects(:new).with { |args| args[:source] == "my source" }
@status.stubs(:source_description).returns "my source"
@status.send_log :notice, "my message"
end
end
it "should support adding events" do
event = Puppet::Transaction::Event.new(:name => :foobar)
@status.add_event(event)
@status.events.should == [event]
end
it "should use '<<' to add events" do
event = Puppet::Transaction::Event.new(:name => :foobar)
(@status << event).should equal(@status)
@status.events.should == [event]
end
+
+ it "should count the number of successful events and set changed" do
+ 3.times{ @status << Puppet::Transaction::Event.new(:status => 'success') }
+ @status.change_count.should == 3
+
+ @status.changed.should == true
+ @status.out_of_sync.should == true
+ end
+
+ it "should not start with any changes" do
+ @status.change_count.should == 0
+
+ @status.changed.should == false
+ @status.out_of_sync.should == false
+ end
+
+ it "should not treat failure, audit, or noop events as changed" do
+ ['failure', 'audit', 'noop'].each do |s| @status << Puppet::Transaction::Event.new(:status => s) end
+ @status.change_count.should == 0
+ @status.changed.should == false
+ end
+
+ it "should not treat audit events as out of sync" do
+ @status << Puppet::Transaction::Event.new(:status => 'audit')
+ @status.out_of_sync_count.should == 0
+ @status.out_of_sync.should == false
+ end
+
+ ['failure', 'noop', 'success'].each do |event_status|
+ it "should treat #{event_status} events as out of sync" do
+ 3.times do @status << Puppet::Transaction::Event.new(:status => event_status) end
+ @status.out_of_sync_count.should == 3
+ @status.out_of_sync.should == true
+ end
+ end
+
+ describe "When converting to YAML" do
+ it "should include only documented attributes" do
+ @status.file = "/foo.rb"
+ @status.line = 27
+ @status.evaluation_time = 2.7
+ @status.tags = %w{one two}
+ @status.to_yaml_properties.should == Puppet::Resource::Status::YAML_ATTRIBUTES.sort
+ end
+ end
end
diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb
index e65e8a13a..877b6b6b0 100755
--- a/spec/unit/resource_spec.rb
+++ b/spec/unit/resource_spec.rb
@@ -1,782 +1,778 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet/resource'
describe Puppet::Resource do
before do
@basepath = Puppet.features.posix? ? "/somepath" : "C:/somepath"
end
[:catalog, :file, :line].each do |attr|
it "should have an #{attr} attribute" do
resource = Puppet::Resource.new("file", "/my/file")
resource.should respond_to(attr)
resource.should respond_to(attr.to_s + "=")
end
end
it "should have a :title attribute" do
Puppet::Resource.new(:user, "foo").title.should == "foo"
end
it "should require the type and title" do
lambda { Puppet::Resource.new }.should raise_error(ArgumentError)
end
it "should canonize types to capitalized strings" do
Puppet::Resource.new(:user, "foo").type.should == "User"
end
it "should canonize qualified types so all strings are capitalized" do
Puppet::Resource.new("foo::bar", "foo").type.should == "Foo::Bar"
end
it "should tag itself with its type" do
Puppet::Resource.new("file", "/f").should be_tagged("file")
end
it "should tag itself with its title if the title is a valid tag" do
Puppet::Resource.new("user", "bar").should be_tagged("bar")
end
it "should not tag itself with its title if the title is a not valid tag" do
Puppet::Resource.new("file", "/bar").should_not be_tagged("/bar")
end
it "should allow setting of attributes" do
Puppet::Resource.new("file", "/bar", :file => "/foo").file.should == "/foo"
Puppet::Resource.new("file", "/bar", :exported => true).should be_exported
end
it "should set its type to 'Class' and its title to the passed title if the passed type is :component and the title has no square brackets in it" do
ref = Puppet::Resource.new(:component, "foo")
ref.type.should == "Class"
ref.title.should == "Foo"
end
it "should interpret the title as a reference and assign appropriately if the type is :component and the title contains square brackets" do
ref = Puppet::Resource.new(:component, "foo::bar[yay]")
ref.type.should == "Foo::Bar"
ref.title.should == "yay"
end
it "should set the type to 'Class' if it is nil and the title contains no square brackets" do
ref = Puppet::Resource.new(nil, "yay")
ref.type.should == "Class"
ref.title.should == "Yay"
end
it "should interpret the title as a reference and assign appropriately if the type is nil and the title contains square brackets" do
ref = Puppet::Resource.new(nil, "foo::bar[yay]")
ref.type.should == "Foo::Bar"
ref.title.should == "yay"
end
it "should interpret the title as a reference and assign appropriately if the type is nil and the title contains nested square brackets" do
ref = Puppet::Resource.new(nil, "foo::bar[baz[yay]]")
ref.type.should == "Foo::Bar"
ref.title.should =="baz[yay]"
end
it "should interpret the type as a reference and assign appropriately if the title is nil and the type contains square brackets" do
ref = Puppet::Resource.new("foo::bar[baz]")
ref.type.should == "Foo::Bar"
ref.title.should =="baz"
end
it "should be able to extract its information from a Puppet::Type instance" do
ral = Puppet::Type.type(:file).new :path => @basepath+"/foo"
ref = Puppet::Resource.new(ral)
ref.type.should == "File"
ref.title.should == @basepath+"/foo"
end
it "should fail if the title is nil and the type is not a valid resource reference string" do
lambda { Puppet::Resource.new("foo") }.should raise_error(ArgumentError)
end
it "should fail if the title is a hash and the type is not a valid resource reference string" do
lambda { Puppet::Resource.new({:type => "foo", :title => "bar"}) }.should raise_error(ArgumentError,
'Puppet::Resource.new does not take a hash as the first argument. Did you mean ("foo", "bar") ?'
)
end
it "should be able to produce a backward-compatible reference array" do
Puppet::Resource.new("foobar", "/f").to_trans_ref.should == %w{Foobar /f}
end
it "should be taggable" do
Puppet::Resource.ancestors.should be_include(Puppet::Util::Tagging)
end
it "should have an 'exported' attribute" do
resource = Puppet::Resource.new("file", "/f")
resource.exported = true
resource.exported.should == true
resource.should be_exported
end
it "should support an environment attribute" do
Puppet::Resource.new("file", "/my/file", :environment => :foo).environment.name.should == :foo
end
describe "and munging its type and title" do
describe "when modeling a builtin resource" do
it "should be able to find the resource type" do
Puppet::Resource.new("file", "/my/file").resource_type.should equal(Puppet::Type.type(:file))
end
it "should set its type to the capitalized type name" do
Puppet::Resource.new("file", "/my/file").type.should == "File"
end
end
describe "when modeling a defined resource" do
describe "that exists" do
before do
@type = Puppet::Resource::Type.new(:definition, "foo::bar")
Puppet::Node::Environment.new.known_resource_types.add @type
end
it "should set its type to the capitalized type name" do
Puppet::Resource.new("foo::bar", "/my/file").type.should == "Foo::Bar"
end
it "should be able to find the resource type" do
Puppet::Resource.new("foo::bar", "/my/file").resource_type.should equal(@type)
end
it "should set its title to the provided title" do
Puppet::Resource.new("foo::bar", "/my/file").title.should == "/my/file"
end
end
describe "that does not exist" do
it "should set its resource type to the capitalized resource type name" do
Puppet::Resource.new("foo::bar", "/my/file").type.should == "Foo::Bar"
end
end
end
describe "when modeling a node" do
# Life's easier with nodes, because they can't be qualified.
it "should set its type to 'Node' and its title to the provided title" do
node = Puppet::Resource.new("node", "foo")
node.type.should == "Node"
node.title.should == "foo"
end
end
describe "when modeling a class" do
it "should set its type to 'Class'" do
Puppet::Resource.new("class", "foo").type.should == "Class"
end
describe "that exists" do
before do
@type = Puppet::Resource::Type.new(:hostclass, "foo::bar")
Puppet::Node::Environment.new.known_resource_types.add @type
end
it "should set its title to the capitalized, fully qualified resource type" do
Puppet::Resource.new("class", "foo::bar").title.should == "Foo::Bar"
end
it "should be able to find the resource type" do
Puppet::Resource.new("class", "foo::bar").resource_type.should equal(@type)
end
end
describe "that does not exist" do
it "should set its type to 'Class' and its title to the capitalized provided name" do
klass = Puppet::Resource.new("class", "foo::bar")
klass.type.should == "Class"
klass.title.should == "Foo::Bar"
end
end
describe "and its name is set to the empty string" do
it "should set its title to :main" do
Puppet::Resource.new("class", "").title.should == :main
end
describe "and a class exists whose name is the empty string" do # this was a bit tough to track down
it "should set its title to :main" do
@type = Puppet::Resource::Type.new(:hostclass, "")
Puppet::Node::Environment.new.known_resource_types.add @type
Puppet::Resource.new("class", "").title.should == :main
end
end
end
describe "and its name is set to :main" do
it "should set its title to :main" do
Puppet::Resource.new("class", :main).title.should == :main
end
describe "and a class exists whose name is the empty string" do # this was a bit tough to track down
it "should set its title to :main" do
@type = Puppet::Resource::Type.new(:hostclass, "")
Puppet::Node::Environment.new.known_resource_types.add @type
Puppet::Resource.new("class", :main).title.should == :main
end
end
end
end
end
it "should return nil when looking up resource types that don't exist" do
Puppet::Resource.new("foobar", "bar").resource_type.should be_nil
end
it "should not fail when an invalid parameter is used and strict mode is disabled" do
type = Puppet::Resource::Type.new(:definition, "foobar")
Puppet::Node::Environment.new.known_resource_types.add type
resource = Puppet::Resource.new("foobar", "/my/file")
resource[:yay] = true
end
it "should be considered equivalent to another resource if their type and title match and no parameters are set" do
Puppet::Resource.new("file", "/f").should == Puppet::Resource.new("file", "/f")
end
it "should be considered equivalent to another resource if their type, title, and parameters are equal" do
Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"}).should == Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"})
end
it "should not be considered equivalent to another resource if their type and title match but parameters are different" do
Puppet::Resource.new("file", "/f", :parameters => {:fee => "baz"}).should_not == Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"})
end
it "should not be considered equivalent to a non-resource" do
Puppet::Resource.new("file", "/f").should_not == "foo"
end
it "should not be considered equivalent to another resource if their types do not match" do
Puppet::Resource.new("file", "/f").should_not == Puppet::Resource.new("exec", "/f")
end
it "should not be considered equivalent to another resource if their titles do not match" do
Puppet::Resource.new("file", "/foo").should_not == Puppet::Resource.new("file", "/f")
end
describe "when referring to a resource with name canonicalization" do
it "should canonicalize its own name" do
res = Puppet::Resource.new("file", "/path/")
res.uniqueness_key.should == ["/path"]
res.ref.should == "File[/path/]"
end
end
describe "when running in strict mode" do
it "should be strict" do
Puppet::Resource.new("file", "/path", :strict => true).should be_strict
end
it "should fail if invalid parameters are used" do
lambda { Puppet::Resource.new("file", "/path", :strict => true, :parameters => {:nosuchparam => "bar"}) }.should raise_error
end
it "should fail if the resource type cannot be resolved" do
lambda { Puppet::Resource.new("nosuchtype", "/path", :strict => true) }.should raise_error
end
end
describe "when managing parameters" do
before do
@resource = Puppet::Resource.new("file", "/my/file")
end
it "should correctly detect when provided parameters are not valid for builtin types" do
Puppet::Resource.new("file", "/my/file").should_not be_valid_parameter("foobar")
end
it "should correctly detect when provided parameters are valid for builtin types" do
Puppet::Resource.new("file", "/my/file").should be_valid_parameter("mode")
end
it "should correctly detect when provided parameters are not valid for defined resource types" do
type = Puppet::Resource::Type.new(:definition, "foobar")
Puppet::Node::Environment.new.known_resource_types.add type
Puppet::Resource.new("foobar", "/my/file").should_not be_valid_parameter("myparam")
end
it "should correctly detect when provided parameters are valid for defined resource types" do
type = Puppet::Resource::Type.new(:definition, "foobar", :arguments => {"myparam" => nil})
Puppet::Node::Environment.new.known_resource_types.add type
Puppet::Resource.new("foobar", "/my/file").should be_valid_parameter("myparam")
end
it "should allow setting and retrieving of parameters" do
@resource[:foo] = "bar"
@resource[:foo].should == "bar"
end
it "should allow setting of parameters at initialization" do
Puppet::Resource.new("file", "/my/file", :parameters => {:foo => "bar"})[:foo].should == "bar"
end
it "should canonicalize retrieved parameter names to treat symbols and strings equivalently" do
@resource[:foo] = "bar"
@resource["foo"].should == "bar"
end
it "should canonicalize set parameter names to treat symbols and strings equivalently" do
@resource["foo"] = "bar"
@resource[:foo].should == "bar"
end
it "should set the namevar when asked to set the name" do
resource = Puppet::Resource.new("user", "bob")
Puppet::Type.type(:user).stubs(:key_attributes).returns [:myvar]
resource[:name] = "bob"
resource[:myvar].should == "bob"
end
it "should return the namevar when asked to return the name" do
resource = Puppet::Resource.new("user", "bob")
Puppet::Type.type(:user).stubs(:key_attributes).returns [:myvar]
resource[:myvar] = "test"
resource[:name].should == "test"
end
it "should be able to set the name for non-builtin types" do
resource = Puppet::Resource.new(:foo, "bar")
resource[:name] = "eh"
lambda { resource[:name] = "eh" }.should_not raise_error
end
it "should be able to return the name for non-builtin types" do
resource = Puppet::Resource.new(:foo, "bar")
resource[:name] = "eh"
resource[:name].should == "eh"
end
it "should be able to iterate over parameters" do
@resource[:foo] = "bar"
@resource[:fee] = "bare"
params = {}
@resource.each do |key, value|
params[key] = value
end
params.should == {:foo => "bar", :fee => "bare"}
end
it "should include Enumerable" do
@resource.class.ancestors.should be_include(Enumerable)
end
it "should have a method for testing whether a parameter is included" do
@resource[:foo] = "bar"
@resource.should be_has_key(:foo)
@resource.should_not be_has_key(:eh)
end
it "should have a method for providing the list of parameters" do
@resource[:foo] = "bar"
@resource[:bar] = "foo"
keys = @resource.keys
keys.should be_include(:foo)
keys.should be_include(:bar)
end
it "should have a method for providing the number of parameters" do
@resource[:foo] = "bar"
@resource.length.should == 1
end
it "should have a method for deleting parameters" do
@resource[:foo] = "bar"
@resource.delete(:foo)
@resource[:foo].should be_nil
end
it "should have a method for testing whether the parameter list is empty" do
@resource.should be_empty
@resource[:foo] = "bar"
@resource.should_not be_empty
end
it "should be able to produce a hash of all existing parameters" do
@resource[:foo] = "bar"
@resource[:fee] = "yay"
hash = @resource.to_hash
hash[:foo].should == "bar"
hash[:fee].should == "yay"
end
it "should not provide direct access to the internal parameters hash when producing a hash" do
hash = @resource.to_hash
hash[:foo] = "bar"
@resource[:foo].should be_nil
end
it "should use the title as the namevar to the hash if no namevar is present" do
resource = Puppet::Resource.new("user", "bob")
Puppet::Type.type(:user).stubs(:key_attributes).returns [:myvar]
resource.to_hash[:myvar].should == "bob"
end
it "should set :name to the title if :name is not present for non-builtin types" do
krt = Puppet::Resource::TypeCollection.new("myenv")
krt.add Puppet::Resource::Type.new(:definition, :foo)
resource = Puppet::Resource.new :foo, "bar"
resource.stubs(:known_resource_types).returns krt
resource.to_hash[:name].should == "bar"
end
end
describe "when serializing" do
before do
@resource = Puppet::Resource.new("file", "/my/file")
@resource["one"] = "test"
@resource["two"] = "other"
end
it "should be able to be dumped to yaml" do
proc { YAML.dump(@resource) }.should_not raise_error
end
it "should produce an equivalent yaml object" do
text = YAML.dump(@resource)
newresource = YAML.load(text)
newresource.title.should == @resource.title
newresource.type.should == @resource.type
%w{one two}.each do |param|
newresource[param].should == @resource[param]
end
end
end
describe "when converting to a RAL resource" do
it "should use the resource type's :new method to create the resource if the resource is of a builtin type" do
resource = Puppet::Resource.new("file", @basepath+"/my/file")
result = resource.to_ral
result.should be_instance_of(Puppet::Type.type(:file))
result[:path].should == @basepath+"/my/file"
end
it "should convert to a component instance if the resource type is not of a builtin type" do
resource = Puppet::Resource.new("foobar", "somename")
result = resource.to_ral
result.should be_instance_of(Puppet::Type.type(:component))
result.title.should == "Foobar[somename]"
end
end
it "should be able to convert itself to Puppet code" do
Puppet::Resource.new("one::two", "/my/file").should respond_to(:to_manifest)
end
describe "when converting to puppet code" do
before do
@resource = Puppet::Resource.new("one::two", "/my/file", :parameters => {:noop => true, :foo => %w{one two}})
end
it "should print the type and title" do
@resource.to_manifest.should be_include("one::two { '/my/file':\n")
end
it "should print each parameter, with the value single-quoted" do
@resource.to_manifest.should be_include(" noop => 'true'")
end
it "should print array values appropriately" do
@resource.to_manifest.should be_include(" foo => ['one','two']")
end
end
it "should be able to convert itself to a TransObject instance" do
Puppet::Resource.new("one::two", "/my/file").should respond_to(:to_trans)
end
describe "when converting to a TransObject" do
describe "and the resource is not an instance of a builtin type" do
before do
@resource = Puppet::Resource.new("foo", "bar")
end
it "should return a simple TransBucket if it is not an instance of a builtin type" do
bucket = @resource.to_trans
bucket.should be_instance_of(Puppet::TransBucket)
bucket.type.should == @resource.type
bucket.name.should == @resource.title
end
it "should return a simple TransBucket if it is a stage" do
@resource = Puppet::Resource.new("stage", "bar")
bucket = @resource.to_trans
bucket.should be_instance_of(Puppet::TransBucket)
bucket.type.should == @resource.type
bucket.name.should == @resource.title
end
it "should copy over the resource's file" do
@resource.file = "/foo/bar"
@resource.to_trans.file.should == "/foo/bar"
end
it "should copy over the resource's line" do
@resource.line = 50
@resource.to_trans.line.should == 50
end
end
describe "and the resource is an instance of a builtin type" do
before do
@resource = Puppet::Resource.new("file", "bar")
end
it "should return a TransObject if it is an instance of a builtin resource type" do
trans = @resource.to_trans
trans.should be_instance_of(Puppet::TransObject)
trans.type.should == "file"
trans.name.should == @resource.title
end
it "should copy over the resource's file" do
@resource.file = "/foo/bar"
@resource.to_trans.file.should == "/foo/bar"
end
it "should copy over the resource's line" do
@resource.line = 50
@resource.to_trans.line.should == 50
end
# Only TransObjects support tags, annoyingly
it "should copy over the resource's tags" do
@resource.tag "foo"
@resource.to_trans.tags.should == @resource.tags
end
it "should copy the resource's parameters into the transobject and convert the parameter name to a string" do
@resource[:foo] = "bar"
@resource.to_trans["foo"].should == "bar"
end
it "should be able to copy arrays of values" do
@resource[:foo] = %w{yay fee}
@resource.to_trans["foo"].should == %w{yay fee}
end
it "should reduce single-value arrays to just a value" do
@resource[:foo] = %w{yay}
@resource.to_trans["foo"].should == "yay"
end
it "should convert resource references into the backward-compatible form" do
@resource[:foo] = Puppet::Resource.new(:file, "/f")
@resource.to_trans["foo"].should == %w{File /f}
end
it "should convert resource references into the backward-compatible form even when within arrays" do
@resource[:foo] = ["a", Puppet::Resource.new(:file, "/f")]
@resource.to_trans["foo"].should == ["a", %w{File /f}]
end
end
end
- describe "when converting to pson" do
- confine "Missing 'pson' library" => Puppet.features.pson?
-
+ describe "when converting to pson", :if => Puppet.features.pson? do
def pson_output_should
@resource.class.expects(:pson_create).with { |hash| yield hash }
end
it "should include the pson util module" do
Puppet::Resource.singleton_class.ancestors.should be_include(Puppet::Util::Pson)
end
# LAK:NOTE For all of these tests, we convert back to the resource so we can
# trap the actual data structure then.
it "should set its type to the provided type" do
Puppet::Resource.from_pson(PSON.parse(Puppet::Resource.new("File", "/foo").to_pson)).type.should == "File"
end
it "should set its title to the provided title" do
Puppet::Resource.from_pson(PSON.parse(Puppet::Resource.new("File", "/foo").to_pson)).title.should == "/foo"
end
it "should include all tags from the resource" do
resource = Puppet::Resource.new("File", "/foo")
resource.tag("yay")
Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).tags.should == resource.tags
end
it "should include the file if one is set" do
resource = Puppet::Resource.new("File", "/foo")
resource.file = "/my/file"
Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).file.should == "/my/file"
end
it "should include the line if one is set" do
resource = Puppet::Resource.new("File", "/foo")
resource.line = 50
Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).line.should == 50
end
it "should include the 'exported' value if one is set" do
resource = Puppet::Resource.new("File", "/foo")
resource.exported = true
Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).exported.should be_true
end
it "should set 'exported' to false if no value is set" do
resource = Puppet::Resource.new("File", "/foo")
Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).exported.should be_false
end
it "should set all of its parameters as the 'parameters' entry" do
resource = Puppet::Resource.new("File", "/foo")
resource[:foo] = %w{bar eh}
resource[:fee] = %w{baz}
result = Puppet::Resource.from_pson(PSON.parse(resource.to_pson))
result["foo"].should == %w{bar eh}
result["fee"].should == %w{baz}
end
it "should serialize relationships as reference strings" do
resource = Puppet::Resource.new("File", "/foo")
resource[:requires] = Puppet::Resource.new("File", "/bar")
result = Puppet::Resource.from_pson(PSON.parse(resource.to_pson))
result[:requires].should == "File[/bar]"
end
it "should serialize multiple relationships as arrays of reference strings" do
resource = Puppet::Resource.new("File", "/foo")
resource[:requires] = [Puppet::Resource.new("File", "/bar"), Puppet::Resource.new("File", "/baz")]
result = Puppet::Resource.from_pson(PSON.parse(resource.to_pson))
result[:requires].should == [ "File[/bar]", "File[/baz]" ]
end
end
- describe "when converting from pson" do
- confine "Missing 'pson' library" => Puppet.features.pson?
-
+ describe "when converting from pson", :if => Puppet.features.pson? do
def pson_result_should
Puppet::Resource.expects(:new).with { |hash| yield hash }
end
before do
@data = {
'type' => "file",
'title' => @basepath+"/yay",
}
end
it "should set its type to the provided type" do
Puppet::Resource.from_pson(@data).type.should == "File"
end
it "should set its title to the provided title" do
Puppet::Resource.from_pson(@data).title.should == @basepath+"/yay"
end
it "should tag the resource with any provided tags" do
@data['tags'] = %w{foo bar}
resource = Puppet::Resource.from_pson(@data)
resource.tags.should be_include("foo")
resource.tags.should be_include("bar")
end
it "should set its file to the provided file" do
@data['file'] = "/foo/bar"
Puppet::Resource.from_pson(@data).file.should == "/foo/bar"
end
it "should set its line to the provided line" do
@data['line'] = 50
Puppet::Resource.from_pson(@data).line.should == 50
end
it "should 'exported' to true if set in the pson data" do
@data['exported'] = true
Puppet::Resource.from_pson(@data).exported.should be_true
end
it "should 'exported' to false if not set in the pson data" do
Puppet::Resource.from_pson(@data).exported.should be_false
end
it "should fail if no title is provided" do
@data.delete('title')
lambda { Puppet::Resource.from_pson(@data) }.should raise_error(ArgumentError)
end
it "should fail if no type is provided" do
@data.delete('type')
lambda { Puppet::Resource.from_pson(@data) }.should raise_error(ArgumentError)
end
it "should set each of the provided parameters" do
@data['parameters'] = {'foo' => %w{one two}, 'fee' => %w{three four}}
resource = Puppet::Resource.from_pson(@data)
resource['foo'].should == %w{one two}
resource['fee'].should == %w{three four}
end
it "should convert single-value array parameters to normal values" do
@data['parameters'] = {'foo' => %w{one}}
resource = Puppet::Resource.from_pson(@data)
resource['foo'].should == %w{one}
end
end
describe "it should implement to_resource" do
resource = Puppet::Resource.new("file", "/my/file")
resource.to_resource.should == resource
end
describe "because it is an indirector model" do
it "should include Puppet::Indirector" do
Puppet::Resource.should be_is_a(Puppet::Indirector)
end
it "should have a default terminus" do
Puppet::Resource.indirection.terminus_class.should == :ral
end
it "should have a name" do
Puppet::Resource.new("file", "/my/file").name.should == "File//my/file"
end
end
describe "when resolving resources with a catalog" do
it "should resolve all resources using the catalog" do
catalog = mock 'catalog'
resource = Puppet::Resource.new("foo::bar", "yay")
resource.catalog = catalog
catalog.expects(:resource).with("Foo::Bar[yay]").returns(:myresource)
resource.resolve.should == :myresource
end
end
describe "when generating the uniqueness key" do
it "should include all of the key_attributes in alphabetical order by attribute name" do
Puppet::Type.type(:file).stubs(:key_attributes).returns [:myvar, :owner, :path]
Puppet::Type.type(:file).stubs(:title_patterns).returns(
[ [ /(.*)/, [ [:path, lambda{|x| x} ] ] ] ]
)
res = Puppet::Resource.new("file", "/my/file", :parameters => {:owner => 'root', :content => 'hello'})
res.uniqueness_key.should == [ nil, 'root', '/my/file']
end
end
end
diff --git a/spec/unit/ssl/certificate_authority/interface_spec.rb b/spec/unit/ssl/certificate_authority/interface_spec.rb
index d8c351ae2..5cf4073df 100755
--- a/spec/unit/ssl/certificate_authority/interface_spec.rb
+++ b/spec/unit/ssl/certificate_authority/interface_spec.rb
@@ -1,333 +1,333 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../spec_helper'
require 'puppet/ssl/certificate_authority'
-describe "a normal interface method", :shared => true do
+shared_examples_for "a normal interface method" do
it "should call the method on the CA for each host specified if an array was provided" do
@ca.expects(@method).with("host1")
@ca.expects(@method).with("host2")
@applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, :to => %w{host1 host2})
@applier.apply(@ca)
end
it "should call the method on the CA for all existing certificates if :all was provided" do
@ca.expects(:list).returns %w{host1 host2}
@ca.expects(@method).with("host1")
@ca.expects(@method).with("host2")
@applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, :to => :all)
@applier.apply(@ca)
end
end
describe Puppet::SSL::CertificateAuthority::Interface do
before do
@class = Puppet::SSL::CertificateAuthority::Interface
end
describe "when initializing" do
it "should set its method using its settor" do
@class.any_instance.expects(:method=).with(:generate)
@class.new(:generate, :to => :all)
end
it "should set its subjects using the settor" do
@class.any_instance.expects(:subjects=).with(:all)
@class.new(:generate, :to => :all)
end
it "should set the digest if given" do
interface = @class.new(:generate, :to => :all, :digest => :digest)
interface.digest.should == :digest
end
it "should set the digest to md5 if none given" do
interface = @class.new(:generate, :to => :all)
interface.digest.should == :MD5
end
end
describe "when setting the method" do
it "should set the method" do
@class.new(:generate, :to => :all).method.should == :generate
end
it "should fail if the method isn't a member of the INTERFACE_METHODS array" do
Puppet::SSL::CertificateAuthority::Interface::INTERFACE_METHODS.expects(:include?).with(:thing).returns false
lambda { @class.new(:thing, :to => :all) }.should raise_error(ArgumentError)
end
end
describe "when setting the subjects" do
it "should set the subjects" do
@class.new(:generate, :to => :all).subjects.should == :all
end
it "should fail if the subjects setting isn't :all or an array" do
lambda { @class.new(:generate, "other") }.should raise_error(ArgumentError)
end
end
it "should have a method for triggering the application" do
@class.new(:generate, :to => :all).should respond_to(:apply)
end
describe "when applying" do
before do
# We use a real object here, because :verify can't be stubbed, apparently.
@ca = Object.new
end
it "should raise InterfaceErrors" do
@applier = @class.new(:revoke, :to => :all)
@ca.expects(:list).raises Puppet::SSL::CertificateAuthority::Interface::InterfaceError
lambda { @applier.apply(@ca) }.should raise_error(Puppet::SSL::CertificateAuthority::Interface::InterfaceError)
end
it "should log non-Interface failures rather than failing" do
@applier = @class.new(:revoke, :to => :all)
@ca.expects(:list).raises ArgumentError
Puppet.expects(:err)
lambda { @applier.apply(@ca) }.should_not raise_error
end
describe "with an empty array specified and the method is not list" do
it "should fail" do
@applier = @class.new(:sign, :to => [])
lambda { @applier.apply(@ca) }.should raise_error(ArgumentError)
end
end
describe ":generate" do
it "should fail if :all was specified" do
@applier = @class.new(:generate, :to => :all)
lambda { @applier.apply(@ca) }.should raise_error(ArgumentError)
end
it "should call :generate on the CA for each host specified" do
@applier = @class.new(:generate, :to => %w{host1 host2})
@ca.expects(:generate).with("host1")
@ca.expects(:generate).with("host2")
@applier.apply(@ca)
end
end
describe ":verify" do
before { @method = :verify }
#it_should_behave_like "a normal interface method"
it "should call the method on the CA for each host specified if an array was provided" do
# LAK:NOTE Mocha apparently doesn't allow you to mock :verify, but I'm confident this works in real life.
end
it "should call the method on the CA for all existing certificates if :all was provided" do
# LAK:NOTE Mocha apparently doesn't allow you to mock :verify, but I'm confident this works in real life.
end
end
describe ":destroy" do
before { @method = :destroy }
it_should_behave_like "a normal interface method"
end
describe ":revoke" do
before { @method = :revoke }
it_should_behave_like "a normal interface method"
end
describe ":sign" do
describe "and an array of names was provided" do
before do
@applier = @class.new(:sign, :to => %w{host1 host2})
end
it "should sign the specified waiting certificate requests" do
@ca.expects(:sign).with("host1")
@ca.expects(:sign).with("host2")
@applier.apply(@ca)
end
end
describe "and :all was provided" do
it "should sign all waiting certificate requests" do
@ca.stubs(:waiting?).returns(%w{cert1 cert2})
@ca.expects(:sign).with("cert1")
@ca.expects(:sign).with("cert2")
@applier = @class.new(:sign, :to => :all)
@applier.apply(@ca)
end
it "should fail if there are no waiting certificate requests" do
@ca.stubs(:waiting?).returns([])
@applier = @class.new(:sign, :to => :all)
lambda { @applier.apply(@ca) }.should raise_error(Puppet::SSL::CertificateAuthority::Interface::InterfaceError)
end
end
end
describe ":list" do
describe "and an empty array was provided" do
it "should print a string containing all certificate requests" do
@ca.expects(:waiting?).returns %w{host1 host2}
@ca.stubs(:verify)
@applier = @class.new(:list, :to => [])
@applier.expects(:puts).with "host1\nhost2"
@applier.apply(@ca)
end
end
describe "and :all was provided" do
it "should print a string containing all certificate requests and certificates" do
@ca.expects(:waiting?).returns %w{host1 host2}
@ca.expects(:list).returns %w{host3 host4}
@ca.stubs(:verify)
@ca.stubs(:fingerprint).returns "fingerprint"
@ca.expects(:verify).with("host3").raises(Puppet::SSL::CertificateAuthority::CertificateVerificationError.new(23), "certificate revoked")
@applier = @class.new(:list, :to => :all)
@applier.expects(:puts).with "host1 (fingerprint)"
@applier.expects(:puts).with "host2 (fingerprint)"
@applier.expects(:puts).with "- host3 (fingerprint) (certificate revoked)"
@applier.expects(:puts).with "+ host4 (fingerprint)"
@applier.apply(@ca)
end
end
describe "and :signed was provided" do
it "should print a string containing all signed certificate requests and certificates" do
@ca.expects(:list).returns %w{host1 host2}
@applier = @class.new(:list, :to => :signed)
@applier.apply(@ca)
end
end
describe "and an array of names was provided" do
it "should print a string of all named hosts that have a waiting request" do
@ca.expects(:waiting?).returns %w{host1 host2}
@ca.expects(:list).returns %w{host3 host4}
@ca.stubs(:fingerprint).returns "fingerprint"
@ca.stubs(:verify)
@applier = @class.new(:list, :to => %w{host1 host2 host3 host4})
@applier.expects(:puts).with "host1 (fingerprint)"
@applier.expects(:puts).with "host2 (fingerprint)"
@applier.expects(:puts).with "+ host3 (fingerprint)"
@applier.expects(:puts).with "+ host4 (fingerprint)"
@applier.apply(@ca)
end
end
end
describe ":print" do
describe "and :all was provided" do
it "should print all certificates" do
@ca.expects(:list).returns %w{host1 host2}
@applier = @class.new(:print, :to => :all)
@ca.expects(:print).with("host1").returns "h1"
@applier.expects(:puts).with "h1"
@ca.expects(:print).with("host2").returns "h2"
@applier.expects(:puts).with "h2"
@applier.apply(@ca)
end
end
describe "and an array of names was provided" do
it "should print each named certificate if found" do
@applier = @class.new(:print, :to => %w{host1 host2})
@ca.expects(:print).with("host1").returns "h1"
@applier.expects(:puts).with "h1"
@ca.expects(:print).with("host2").returns "h2"
@applier.expects(:puts).with "h2"
@applier.apply(@ca)
end
it "should log any named but not found certificates" do
@applier = @class.new(:print, :to => %w{host1 host2})
@ca.expects(:print).with("host1").returns "h1"
@applier.expects(:puts).with "h1"
@ca.expects(:print).with("host2").returns nil
Puppet.expects(:err).with { |msg| msg.include?("host2") }
@applier.apply(@ca)
end
end
end
describe ":fingerprint" do
it "should fingerprint with the set digest algorithm" do
@applier = @class.new(:fingerprint, :to => %w{host1}, :digest => :digest)
@ca.expects(:fingerprint).with("host1", :digest).returns "fingerprint1"
@applier.expects(:puts).with "host1 fingerprint1"
@applier.apply(@ca)
end
describe "and :all was provided" do
it "should fingerprint all certificates (including waiting ones)" do
@ca.expects(:list).returns %w{host1}
@ca.expects(:waiting?).returns %w{host2}
@applier = @class.new(:fingerprint, :to => :all)
@ca.expects(:fingerprint).with("host1", :MD5).returns "fingerprint1"
@applier.expects(:puts).with "host1 fingerprint1"
@ca.expects(:fingerprint).with("host2", :MD5).returns "fingerprint2"
@applier.expects(:puts).with "host2 fingerprint2"
@applier.apply(@ca)
end
end
describe "and an array of names was provided" do
it "should print each named certificate if found" do
@applier = @class.new(:fingerprint, :to => %w{host1 host2})
@ca.expects(:fingerprint).with("host1", :MD5).returns "fingerprint1"
@applier.expects(:puts).with "host1 fingerprint1"
@ca.expects(:fingerprint).with("host2", :MD5).returns "fingerprint2"
@applier.expects(:puts).with "host2 fingerprint2"
@applier.apply(@ca)
end
end
end
end
end
diff --git a/spec/unit/transaction/change_spec.rb b/spec/unit/transaction/change_spec.rb
deleted file mode 100755
index fbc662df0..000000000
--- a/spec/unit/transaction/change_spec.rb
+++ /dev/null
@@ -1,206 +0,0 @@
-#!/usr/bin/env ruby
-
-require File.dirname(__FILE__) + '/../../spec_helper'
-
-require 'puppet/transaction/change'
-
-describe Puppet::Transaction::Change do
- Change = Puppet::Transaction::Change
-
- describe "when initializing" do
- before do
- @property = stub 'property', :path => "/property/path", :should => "shouldval"
- end
-
- it "should require the property and current value" do
- lambda { Change.new }.should raise_error
- end
-
- it "should set its property to the provided property" do
- Change.new(@property, "value").property.should == :property
- end
-
- it "should set its 'is' value to the provided value" do
- Change.new(@property, "value").is.should == "value"
- end
-
- it "should retrieve the 'should' value from the property" do
- # Yay rspec :)
- Change.new(@property, "value").should.should == @property.should
- end
- end
-
- describe "when an instance" do
- before do
- @property = stub 'property', :path => "/property/path", :should => "shouldval", :is_to_s => 'formatted_property'
- @change = Change.new(@property, "value")
- end
-
- it "should be noop if the property is noop" do
- @property.expects(:noop).returns true
- @change.noop?.should be_true
- end
-
- it "should be auditing if set so" do
- @change.auditing = true
- @change.must be_auditing
- end
-
- it "should set its resource to the proxy if it has one" do
- @change.proxy = :myresource
- @change.resource.should == :myresource
- end
-
- it "should set its resource to the property's resource if no proxy is set" do
- @property.expects(:resource).returns :myresource
- @change.resource.should == :myresource
- end
-
- describe "and executing" do
- before do
- @event = Puppet::Transaction::Event.new(:myevent)
- @event.stubs(:send_log)
- @change.stubs(:noop?).returns false
- @property.stubs(:event).returns @event
-
- @property.stub_everything
- @property.stubs(:resource).returns "myresource"
- @property.stubs(:name).returns :myprop
- end
-
- describe "in noop mode" do
- before { @change.stubs(:noop?).returns true }
-
- it "should log that it is in noop" do
- @property.expects(:is_to_s)
- @property.expects(:should_to_s)
-
- @event.expects(:message=).with { |msg| msg.include?("should be") }
-
- @change.apply
- end
-
- it "should produce a :noop event and return" do
- @property.stub_everything
- @property.expects(:sync).never.never.never.never.never # VERY IMPORTANT
-
- @event.expects(:status=).with("noop")
-
- @change.apply.should == @event
- end
- end
-
- describe "in audit mode" do
- before do
- @change.auditing = true
- @change.old_audit_value = "old_value"
- @property.stubs(:insync?).returns(true)
- end
-
- it "should log that it is in audit mode" do
- message = nil
- @event.expects(:message=).with { |msg| message = msg }
-
- @change.apply
- message.should == "audit change: previously recorded value formatted_property has been changed to formatted_property"
- end
-
- it "should produce a :audit event and return" do
- @property.stub_everything
-
- @event.expects(:status=).with("audit")
-
- @change.apply.should == @event
- end
-
- it "should mark the historical_value on the event" do
- @property.stub_everything
-
- @change.apply.historical_value.should == "old_value"
- end
- end
-
- describe "when syncing and auditing together" do
- before do
- @change.auditing = true
- @change.old_audit_value = "old_value"
- @property.stubs(:insync?).returns(false)
- end
-
- it "should sync the property" do
- @property.expects(:sync)
-
- @change.apply
- end
-
- it "should produce a success event" do
- @property.stub_everything
-
- @change.apply.status.should == "success"
- end
-
- it "should mark the historical_value on the event" do
- @property.stub_everything
-
- @change.apply.historical_value.should == "old_value"
- end
- end
-
- it "should sync the property" do
- @property.expects(:sync)
-
- @change.apply
- end
-
- it "should return the default event if syncing the property returns nil" do
- @property.stubs(:sync).returns nil
-
- @property.expects(:event).with(nil).returns @event
-
- @change.apply.should == @event
- end
-
- it "should return the default event if syncing the property returns an empty array" do
- @property.stubs(:sync).returns []
-
- @property.expects(:event).with(nil).returns @event
-
- @change.apply.should == @event
- end
-
- it "should log the change" do
- @property.expects(:sync).returns [:one]
-
- @event.expects(:send_log)
-
- @change.apply
- end
-
- it "should set the event's message to the change log" do
- @property.expects(:change_to_s).returns "my change"
- @change.apply.message.should == "my change"
- end
-
- it "should set the event's status to 'success'" do
- @change.apply.status.should == "success"
- end
-
- describe "and the change fails" do
- before { @property.expects(:sync).raises "an exception" }
-
- it "should catch the exception and log the err" do
- @event.expects(:send_log)
- lambda { @change.apply }.should_not raise_error
- end
-
- it "should mark the event status as 'failure'" do
- @change.apply.status.should == "failure"
- end
-
- it "should set the event log to a failure log" do
- @change.apply.message.should be_include("failed")
- end
- end
- end
- end
-end
diff --git a/spec/unit/transaction/event_spec.rb b/spec/unit/transaction/event_spec.rb
index 9cf6bc165..6ed14722b 100755
--- a/spec/unit/transaction/event_spec.rb
+++ b/spec/unit/transaction/event_spec.rb
@@ -1,108 +1,127 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/transaction/event'
describe Puppet::Transaction::Event do
- [:previous_value, :desired_value, :property, :resource, :name, :message, :node, :version, :file, :line, :tags].each do |attr|
+ [:previous_value, :desired_value, :property, :resource, :name, :message, :file, :line, :tags, :audited].each do |attr|
it "should support #{attr}" do
event = Puppet::Transaction::Event.new
event.send(attr.to_s + "=", "foo")
event.send(attr).should == "foo"
end
end
it "should always convert the property to a string" do
Puppet::Transaction::Event.new(:property => :foo).property.should == "foo"
end
it "should always convert the resource to a string" do
Puppet::Transaction::Event.new(:resource => :foo).resource.should == "foo"
end
it "should produce the message when converted to a string" do
event = Puppet::Transaction::Event.new
event.expects(:message).returns "my message"
event.to_s.should == "my message"
end
it "should support 'status'" do
event = Puppet::Transaction::Event.new
event.status = "success"
event.status.should == "success"
end
it "should fail if the status is not to 'audit', 'noop', 'success', or 'failure" do
event = Puppet::Transaction::Event.new
lambda { event.status = "foo" }.should raise_error(ArgumentError)
end
it "should support tags" do
Puppet::Transaction::Event.ancestors.should include(Puppet::Util::Tagging)
end
it "should create a timestamp at its creation time" do
Puppet::Transaction::Event.new.time.should be_instance_of(Time)
end
+ describe "audit property" do
+ it "should default to false" do
+ Puppet::Transaction::Event.new.audited.should == false
+ end
+ end
+
describe "when sending logs" do
before do
Puppet::Util::Log.stubs(:new)
end
it "should set the level to the resources's log level if the event status is 'success' and a resource is available" do
resource = stub 'resource'
resource.expects(:[]).with(:loglevel).returns :myloglevel
Puppet::Util::Log.expects(:create).with { |args| args[:level] == :myloglevel }
Puppet::Transaction::Event.new(:status => "success", :resource => resource).send_log
end
it "should set the level to 'notice' if the event status is 'success' and no resource is available" do
Puppet::Util::Log.expects(:new).with { |args| args[:level] == :notice }
Puppet::Transaction::Event.new(:status => "success").send_log
end
it "should set the level to 'notice' if the event status is 'noop'" do
Puppet::Util::Log.expects(:new).with { |args| args[:level] == :notice }
Puppet::Transaction::Event.new(:status => "noop").send_log
end
it "should set the level to 'err' if the event status is 'failure'" do
Puppet::Util::Log.expects(:new).with { |args| args[:level] == :err }
Puppet::Transaction::Event.new(:status => "failure").send_log
end
it "should set the 'message' to the event log" do
Puppet::Util::Log.expects(:new).with { |args| args[:message] == "my message" }
Puppet::Transaction::Event.new(:message => "my message").send_log
end
it "should set the tags to the event tags" do
Puppet::Util::Log.expects(:new).with { |args| args[:tags] == %w{one two} }
Puppet::Transaction::Event.new(:tags => %w{one two}).send_log
end
- [:file, :line, :version].each do |attr|
+ [:file, :line].each do |attr|
it "should pass the #{attr}" do
Puppet::Util::Log.expects(:new).with { |args| args[attr] == "my val" }
Puppet::Transaction::Event.new(attr => "my val").send_log
end
end
it "should use the source description as the source if one is set" do
Puppet::Util::Log.expects(:new).with { |args| args[:source] == "/my/param" }
Puppet::Transaction::Event.new(:source_description => "/my/param", :resource => "Foo[bar]", :property => "foo").send_log
end
it "should use the property as the source if one is available and no source description is set" do
Puppet::Util::Log.expects(:new).with { |args| args[:source] == "foo" }
Puppet::Transaction::Event.new(:resource => "Foo[bar]", :property => "foo").send_log
end
it "should use the property as the source if one is available and no property or source description is set" do
Puppet::Util::Log.expects(:new).with { |args| args[:source] == "Foo[bar]" }
Puppet::Transaction::Event.new(:resource => "Foo[bar]").send_log
end
end
+
+ describe "When converting to YAML" do
+ it "should include only documented attributes" do
+ resource = Puppet::Type.type(:file).new(:title => "/tmp/foo")
+ event = Puppet::Transaction::Event.new(:source_description => "/my/param", :resource => resource,
+ :file => "/foo.rb", :line => 27, :tags => %w{one two},
+ :desired_value => 7, :historical_value => 'Brazil',
+ :message => "Help I'm trapped in a spec test",
+ :name => :mode_changed, :previous_value => 6, :property => :mode,
+ :status => 'success')
+ event.to_yaml_properties.should == Puppet::Transaction::Event::YAML_ATTRIBUTES.sort
+ end
+ end
end
diff --git a/spec/unit/transaction/report_spec.rb b/spec/unit/transaction/report_spec.rb
index 77f82159b..766d4f14d 100755
--- a/spec/unit/transaction/report_spec.rb
+++ b/spec/unit/transaction/report_spec.rb
@@ -1,242 +1,290 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/transaction/report'
describe Puppet::Transaction::Report do
before do
Puppet::Util::Storage.stubs(:store)
end
it "should set its host name to the certname" do
Puppet.settings.expects(:value).with(:certname).returns "myhost"
- Puppet::Transaction::Report.new.host.should == "myhost"
+ Puppet::Transaction::Report.new("apply").host.should == "myhost"
end
it "should return its host name as its name" do
- r = Puppet::Transaction::Report.new
+ r = Puppet::Transaction::Report.new("apply")
r.name.should == r.host
end
it "should create an initialization timestamp" do
Time.expects(:now).returns "mytime"
- Puppet::Transaction::Report.new.time.should == "mytime"
- end
-
- it "should have a default 'kind' of 'apply'" do
- Puppet::Transaction::Report.new.kind.should == "apply"
+ Puppet::Transaction::Report.new("apply").time.should == "mytime"
end
it "should take a 'kind' as an argument" do
Puppet::Transaction::Report.new("inspect").kind.should == "inspect"
end
+ it "should take a 'configuration_version' as an argument" do
+ Puppet::Transaction::Report.new("inspect", "some configuration version").configuration_version.should == "some configuration version"
+ end
+
+ it "should be able to set configuration_version" do
+ report = Puppet::Transaction::Report.new("inspect")
+ report.configuration_version = "some version"
+ report.configuration_version.should == "some version"
+ end
+
describe "when accepting logs" do
before do
- @report = Puppet::Transaction::Report.new
+ @report = Puppet::Transaction::Report.new("apply")
end
it "should add new logs to the log list" do
@report << "log"
@report.logs[-1].should == "log"
end
it "should return self" do
r = @report << "log"
r.should equal(@report)
end
end
describe "when accepting resource statuses" do
before do
- @report = Puppet::Transaction::Report.new
+ @report = Puppet::Transaction::Report.new("apply")
end
it "should add each status to its status list" do
status = stub 'status', :resource => "foo"
@report.add_resource_status status
@report.resource_statuses["foo"].should equal(status)
end
end
describe "when using the indirector" do
it "should redirect :find to the indirection" do
@indirection = stub 'indirection', :name => :report
Puppet::Transaction::Report.stubs(:indirection).returns(@indirection)
@indirection.expects(:find)
Puppet::Transaction::Report.find(:report)
end
it "should redirect :save to the indirection" do
Facter.stubs(:value).returns("eh")
@indirection = stub 'indirection', :name => :report
Puppet::Transaction::Report.stubs(:indirection).returns(@indirection)
- report = Puppet::Transaction::Report.new
+ report = Puppet::Transaction::Report.new("apply")
@indirection.expects(:save)
report.save
end
it "should default to the 'processor' terminus" do
Puppet::Transaction::Report.indirection.terminus_class.should == :processor
end
it "should delegate its name attribute to its host method" do
- report = Puppet::Transaction::Report.new
+ report = Puppet::Transaction::Report.new("apply")
report.expects(:host).returns "me"
report.name.should == "me"
end
after do
Puppet::Util::Cacher.expire
end
end
describe "when computing exit status" do
it "should produce 2 if changes are present" do
- report = Puppet::Transaction::Report.new
- report.add_metric("changes", {:total => 1})
- report.add_metric("resources", {:failed => 0})
+ report = Puppet::Transaction::Report.new("apply")
+ report.add_metric("changes", {"total" => 1})
+ report.add_metric("resources", {"failed" => 0})
report.exit_status.should == 2
end
it "should produce 4 if failures are present" do
- report = Puppet::Transaction::Report.new
- report.add_metric("changes", {:total => 0})
- report.add_metric("resources", {:failed => 1})
+ report = Puppet::Transaction::Report.new("apply")
+ report.add_metric("changes", {"total" => 0})
+ report.add_metric("resources", {"failed" => 1})
report.exit_status.should == 4
end
it "should produce 6 if both changes and failures are present" do
- report = Puppet::Transaction::Report.new
- report.add_metric("changes", {:total => 1})
- report.add_metric("resources", {:failed => 1})
+ report = Puppet::Transaction::Report.new("apply")
+ report.add_metric("changes", {"total" => 1})
+ report.add_metric("resources", {"failed" => 1})
report.exit_status.should == 6
end
end
- describe "when calculating metrics" do
+ describe "before finalizing the report" do
+ it "should have a status of 'failed'" do
+ report = Puppet::Transaction::Report.new("apply")
+ report.status.should == 'failed'
+ end
+ end
+
+ describe "when finalizing the report" do
before do
- @report = Puppet::Transaction::Report.new
+ @report = Puppet::Transaction::Report.new("apply")
end
def metric(name, value)
if metric = @report.metrics[name.to_s]
metric[value]
else
nil
end
end
def add_statuses(count, type = :file)
- 3.times do |i|
+ count.times do |i|
status = Puppet::Resource::Status.new(Puppet::Type.type(type).new(:title => "/my/path#{i}"))
yield status if block_given?
@report.add_resource_status status
end
end
[:time, :resources, :changes, :events].each do |type|
it "should add #{type} metrics" do
- @report.calculate_metrics
+ @report.finalize_report
@report.metrics[type.to_s].should be_instance_of(Puppet::Transaction::Metric)
end
end
describe "for resources" do
it "should provide the total number of resources" do
add_statuses(3)
- @report.calculate_metrics
- metric(:resources, :total).should == 3
+ @report.finalize_report
+ metric(:resources, "total").should == 3
end
Puppet::Resource::Status::STATES.each do |state|
it "should provide the number of #{state} resources as determined by the status objects" do
add_statuses(3) { |status| status.send(state.to_s + "=", true) }
- @report.calculate_metrics
- metric(:resources, state).should == 3
+ @report.finalize_report
+ metric(:resources, state.to_s).should == 3
end
end
+
+ it "should mark the report as 'failed' if there are failing resources" do
+ add_statuses(1) { |status| status.failed = true }
+ @report.finalize_report
+ @report.status.should == 'failed'
+ end
end
describe "for changes" do
- it "should provide the number of changes from the resource statuses" do
- add_statuses(3) { |status| status.change_count = 3 }
- @report.calculate_metrics
- metric(:changes, :total).should == 9
+ it "should provide the number of changes from the resource statuses and mark the report as 'changed'" do
+ add_statuses(3) { |status| 3.times { status << Puppet::Transaction::Event.new(:status => 'success') } }
+ @report.finalize_report
+ metric(:changes, "total").should == 9
+ @report.status.should == 'changed'
+ end
+
+ it "should provide a total even if there are no changes, and mark the report as 'unchanged'" do
+ @report.finalize_report
+ metric(:changes, "total").should == 0
+ @report.status.should == 'unchanged'
end
end
describe "for times" do
it "should provide the total amount of time for each resource type" do
add_statuses(3, :file) do |status|
status.evaluation_time = 1
end
add_statuses(3, :exec) do |status|
status.evaluation_time = 2
end
add_statuses(3, :mount) do |status|
status.evaluation_time = 3
end
- @report.calculate_metrics
+ @report.finalize_report
metric(:time, "file").should == 3
metric(:time, "exec").should == 6
metric(:time, "mount").should == 9
end
it "should add any provided times from external sources" do
@report.add_times :foobar, 50
- @report.calculate_metrics
+ @report.finalize_report
metric(:time, "foobar").should == 50
end
+
+ it "should have a total time" do
+ add_statuses(3, :file) do |status|
+ status.evaluation_time = 1.25
+ end
+ @report.add_times :config_retrieval, 0.5
+ @report.finalize_report
+ metric(:time, "total").should == 4.25
+ end
end
describe "for events" do
it "should provide the total number of events" do
add_statuses(3) do |status|
- 3.times { |i| status.add_event(Puppet::Transaction::Event.new) }
+ 3.times { |i| status.add_event(Puppet::Transaction::Event.new :status => 'success') }
end
- @report.calculate_metrics
- metric(:events, :total).should == 9
+ @report.finalize_report
+ metric(:events, "total").should == 9
+ end
+
+ it "should provide the total even if there are no events" do
+ @report.finalize_report
+ metric(:events, "total").should == 0
end
Puppet::Transaction::Event::EVENT_STATUSES.each do |status_name|
it "should provide the number of #{status_name} events" do
add_statuses(3) do |status|
3.times do |i|
event = Puppet::Transaction::Event.new
event.status = status_name
status.add_event(event)
end
end
- @report.calculate_metrics
+ @report.finalize_report
metric(:events, status_name).should == 9
end
end
end
end
describe "when producing a summary" do
before do
resource = Puppet::Type.type(:notify).new(:name => "testing")
catalog = Puppet::Resource::Catalog.new
catalog.add_resource resource
trans = catalog.apply
@report = trans.report
- @report.calculate_metrics
+ @report.finalize_report
end
%w{Changes Total Resources}.each do |main|
it "should include information on #{main} in the summary" do
@report.summary.should be_include(main)
end
end
end
+
+ describe "when outputting yaml" do
+ it "should not include @external_times" do
+ report = Puppet::Transaction::Report.new('apply')
+ report.add_times('config_retrieval', 1.0)
+ report.to_yaml_properties.should_not include('@external_times')
+ end
+ end
end
diff --git a/spec/unit/transaction/resource_harness_spec.rb b/spec/unit/transaction/resource_harness_spec.rb
index b143c21ed..65c148a93 100755
--- a/spec/unit/transaction/resource_harness_spec.rb
+++ b/spec/unit/transaction/resource_harness_spec.rb
@@ -1,514 +1,398 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet_spec/files'
require 'puppet/transaction/resource_harness'
describe Puppet::Transaction::ResourceHarness do
include PuppetSpec::Files
before do
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@resource = Puppet::Type.type(:file).new :path => "/my/file"
@harness = Puppet::Transaction::ResourceHarness.new(@transaction)
@current_state = Puppet::Resource.new(:file, "/my/file")
@resource.stubs(:retrieve).returns @current_state
@status = Puppet::Resource::Status.new(@resource)
Puppet::Resource::Status.stubs(:new).returns @status
end
it "should accept a transaction at initialization" do
harness = Puppet::Transaction::ResourceHarness.new(@transaction)
harness.transaction.should equal(@transaction)
end
it "should delegate to the transaction for its relationship graph" do
@transaction.expects(:relationship_graph).returns "relgraph"
Puppet::Transaction::ResourceHarness.new(@transaction).relationship_graph.should == "relgraph"
end
describe "when evaluating a resource" do
it "should create and return a resource status instance for the resource" do
@harness.evaluate(@resource).should be_instance_of(Puppet::Resource::Status)
end
it "should fail if no status can be created" do
Puppet::Resource::Status.expects(:new).raises ArgumentError
lambda { @harness.evaluate(@resource) }.should raise_error
end
it "should retrieve the current state of the resource" do
@resource.expects(:retrieve).returns @current_state
@harness.evaluate(@resource)
end
it "should mark the resource as failed and return if the current state cannot be retrieved" do
@resource.expects(:retrieve).raises ArgumentError
@harness.evaluate(@resource).should be_failed
end
- it "should use the status and retrieved state to determine which changes need to be made" do
- @harness.expects(:changes_to_perform).with(@status, @resource).returns []
- @harness.evaluate(@resource)
- end
-
- it "should mark the status as out of sync and apply the created changes if there are any" do
- changes = %w{mychanges}
- @harness.expects(:changes_to_perform).returns changes
- @harness.expects(:apply_changes).with(@status, changes)
- @harness.evaluate(@resource).should be_out_of_sync
- end
-
- it "should cache the last-synced time" do
- changes = %w{mychanges}
- @harness.stubs(:changes_to_perform).returns changes
- @harness.stubs(:apply_changes)
- @harness.expects(:cache).with { |resource, name, time| name == :synced and time.is_a?(Time) }
- @harness.evaluate(@resource)
- end
-
- it "should flush the resource when applying changes if appropriate" do
- changes = %w{mychanges}
- @harness.stubs(:changes_to_perform).returns changes
- @harness.stubs(:apply_changes)
- @resource.expects(:flush)
- @harness.evaluate(@resource)
- end
-
- it "should use the status and retrieved state to determine which changes need to be made" do
- @harness.expects(:changes_to_perform).with(@status, @resource).returns []
- @harness.evaluate(@resource)
- end
-
- it "should not attempt to apply changes if none need to be made" do
- @harness.expects(:changes_to_perform).returns []
- @harness.expects(:apply_changes).never
- @harness.evaluate(@resource).should_not be_out_of_sync
- end
-
it "should store the resource's evaluation time in the resource status" do
@harness.evaluate(@resource).evaluation_time.should be_instance_of(Float)
end
-
- it "should set the change count to the total number of changes" do
- changes = %w{a b c d}
- @harness.expects(:changes_to_perform).returns changes
- @harness.expects(:apply_changes).with(@status, changes)
- @harness.evaluate(@resource).change_count.should == 4
- end
end
- describe "when creating changes" do
- before do
- @current_state = Puppet::Resource.new(:file, "/my/file")
- @resource.stubs(:retrieve).returns @current_state
- Puppet.features.stubs(:root?).returns true
- end
-
- it "should retrieve the current values from the resource" do
- @resource.expects(:retrieve).returns @current_state
- @harness.changes_to_perform(@status, @resource)
- end
-
- it "should cache that the resource was checked" do
- @harness.expects(:cache).with { |resource, name, time| name == :checked and time.is_a?(Time) }
- @harness.changes_to_perform(@status, @resource)
- end
-
- it "should create changes with the appropriate property and current value" do
- @resource[:ensure] = :present
- @current_state[:ensure] = :absent
-
- change = stub 'change'
- Puppet::Transaction::Change.expects(:new).with(@resource.parameter(:ensure), :absent).returns change
-
- @harness.changes_to_perform(@status, @resource)[0].should equal(change)
- end
-
- it "should not attempt to manage properties that do not have desired values set" do
- mode = @resource.newattr(:mode)
- @current_state[:mode] = :absent
-
- mode.expects(:insync?).never
-
- @harness.changes_to_perform(@status, @resource)
- end
-
-# it "should copy audited parameters" do
-# @resource[:audit] = :mode
-# @harness.cache(@resource, :mode, "755")
-# @harness.changes_to_perform(@status, @resource)
-# @resource[:mode].should == "755"
-# end
-
- it "should mark changes created as a result of auditing as auditing changes" do
- @current_state[:mode] = 0644
- @resource[:audit] = :mode
- @harness.cache(@resource, :mode, "755")
- @harness.changes_to_perform(@status, @resource)[0].must be_auditing
- end
-
- describe "and the 'ensure' parameter is present but not in sync" do
- it "should return a single change for the 'ensure' parameter" do
- @resource[:ensure] = :present
- @resource[:mode] = "755"
- @current_state[:ensure] = :absent
- @current_state[:mode] = :absent
-
- @resource.stubs(:retrieve).returns @current_state
-
- changes = @harness.changes_to_perform(@status, @resource)
- changes.length.should == 1
- changes[0].property.name.should == :ensure
- end
- end
-
- describe "and the 'ensure' parameter should be set to 'absent', and is correctly set to 'absent'" do
- it "should return no changes" do
- @resource[:ensure] = :absent
- @resource[:mode] = "755"
- @current_state[:ensure] = :absent
- @current_state[:mode] = :absent
-
- @harness.changes_to_perform(@status, @resource).should == []
- end
- end
-
- describe "and the 'ensure' parameter is 'absent' and there is no 'desired value'" do
- it "should return no changes" do
- @resource.newattr(:ensure)
- @resource[:mode] = "755"
- @current_state[:ensure] = :absent
- @current_state[:mode] = :absent
-
- @harness.changes_to_perform(@status, @resource).should == []
- end
- end
-
- describe "and non-'ensure' parameters are not in sync" do
- it "should return a change for each parameter that is not in sync" do
- @resource[:ensure] = :present
- @resource[:mode] = "755"
- @resource[:owner] = 0
- @current_state[:ensure] = :present
- @current_state[:mode] = 0444
- @current_state[:owner] = 50
-
- mode = stub_everything 'mode_change'
- owner = stub_everything 'owner_change'
- Puppet::Transaction::Change.expects(:new).with(@resource.parameter(:mode), 0444).returns mode
- Puppet::Transaction::Change.expects(:new).with(@resource.parameter(:owner), 50).returns owner
-
- changes = @harness.changes_to_perform(@status, @resource)
- changes.length.should == 2
- changes.should be_include(mode)
- changes.should be_include(owner)
- end
- end
-
- describe "and all parameters are in sync" do
- it "should return an empty array" do
- @resource[:ensure] = :present
- @resource[:mode] = "755"
- @current_state[:ensure] = :present
- @current_state[:mode] = "755"
- @harness.changes_to_perform(@status, @resource).should == []
+ def events_to_hash(events)
+ events.map do |event|
+ hash = {}
+ event.instance_variables.each do |varname|
+ hash[varname] = event.instance_variable_get(varname.to_sym)
end
+ hash
end
end
describe "when applying changes" do
- before do
- @change1 = stub 'change1', :apply => stub("event", :status => "success"), :auditing? => false
- @change2 = stub 'change2', :apply => stub("event", :status => "success"), :auditing? => false
- @changes = [@change1, @change2]
- end
-
- it "should apply the change" do
- @change1.expects(:apply).returns( stub("event", :status => "success") )
- @change2.expects(:apply).returns( stub("event", :status => "success") )
-
- @harness.apply_changes(@status, @changes)
- end
-
- it "should mark the resource as changed" do
- @harness.apply_changes(@status, @changes)
-
- @status.should be_changed
- end
-
- it "should queue the resulting event" do
- @harness.apply_changes(@status, @changes)
-
- @status.events.should be_include(@change1.apply)
- @status.events.should be_include(@change2.apply)
- end
-
- it "should cache the new value if it is an auditing change" do
- @change1.expects(:auditing?).returns true
- property = stub 'property', :name => "foo", :resource => "myres"
- @change1.stubs(:property).returns property
- @change1.stubs(:is).returns "myval"
-
- @harness.apply_changes(@status, @changes)
-
- @harness.cached("myres", "foo").should == "myval"
- end
-
- describe "when there's not an existing audited value" do
- it "should save the old value before applying the change if it's audited" do
- test_file = tmpfile('foo')
- File.open(test_file, "w", 0750).close
-
- resource = Puppet::Type.type(:file).new :path => test_file, :mode => '755', :audit => :mode
-
- @harness.evaluate(resource)
- @harness.cached(resource, :mode).should == "750"
-
- (File.stat(test_file).mode & 0777).should == 0755
- @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ [
- "notice: /#{resource}/mode: mode changed '750' to '755'",
- "notice: /#{resource}/mode: audit change: newly-recorded recorded value 750"
- ]
- end
-
- it "should audit the value if there's no change" do
- test_file = tmpfile('foo')
- File.open(test_file, "w", 0755).close
-
- resource = Puppet::Type.type(:file).new :path => test_file, :mode => '755', :audit => :mode
-
- @harness.evaluate(resource)
- @harness.cached(resource, :mode).should == "755"
-
- (File.stat(test_file).mode & 0777).should == 0755
-
- @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ [
- "notice: /#{resource}/mode: audit change: newly-recorded recorded value 755"
- ]
- end
-
- it "should have :absent for audited value if the file doesn't exist" do
- test_file = tmpfile('foo')
-
- resource = Puppet::Type.type(:file).new :ensure => 'present', :path => test_file, :mode => '755', :audit => :mode
-
- @harness.evaluate(resource)
- @harness.cached(resource, :mode).should == :absent
-
- (File.stat(test_file).mode & 0777).should == 0755
- @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ [
- "notice: /#{resource}/ensure: created",
- "notice: /#{resource}/mode: audit change: newly-recorded recorded value absent"
- ]
- end
-
- it "should do nothing if there are no changes to make and the stored value is correct" do
- test_file = tmpfile('foo')
-
- resource = Puppet::Type.type(:file).new :path => test_file, :mode => '755', :audit => :mode, :ensure => 'absent'
- @harness.cache(resource, :mode, :absent)
-
- @harness.evaluate(resource)
- @harness.cached(resource, :mode).should == :absent
-
- File.exists?(test_file).should == false
- @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ []
- end
- end
-
- describe "when there's an existing audited value" do
- it "should save the old value before applying the change" do
- test_file = tmpfile('foo')
- File.open(test_file, "w", 0750).close
-
- resource = Puppet::Type.type(:file).new :path => test_file, :audit => :mode
- @harness.cache(resource, :mode, '555')
-
- @harness.evaluate(resource)
- @harness.cached(resource, :mode).should == "750"
-
- (File.stat(test_file).mode & 0777).should == 0750
- @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ [
- "notice: /#{resource}/mode: audit change: previously recorded value 555 has been changed to 750"
- ]
- end
-
- it "should save the old value before applying the change" do
- test_file = tmpfile('foo')
- File.open(test_file, "w", 0750).close
-
- resource = Puppet::Type.type(:file).new :path => test_file, :mode => '755', :audit => :mode
- @harness.cache(resource, :mode, '555')
-
- @harness.evaluate(resource)
- @harness.cached(resource, :mode).should == "750"
-
- (File.stat(test_file).mode & 0777).should == 0755
- @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ [
- "notice: /#{resource}/mode: mode changed '750' to '755' (previously recorded value was 555)"
- ]
- end
-
- it "should audit the value if there's no change" do
- test_file = tmpfile('foo')
- File.open(test_file, "w", 0755).close
-
- resource = Puppet::Type.type(:file).new :path => test_file, :mode => '755', :audit => :mode
- @harness.cache(resource, :mode, '555')
-
- @harness.evaluate(resource)
- @harness.cached(resource, :mode).should == "755"
-
- (File.stat(test_file).mode & 0777).should == 0755
- @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ [
- "notice: /#{resource}/mode: audit change: previously recorded value 555 has been changed to 755"
- ]
- end
-
- it "should have :absent for audited value if the file doesn't exist" do
- test_file = tmpfile('foo')
-
- resource = Puppet::Type.type(:file).new :ensure => 'present', :path => test_file, :mode => '755', :audit => :mode
- @harness.cache(resource, :mode, '555')
-
- @harness.evaluate(resource)
- @harness.cached(resource, :mode).should == :absent
-
- (File.stat(test_file).mode & 0777).should == 0755
-
- @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ [
- "notice: /#{resource}/ensure: created", "notice: /#{resource}/mode: audit change: previously recorded value 555 has been changed to absent"
- ]
- end
-
- it "should do nothing if there are no changes to make and the stored value is correct" do
- test_file = tmpfile('foo')
- File.open(test_file, "w", 0755).close
-
- resource = Puppet::Type.type(:file).new :path => test_file, :mode => '755', :audit => :mode
- @harness.cache(resource, :mode, '755')
-
- @harness.evaluate(resource)
- @harness.cached(resource, :mode).should == "755"
-
- (File.stat(test_file).mode & 0777).should == 0755
- @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ []
- end
+ [false, true].each do |noop_mode|; describe (noop_mode ? "in noop mode" : "in normal mode") do
+ [nil, '750'].each do |machine_state|; describe (machine_state ? "with a file initially present" : "with no file initially present") do
+ [nil, '750', '755'].each do |yaml_mode|
+ [nil, :file, :absent].each do |yaml_ensure|; describe "with mode=#{yaml_mode.inspect} and ensure=#{yaml_ensure.inspect} stored in state.yml" do
+ [false, true].each do |auditing_ensure|
+ [false, true].each do |auditing_mode|
+ auditing = []
+ auditing.push(:mode) if auditing_mode
+ auditing.push(:ensure) if auditing_ensure
+ [nil, :file, :absent].each do |ensure_property| # what we set "ensure" to in the manifest
+ [nil, '750', '755'].each do |mode_property| # what we set "mode" to in the manifest
+ manifest_settings = {}
+ manifest_settings[:audit] = auditing if !auditing.empty?
+ manifest_settings[:ensure] = ensure_property if ensure_property
+ manifest_settings[:mode] = mode_property if mode_property
+ describe "with manifest settings #{manifest_settings.inspect}" do; it "should behave properly" do
+ # Set up preconditions
+ test_file = tmpfile('foo')
+ if machine_state
+ File.open(test_file, 'w', machine_state.to_i(8)).close
+ end
+
+ Puppet[:noop] = noop_mode
+ params = { :path => test_file, :backup => false }
+ params.merge!(manifest_settings)
+ resource = Puppet::Type.type(:file).new params
+
+ @harness.cache(resource, :mode, yaml_mode) if yaml_mode
+ @harness.cache(resource, :ensure, yaml_ensure) if yaml_ensure
+
+ fake_time = Time.utc(2011, 'jan', 3, 12, 24, 0)
+ Time.stubs(:now).returns(fake_time) # So that Puppet::Resource::Status objects will compare properly
+
+ resource.expects(:err).never # make sure no exceptions get swallowed
+ status = @harness.evaluate(resource) # do the thing
+
+ # check that the state of the machine has been properly updated
+ expected_logs = []
+ expected_status_events = []
+ if auditing_mode
+ @harness.cached(resource, :mode).should == (machine_state || :absent)
+ else
+ @harness.cached(resource, :mode).should == yaml_mode
+ end
+ if auditing_ensure
+ @harness.cached(resource, :ensure).should == (machine_state ? :file : :absent)
+ else
+ @harness.cached(resource, :ensure).should == yaml_ensure
+ end
+ if ensure_property == :file
+ file_would_be_there_if_not_noop = true
+ elsif ensure_property == nil
+ file_would_be_there_if_not_noop = machine_state != nil
+ else # ensure_property == :absent
+ file_would_be_there_if_not_noop = false
+ end
+ file_should_be_there = noop_mode ? machine_state != nil : file_would_be_there_if_not_noop
+ File.exists?(test_file).should == file_should_be_there
+ if file_should_be_there
+ if noop_mode
+ expected_file_mode = machine_state
+ else
+ expected_file_mode = mode_property || machine_state
+ end
+ if !expected_file_mode
+ # we didn't specify a mode and the file was created, so mode comes from umode
+ else
+ file_mode = File.stat(test_file).mode & 0777
+ file_mode.should == expected_file_mode.to_i(8)
+ end
+ end
+
+ # Test log output for the "mode" parameter
+ previously_recorded_mode_already_logged = false
+ mode_status_msg = nil
+ if machine_state && file_would_be_there_if_not_noop && mode_property && machine_state != mode_property
+ if noop_mode
+ what_happened = "current_value #{machine_state}, should be #{mode_property} (noop)"
+ expected_status = 'noop'
+ else
+ what_happened = "mode changed '#{machine_state}' to '#{mode_property}'"
+ expected_status = 'success'
+ end
+ if auditing_mode && yaml_mode && yaml_mode != machine_state
+ previously_recorded_mode_already_logged = true
+ mode_status_msg = "#{what_happened} (previously recorded value was #{yaml_mode})"
+ else
+ mode_status_msg = what_happened
+ end
+ expected_logs << "notice: /#{resource}/mode: #{mode_status_msg}"
+ end
+ if @harness.cached(resource, :mode) && @harness.cached(resource, :mode) != yaml_mode
+ if yaml_mode
+ unless previously_recorded_mode_already_logged
+ mode_status_msg = "audit change: previously recorded value #{yaml_mode} has been changed to #{@harness.cached(resource, :mode)}"
+ expected_logs << "notice: /#{resource}/mode: #{mode_status_msg}"
+ expected_status = 'audit'
+ end
+ else
+ expected_logs << "notice: /#{resource}/mode: audit change: newly-recorded value #{@harness.cached(resource, :mode)}"
+ end
+ end
+ if mode_status_msg
+ expected_status_events << Puppet::Transaction::Event.new(
+ :source_description => "/#{resource}/mode", :resource => resource, :file => nil,
+ :line => nil, :tags => %w{file}, :desired_value => mode_property,
+ :historical_value => yaml_mode, :message => mode_status_msg, :name => :mode_changed,
+ :previous_value => machine_state || :absent, :property => :mode, :status => expected_status,
+ :audited => auditing_mode)
+ end
+
+ # Test log output for the "ensure" parameter
+ previously_recorded_ensure_already_logged = false
+ ensure_status_msg = nil
+ if file_would_be_there_if_not_noop != (machine_state != nil)
+ if noop_mode
+ what_happened = "current_value #{machine_state ? 'file' : 'absent'}, should be #{file_would_be_there_if_not_noop ? 'file' : 'absent'} (noop)"
+ expected_status = 'noop'
+ else
+ what_happened = file_would_be_there_if_not_noop ? 'created' : 'removed'
+ expected_status = 'success'
+ end
+ if auditing_ensure && yaml_ensure && yaml_ensure != (machine_state ? :file : :absent)
+ previously_recorded_ensure_already_logged = true
+ ensure_status_msg = "#{what_happened} (previously recorded value was #{yaml_ensure})"
+ else
+ ensure_status_msg = "#{what_happened}"
+ end
+ expected_logs << "notice: /#{resource}/ensure: #{ensure_status_msg}"
+ end
+ if @harness.cached(resource, :ensure) && @harness.cached(resource, :ensure) != yaml_ensure
+ if yaml_ensure
+ unless previously_recorded_ensure_already_logged
+ ensure_status_msg = "audit change: previously recorded value #{yaml_ensure} has been changed to #{@harness.cached(resource, :ensure)}"
+ expected_logs << "notice: /#{resource}/ensure: #{ensure_status_msg}"
+ expected_status = 'audit'
+ end
+ else
+ expected_logs << "notice: /#{resource}/ensure: audit change: newly-recorded value #{@harness.cached(resource, :ensure)}"
+ end
+ end
+ if ensure_status_msg
+ if ensure_property == :file
+ ensure_event_name = :file_created
+ elsif ensure_property == nil
+ ensure_event_name = :file_changed
+ else # ensure_property == :absent
+ ensure_event_name = :file_removed
+ end
+ expected_status_events << Puppet::Transaction::Event.new(
+ :source_description => "/#{resource}/ensure", :resource => resource, :file => nil,
+ :line => nil, :tags => %w{file}, :desired_value => ensure_property,
+ :historical_value => yaml_ensure, :message => ensure_status_msg, :name => ensure_event_name,
+ :previous_value => machine_state ? :file : :absent, :property => :ensure,
+ :status => expected_status, :audited => auditing_ensure)
+ end
+
+ # Actually check the logs.
+ @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ expected_logs
+
+ # All the log messages should show up as events except the "newly-recorded" ones.
+ expected_event_logs = @logs.reject {|l| l.message =~ /newly-recorded/ }
+ status.events.map {|e| e.message}.should =~ expected_event_logs.map {|l| l.message }
+ events_to_hash(status.events).should =~ events_to_hash(expected_status_events)
+
+ # Check change count - this is the number of changes that actually occurred.
+ expected_change_count = 0
+ if (machine_state != nil) != file_should_be_there
+ expected_change_count = 1
+ elsif machine_state != nil
+ if expected_file_mode != machine_state
+ expected_change_count = 1
+ end
+ end
+ status.change_count.should == expected_change_count
+
+ # Check out of sync count - this is the number
+ # of changes that would have occurred in
+ # non-noop mode.
+ expected_out_of_sync_count = 0
+ if (machine_state != nil) != file_would_be_there_if_not_noop
+ expected_out_of_sync_count = 1
+ elsif machine_state != nil
+ if mode_property != nil && mode_property != machine_state
+ expected_out_of_sync_count = 1
+ end
+ end
+ if !noop_mode
+ expected_out_of_sync_count.should == expected_change_count
+ end
+ status.out_of_sync_count.should == expected_out_of_sync_count
+
+ # Check legacy summary fields
+ status.changed.should == (expected_change_count != 0)
+ status.out_of_sync.should == (expected_out_of_sync_count != 0)
+
+ # Check the :synced field on state.yml
+ synced_should_be_set = !noop_mode && status.changed
+ (@harness.cached(resource, :synced) != nil).should == synced_should_be_set
+ end; end
+ end
+ end
+ end
+ end
+ end; end
+ end
+ end; end
+ end; end
+
+ it "should not apply changes if allow_changes?() returns false" do
+ test_file = tmpfile('foo')
+ resource = Puppet::Type.type(:file).new :path => test_file, :backup => false, :ensure => :file
+ resource.expects(:err).never # make sure no exceptions get swallowed
+ @harness.expects(:allow_changes?).with(resource).returns false
+ status = @harness.evaluate(resource)
+ File.exists?(test_file).should == false
end
end
describe "when determining whether the resource can be changed" do
before do
@resource.stubs(:purging?).returns true
@resource.stubs(:deleting?).returns true
end
it "should be true if the resource is not being purged" do
@resource.expects(:purging?).returns false
@harness.should be_allow_changes(@resource)
end
it "should be true if the resource is not being deleted" do
@resource.expects(:deleting?).returns false
@harness.should be_allow_changes(@resource)
end
it "should be true if the resource has no dependents" do
@harness.relationship_graph.expects(:dependents).with(@resource).returns []
@harness.should be_allow_changes(@resource)
end
it "should be true if all dependents are being deleted" do
dep = stub 'dependent', :deleting? => true
@harness.relationship_graph.expects(:dependents).with(@resource).returns [dep]
@resource.expects(:purging?).returns true
@harness.should be_allow_changes(@resource)
end
it "should be false if the resource's dependents are not being deleted" do
dep = stub 'dependent', :deleting? => false, :ref => "myres"
@resource.expects(:warning)
@harness.relationship_graph.expects(:dependents).with(@resource).returns [dep]
@harness.should_not be_allow_changes(@resource)
end
end
describe "when finding the schedule" do
before do
@catalog = Puppet::Resource::Catalog.new
@resource.catalog = @catalog
end
it "should warn and return nil if the resource has no catalog" do
@resource.catalog = nil
@resource.expects(:warning)
@harness.schedule(@resource).should be_nil
end
it "should return nil if the resource specifies no schedule" do
@harness.schedule(@resource).should be_nil
end
it "should fail if the named schedule cannot be found" do
@resource[:schedule] = "whatever"
@resource.expects(:fail)
@harness.schedule(@resource)
end
it "should return the named schedule if it exists" do
sched = Puppet::Type.type(:schedule).new(:name => "sched")
@catalog.add_resource(sched)
@resource[:schedule] = "sched"
@harness.schedule(@resource).to_s.should == sched.to_s
end
end
describe "when determining if a resource is scheduled" do
before do
@catalog = Puppet::Resource::Catalog.new
@resource.catalog = @catalog
@status = Puppet::Resource::Status.new(@resource)
end
it "should return true if 'ignoreschedules' is set" do
Puppet[:ignoreschedules] = true
@resource[:schedule] = "meh"
@harness.should be_scheduled(@status, @resource)
end
it "should return true if the resource has no schedule set" do
@harness.should be_scheduled(@status, @resource)
end
it "should return the result of matching the schedule with the cached 'checked' time if a schedule is set" do
t = Time.now
@harness.expects(:cached).with(@resource, :checked).returns(t)
sched = Puppet::Type.type(:schedule).new(:name => "sched")
@catalog.add_resource(sched)
@resource[:schedule] = "sched"
sched.expects(:match?).with(t.to_i).returns "feh"
@harness.scheduled?(@status, @resource).should == "feh"
end
end
it "should be able to cache data in the Storage module" do
data = {}
Puppet::Util::Storage.expects(:cache).with(@resource).returns data
@harness.cache(@resource, :foo, "something")
data[:foo].should == "something"
end
it "should be able to retrieve data from the cache" do
data = {:foo => "other"}
Puppet::Util::Storage.expects(:cache).with(@resource).returns data
@harness.cached(@resource, :foo).should == "other"
end
end
diff --git a/spec/unit/transaction_spec.rb b/spec/unit/transaction_spec.rb
index 2df4404be..862413a31 100755
--- a/spec/unit/transaction_spec.rb
+++ b/spec/unit/transaction_spec.rb
@@ -1,452 +1,447 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet/transaction'
def without_warnings
flag = $VERBOSE
$VERBOSE = nil
yield
$VERBOSE = flag
end
describe Puppet::Transaction do
before do
@basepath = Puppet.features.posix? ? "/what/ever" : "C:/tmp"
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
end
it "should delegate its event list to the event manager" do
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@transaction.event_manager.expects(:events).returns %w{my events}
@transaction.events.should == %w{my events}
end
it "should delegate adding times to its report" do
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@transaction.report.expects(:add_times).with(:foo, 10)
@transaction.report.expects(:add_times).with(:bar, 20)
@transaction.add_times :foo => 10, :bar => 20
end
it "should be able to accept resource status instances" do
resource = Puppet::Type.type(:notify).new :title => "foobar"
status = Puppet::Resource::Status.new(resource)
@transaction.add_resource_status(status)
@transaction.resource_status(resource).should equal(status)
end
it "should be able to look resource status up by resource reference" do
resource = Puppet::Type.type(:notify).new :title => "foobar"
status = Puppet::Resource::Status.new(resource)
@transaction.add_resource_status(status)
@transaction.resource_status(resource.to_s).should equal(status)
end
# This will basically only ever be used during testing.
it "should automatically create resource statuses if asked for a non-existent status" do
resource = Puppet::Type.type(:notify).new :title => "foobar"
@transaction.resource_status(resource).should be_instance_of(Puppet::Resource::Status)
end
it "should add provided resource statuses to its report" do
resource = Puppet::Type.type(:notify).new :title => "foobar"
status = Puppet::Resource::Status.new(resource)
@transaction.add_resource_status(status)
@transaction.report.resource_statuses[resource.to_s].should equal(status)
end
- it "should calculate metrics on and report the report when asked to generate a report" do
- @transaction.report.expects(:calculate_metrics)
- @transaction.generate_report.should equal(@transaction.report)
- end
-
it "should consider a resource to be failed if a status instance exists for that resource and indicates it is failed" do
resource = Puppet::Type.type(:notify).new :name => "yayness"
status = Puppet::Resource::Status.new(resource)
status.failed = "some message"
@transaction.add_resource_status(status)
@transaction.should be_failed(resource)
end
it "should not consider a resource to be failed if a status instance exists for that resource but indicates it is not failed" do
resource = Puppet::Type.type(:notify).new :name => "yayness"
status = Puppet::Resource::Status.new(resource)
@transaction.add_resource_status(status)
@transaction.should_not be_failed(resource)
end
it "should consider there to be failed resources if any statuses are marked failed" do
resource = Puppet::Type.type(:notify).new :name => "yayness"
status = Puppet::Resource::Status.new(resource)
status.failed = "some message"
@transaction.add_resource_status(status)
@transaction.should be_any_failed
end
it "should not consider there to be failed resources if no statuses are marked failed" do
resource = Puppet::Type.type(:notify).new :name => "yayness"
status = Puppet::Resource::Status.new(resource)
@transaction.add_resource_status(status)
@transaction.should_not be_any_failed
end
it "should be possible to replace the report object" do
- report = Puppet::Transaction::Report.new
+ report = Puppet::Transaction::Report.new("apply")
@transaction.report = report
@transaction.report.should == report
end
it "should consider a resource to have failed dependencies if any of its dependencies are failed"
describe "when initializing" do
it "should create an event manager" do
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@transaction.event_manager.should be_instance_of(Puppet::Transaction::EventManager)
@transaction.event_manager.transaction.should equal(@transaction)
end
it "should create a resource harness" do
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@transaction.resource_harness.should be_instance_of(Puppet::Transaction::ResourceHarness)
@transaction.resource_harness.transaction.should equal(@transaction)
end
end
describe "when evaluating a resource" do
before do
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@transaction.stubs(:eval_children_and_apply_resource)
@transaction.stubs(:skip?).returns false
@resource = Puppet::Type.type(:file).new :path => @basepath
end
it "should check whether the resource should be skipped" do
@transaction.expects(:skip?).with(@resource).returns false
@transaction.eval_resource(@resource)
end
it "should eval and apply children" do
@transaction.expects(:eval_children_and_apply_resource).with(@resource, nil)
@transaction.eval_resource(@resource)
end
it "should process events" do
@transaction.event_manager.expects(:process_events).with(@resource)
@transaction.eval_resource(@resource)
end
describe "and the resource should be skipped" do
before do
@transaction.expects(:skip?).with(@resource).returns true
end
it "should mark the resource's status as skipped" do
@transaction.eval_resource(@resource)
@transaction.resource_status(@resource).should be_skipped
end
end
end
describe "when applying a resource" do
before do
@resource = Puppet::Type.type(:file).new :path => @basepath
@status = Puppet::Resource::Status.new(@resource)
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@transaction.event_manager.stubs(:queue_events)
@transaction.resource_harness.stubs(:evaluate).returns(@status)
end
it "should use its resource harness to apply the resource" do
@transaction.resource_harness.expects(:evaluate).with(@resource)
@transaction.apply(@resource)
end
it "should add the resulting resource status to its status list" do
@transaction.apply(@resource)
@transaction.resource_status(@resource).should be_instance_of(Puppet::Resource::Status)
end
it "should queue any events added to the resource status" do
@status.expects(:events).returns %w{a b}
@transaction.event_manager.expects(:queue_events).with(@resource, ["a", "b"])
@transaction.apply(@resource)
end
it "should log and skip any resources that cannot be applied" do
@transaction.resource_harness.expects(:evaluate).raises ArgumentError
@resource.expects(:err)
@transaction.apply(@resource)
@transaction.report.resource_statuses[@resource.to_s].should be_nil
end
end
describe "when generating resources" do
it "should call 'generate' on all created resources" do
first = Puppet::Type.type(:notify).new(:name => "first")
second = Puppet::Type.type(:notify).new(:name => "second")
third = Puppet::Type.type(:notify).new(:name => "third")
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
first.expects(:generate).returns [second]
second.expects(:generate).returns [third]
third.expects(:generate)
@transaction.generate_additional_resources(first, :generate)
end
it "should finish all resources" do
generator = stub 'generator', :depthfirst? => true, :tags => []
resource = stub 'resource', :tag => nil
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
generator.expects(:generate).returns [resource]
@catalog.expects(:add_resource).yields(resource)
resource.expects(:finish)
@transaction.generate_additional_resources(generator, :generate)
end
it "should skip generated resources that conflict with existing resources" do
generator = mock 'generator', :tags => []
resource = stub 'resource', :tag => nil
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
generator.expects(:generate).returns [resource]
@catalog.expects(:add_resource).raises(Puppet::Resource::Catalog::DuplicateResourceError.new("foo"))
resource.expects(:finish).never
resource.expects(:info) # log that it's skipped
@transaction.generate_additional_resources(generator, :generate).should be_empty
end
it "should copy all tags to the newly generated resources" do
child = stub 'child'
generator = stub 'resource', :tags => ["one", "two"]
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
generator.stubs(:generate).returns [child]
@catalog.stubs(:add_resource)
child.expects(:tag).with("one", "two")
@transaction.generate_additional_resources(generator, :generate)
end
end
describe "when skipping a resource" do
before :each do
@resource = Puppet::Type.type(:notify).new :name => "foo"
@catalog = Puppet::Resource::Catalog.new
@resource.catalog = @catalog
@transaction = Puppet::Transaction.new(@catalog)
end
it "should skip resource with missing tags" do
@transaction.stubs(:missing_tags?).returns(true)
@transaction.should be_skip(@resource)
end
it "should skip unscheduled resources" do
@transaction.stubs(:scheduled?).returns(false)
@transaction.should be_skip(@resource)
end
it "should skip resources with failed dependencies" do
@transaction.stubs(:failed_dependencies?).returns(true)
@transaction.should be_skip(@resource)
end
it "should skip virtual resource" do
@resource.stubs(:virtual?).returns true
@transaction.should be_skip(@resource)
end
end
describe "when determining if tags are missing" do
before :each do
@resource = Puppet::Type.type(:notify).new :name => "foo"
@catalog = Puppet::Resource::Catalog.new
@resource.catalog = @catalog
@transaction = Puppet::Transaction.new(@catalog)
@transaction.stubs(:ignore_tags?).returns false
end
it "should not be missing tags if tags are being ignored" do
@transaction.expects(:ignore_tags?).returns true
@resource.expects(:tagged?).never
@transaction.should_not be_missing_tags(@resource)
end
it "should not be missing tags if the transaction tags are empty" do
@transaction.tags = []
@resource.expects(:tagged?).never
@transaction.should_not be_missing_tags(@resource)
end
it "should otherwise let the resource determine if it is missing tags" do
tags = ['one', 'two']
@transaction.tags = tags
@resource.expects(:tagged?).with(*tags).returns(false)
@transaction.should be_missing_tags(@resource)
end
end
describe "when determining if a resource should be scheduled" do
before :each do
@resource = Puppet::Type.type(:notify).new :name => "foo"
@catalog = Puppet::Resource::Catalog.new
@resource.catalog = @catalog
@transaction = Puppet::Transaction.new(@catalog)
end
it "should always schedule resources if 'ignoreschedules' is set" do
@transaction.ignoreschedules = true
@transaction.resource_harness.expects(:scheduled?).never
@transaction.should be_scheduled(@resource)
end
it "should let the resource harness determine whether the resource should be scheduled" do
@transaction.resource_harness.expects(:scheduled?).with(@transaction.resource_status(@resource), @resource).returns "feh"
@transaction.scheduled?(@resource).should == "feh"
end
end
describe "when prefetching" do
it "should match resources by name, not title" do
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
# Have both a title and name
resource = Puppet::Type.type(:sshkey).create :title => "foo", :name => "bar", :type => :dsa, :key => "eh"
@catalog.add_resource resource
resource.provider.class.expects(:prefetch).with("bar" => resource)
@transaction.prefetch
end
end
it "should return all resources for which the resource status indicates the resource has changed when determinig changed resources" do
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
names = []
2.times do |i|
name = File.join(@basepath, "file#{i}")
resource = Puppet::Type.type(:file).new :path => name
names << resource.to_s
@catalog.add_resource resource
@transaction.add_resource_status Puppet::Resource::Status.new(resource)
end
@transaction.resource_status(names[0]).changed = true
@transaction.changed?.should == [@catalog.resource(names[0])]
end
describe 'when checking application run state' do
before do
without_warnings { Puppet::Application = Class.new(Puppet::Application) }
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
end
after do
without_warnings { Puppet::Application = Puppet::Application.superclass }
end
it 'should return true for :stop_processing? if Puppet::Application.stop_requested? is true' do
Puppet::Application.stubs(:stop_requested?).returns(true)
@transaction.stop_processing?.should be_true
end
it 'should return false for :stop_processing? if Puppet::Application.stop_requested? is false' do
Puppet::Application.stubs(:stop_requested?).returns(false)
@transaction.stop_processing?.should be_false
end
describe 'within an evaluate call' do
before do
@resource = stub 'resource', :ref => 'some_ref'
@catalog.add_resource @resource
@transaction.stubs(:prepare)
@transaction.sorted_resources = [@resource]
end
it 'should stop processing if :stop_processing? is true' do
@transaction.expects(:stop_processing?).returns(true)
@transaction.expects(:eval_resource).never
@transaction.evaluate
end
it 'should continue processing if :stop_processing? is false' do
@transaction.expects(:stop_processing?).returns(false)
@transaction.expects(:eval_resource).returns(nil)
@transaction.evaluate
end
end
end
end
describe Puppet::Transaction, " when determining tags" do
before do
@config = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@config)
end
it "should default to the tags specified in the :tags setting" do
Puppet.expects(:[]).with(:tags).returns("one")
@transaction.tags.should == %w{one}
end
it "should split tags based on ','" do
Puppet.expects(:[]).with(:tags).returns("one,two")
@transaction.tags.should == %w{one two}
end
it "should use any tags set after creation" do
Puppet.expects(:[]).with(:tags).never
@transaction.tags = %w{one two}
@transaction.tags.should == %w{one two}
end
it "should always convert assigned tags to an array" do
@transaction.tags = "one::two"
@transaction.tags.should == %w{one::two}
end
it "should accept a comma-delimited string" do
@transaction.tags = "one, two"
@transaction.tags.should == %w{one two}
end
it "should accept an empty string" do
@transaction.tags = ""
@transaction.tags.should == []
end
end
diff --git a/spec/unit/type/augeas_spec.rb b/spec/unit/type/augeas_spec.rb
index e426fbeed..d2e40f0f6 100644
--- a/spec/unit/type/augeas_spec.rb
+++ b/spec/unit/type/augeas_spec.rb
@@ -1,122 +1,120 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
augeas = Puppet::Type.type(:augeas)
describe augeas do
- describe "when augeas is present" do
- confine "Augeas is unavailable" => Puppet.features.augeas?
-
+ describe "when augeas is present", :if => Puppet.features.augeas? do
it "should have a default provider inheriting from Puppet::Provider" do
augeas.defaultprovider.ancestors.should be_include(Puppet::Provider)
end
it "should have a valid provider" do
augeas.new(:name => "foo").provider.class.ancestors.should be_include(Puppet::Provider)
end
end
describe "basic structure" do
it "should be able to create a instance" do
provider_class = Puppet::Type::Augeas.provider(Puppet::Type::Augeas.providers[0])
Puppet::Type::Augeas.expects(:defaultprovider).returns provider_class
augeas.new(:name => "bar").should_not be_nil
end
it "should have an parse_commands feature" do
augeas.provider_feature(:parse_commands).should_not be_nil
end
it "should have an need_to_run? feature" do
augeas.provider_feature(:need_to_run?).should_not be_nil
end
it "should have an execute_changes feature" do
augeas.provider_feature(:execute_changes).should_not be_nil
end
properties = [:returns]
params = [:name, :context, :onlyif, :changes, :root, :load_path, :type_check]
properties.each do |property|
it "should have a #{property} property" do
augeas.attrclass(property).ancestors.should be_include(Puppet::Property)
end
it "should have documentation for its #{property} property" do
augeas.attrclass(property).doc.should be_instance_of(String)
end
end
params.each do |param|
it "should have a #{param} parameter" do
augeas.attrclass(param).ancestors.should be_include(Puppet::Parameter)
end
it "should have documentation for its #{param} parameter" do
augeas.attrclass(param).doc.should be_instance_of(String)
end
end
end
describe "default values" do
before do
provider_class = augeas.provider(augeas.providers[0])
augeas.expects(:defaultprovider).returns provider_class
end
it "should be blank for context" do
augeas.new(:name => :context)[:context].should == ""
end
it "should be blank for onlyif" do
augeas.new(:name => :onlyif)[:onlyif].should == ""
end
it "should be blank for load_path" do
augeas.new(:name => :load_path)[:load_path].should == ""
end
it "should be / for root" do
augeas.new(:name => :root)[:root].should == "/"
end
it "should be false for type_check" do
augeas.new(:name => :type_check)[:type_check].should == :false
end
end
describe "provider interaction" do
it "should return 0 if it does not need to run" do
provider = stub("provider", :need_to_run? => false)
resource = stub('resource', :resource => nil, :provider => provider, :line => nil, :file => nil)
changes = augeas.attrclass(:returns).new(:resource => resource)
changes.retrieve.should == 0
end
it "should return :need_to_run if it needs to run" do
provider = stub("provider", :need_to_run? => true)
resource = stub('resource', :resource => nil, :provider => provider, :line => nil, :file => nil)
changes = augeas.attrclass(:returns).new(:resource => resource)
changes.retrieve.should == :need_to_run
end
end
describe "loading specific files" do
it "should require lens when incl is used" do
lambda { augeas.new(:name => :no_lens, :incl => "/etc/hosts")}.should raise_error(Puppet::Error)
end
it "should require incl when lens is used" do
lambda { augeas.new(:name => :no_incl, :lens => "Hosts.lns") }.should raise_error(Puppet::Error)
end
it "should set the context when a specific file is used" do
fake_provider = stub_everything "fake_provider"
augeas.stubs(:defaultprovider).returns fake_provider
augeas.new(:name => :no_incl, :lens => "Hosts.lns", :incl => "/etc/hosts")[:context].should == "/files/etc/hosts"
end
end
end
diff --git a/spec/unit/type/file/ctime.rb b/spec/unit/type/file/ctime.rb
new file mode 100644
index 000000000..6145cbfdc
--- /dev/null
+++ b/spec/unit/type/file/ctime.rb
@@ -0,0 +1,35 @@
+#!/usr/bin/env ruby
+
+Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
+
+describe Puppet::Type.type(:file).attrclass(:ctime) do
+ require 'puppet_spec/files'
+ include PuppetSpec::Files
+
+ before do
+ @filename = tmpfile('ctime')
+ @resource = Puppet::Type.type(:file).new({:name => @filename})
+ end
+
+ it "should be able to audit the file's ctime" do
+ File.open(@filename, "w"){ }
+
+ @resource[:audit] = [:ctime]
+
+ # this .to_resource audit behavior is magical :-(
+ @resource.to_resource[:ctime].should == File.stat(@filename).ctime
+ end
+
+ it "should return absent if auditing an absent file" do
+ @resource[:audit] = [:ctime]
+
+ @resource.to_resource[:ctime].should == :absent
+ end
+
+ it "should prevent the user from trying to set the ctime" do
+ lambda {
+ @resource[:ctime] = Time.now.to_s
+ }.should raise_error(Puppet::Error, /ctime is read-only/)
+ end
+
+end
diff --git a/spec/unit/type/file/mtime.rb b/spec/unit/type/file/mtime.rb
new file mode 100644
index 000000000..043156ceb
--- /dev/null
+++ b/spec/unit/type/file/mtime.rb
@@ -0,0 +1,35 @@
+#!/usr/bin/env ruby
+
+Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
+
+describe Puppet::Type.type(:file).attrclass(:mtime) do
+ require 'puppet_spec/files'
+ include PuppetSpec::Files
+
+ before do
+ @filename = tmpfile('mtime')
+ @resource = Puppet::Type.type(:file).new({:name => @filename})
+ end
+
+ it "should be able to audit the file's mtime" do
+ File.open(@filename, "w"){ }
+
+ @resource[:audit] = [:mtime]
+
+ # this .to_resource audit behavior is magical :-(
+ @resource.to_resource[:mtime].should == File.stat(@filename).mtime
+ end
+
+ it "should return absent if auditing an absent file" do
+ @resource[:audit] = [:mtime]
+
+ @resource.to_resource[:mtime].should == :absent
+ end
+
+ it "should prevent the user from trying to set the mtime" do
+ lambda {
+ @resource[:mtime] = Time.now.to_s
+ }.should raise_error(Puppet::Error, /mtime is read-only/)
+ end
+
+end
diff --git a/spec/unit/type/file/type.rb b/spec/unit/type/file/type.rb
new file mode 100644
index 000000000..e46f0e0b0
--- /dev/null
+++ b/spec/unit/type/file/type.rb
@@ -0,0 +1,20 @@
+#!/usr/bin/env ruby
+
+Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
+
+describe Puppet::Type.type(:file).attrclass(:type) do
+ require 'puppet_spec/files'
+ include PuppetSpec::Files
+
+ before do
+ @filename = tmpfile('type')
+ @resource = Puppet::Type.type(:file).new({:name => @filename})
+ end
+
+ it "should prevent the user from trying to set the type" do
+ lambda {
+ @resource[:type] = "fifo"
+ }.should raise_error(Puppet::Error, /type is read-only/)
+ end
+
+end
diff --git a/spec/unit/type/file_spec.rb b/spec/unit/type/file_spec.rb
index 4fcad07e1..22921d85a 100755
--- a/spec/unit/type/file_spec.rb
+++ b/spec/unit/type/file_spec.rb
@@ -1,1070 +1,1068 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
describe Puppet::Type.type(:file) do
before do
Puppet.settings.stubs(:use)
@real_posix = Puppet.features.posix?
Puppet.features.stubs("posix?").returns(true)
@path = Tempfile.new("puppetspec")
pathname = @path.path
@path.close!()
@path = pathname
@file = Puppet::Type::File.new(:name => @path)
@catalog = Puppet::Resource::Catalog.new
@file.catalog = @catalog
end
describe "when determining if recursion is enabled" do
it "should default to recursion being disabled" do
@file.should_not be_recurse
end
[true, "true", 10, "inf", "remote"].each do |value|
it "should consider #{value} to enable recursion" do
@file[:recurse] = value
@file.must be_recurse
end
end
[false, "false", 0].each do |value|
it "should consider #{value} to disable recursion" do
@file[:recurse] = value
@file.should_not be_recurse
end
end
end
describe "#write" do
it "should propagate failures encountered when renaming the temporary file" do
File.stubs(:open)
File.expects(:rename).raises ArgumentError
file = Puppet::Type::File.new(:name => "/my/file", :backup => "puppet")
file.stubs(:validate_checksum?).returns(false)
property = stub('content_property', :actual_content => "something", :length => "something".length)
file.stubs(:property).with(:content).returns(property)
lambda { file.write(:content) }.should raise_error(Puppet::Error)
end
it "should delegate writing to the content property" do
filehandle = stub_everything 'fh'
File.stubs(:open).yields(filehandle)
File.stubs(:rename)
property = stub('content_property', :actual_content => "something", :length => "something".length)
file = Puppet::Type::File.new(:name => "/my/file", :backup => "puppet")
file.stubs(:validate_checksum?).returns(false)
file.stubs(:property).with(:content).returns(property)
property.expects(:write).with(filehandle)
file.write(:content)
end
describe "when validating the checksum" do
before { @file.stubs(:validate_checksum?).returns(true) }
it "should fail if the checksum parameter and content checksums do not match" do
checksum = stub('checksum_parameter', :sum => 'checksum_b', :sum_file => 'checksum_b')
@file.stubs(:parameter).with(:checksum).returns(checksum)
property = stub('content_property', :actual_content => "something", :length => "something".length, :write => 'checksum_a')
@file.stubs(:property).with(:content).returns(property)
lambda { @file.write :NOTUSED }.should raise_error(Puppet::Error)
end
end
describe "when not validating the checksum" do
before { @file.stubs(:validate_checksum?).returns(false) }
it "should not fail if the checksum property and content checksums do not match" do
checksum = stub('checksum_parameter', :sum => 'checksum_b')
@file.stubs(:parameter).with(:checksum).returns(checksum)
property = stub('content_property', :actual_content => "something", :length => "something".length, :write => 'checksum_a')
@file.stubs(:property).with(:content).returns(property)
lambda { @file.write :NOTUSED }.should_not raise_error(Puppet::Error)
end
end
end
it "should have a method for determining if the file is present" do
@file.must respond_to(:exist?)
end
it "should be considered existent if it can be stat'ed" do
@file.expects(:stat).returns mock('stat')
@file.must be_exist
end
it "should be considered nonexistent if it can not be stat'ed" do
@file.expects(:stat).returns nil
@file.must_not be_exist
end
it "should have a method for determining if the file should be a normal file" do
@file.must respond_to(:should_be_file?)
end
it "should be a file if :ensure is set to :file" do
@file[:ensure] = :file
@file.must be_should_be_file
end
it "should be a file if :ensure is set to :present and the file exists as a normal file" do
@file.stubs(:stat).returns(mock('stat', :ftype => "file"))
@file[:ensure] = :present
@file.must be_should_be_file
end
it "should not be a file if :ensure is set to something other than :file" do
@file[:ensure] = :directory
@file.must_not be_should_be_file
end
it "should not be a file if :ensure is set to :present and the file exists but is not a normal file" do
@file.stubs(:stat).returns(mock('stat', :ftype => "directory"))
@file[:ensure] = :present
@file.must_not be_should_be_file
end
it "should be a file if :ensure is not set and :content is" do
@file[:content] = "foo"
@file.must be_should_be_file
end
it "should be a file if neither :ensure nor :content is set but the file exists as a normal file" do
@file.stubs(:stat).returns(mock("stat", :ftype => "file"))
@file.must be_should_be_file
end
it "should not be a file if neither :ensure nor :content is set but the file exists but not as a normal file" do
@file.stubs(:stat).returns(mock("stat", :ftype => "directory"))
@file.must_not be_should_be_file
end
describe "when using POSIX filenames" do
describe "on POSIX systems" do
before do
Puppet.features.stubs(:posix?).returns(true)
Puppet.features.stubs(:microsoft_windows?).returns(false)
end
it "should autorequire its parent directory" do
file = Puppet::Type::File.new(:path => "/foo/bar")
dir = Puppet::Type::File.new(:path => "/foo")
@catalog.add_resource file
@catalog.add_resource dir
reqs = file.autorequire
reqs[0].source.must == dir
reqs[0].target.must == file
end
it "should not autorequire its parent dir if its parent dir is itself" do
file = Puppet::Type::File.new(:path => "/")
@catalog.add_resource file
file.autorequire.should be_empty
end
it "should remove trailing slashes" do
file = Puppet::Type::File.new(:path => "/foo/bar/baz/")
file[:path].should == "/foo/bar/baz"
end
it "should remove double slashes" do
file = Puppet::Type::File.new(:path => "/foo/bar//baz")
file[:path].should == "/foo/bar/baz"
end
it "should remove trailing double slashes" do
file = Puppet::Type::File.new(:path => "/foo/bar/baz//")
file[:path].should == "/foo/bar/baz"
end
it "should leave a single slash alone" do
file = Puppet::Type::File.new(:path => "/")
file[:path].should == "/"
end
end
describe "on Microsoft Windows systems" do
before do
Puppet.features.stubs(:posix?).returns(false)
Puppet.features.stubs(:microsoft_windows?).returns(true)
end
it "should refuse to work" do
lambda { Puppet::Type::File.new(:path => "/foo/bar") }.should raise_error(Puppet::Error)
end
end
end
- describe "when using Microsoft Windows filenames" do
- confine "Only works on Microsoft Windows" => Puppet.features.microsoft_windows?
+ describe "when using Microsoft Windows filenames", :if => Puppet.features.microsoft_windows? do
describe "on Microsoft Windows systems" do
before do
Puppet.features.stubs(:posix?).returns(false)
Puppet.features.stubs(:microsoft_windows?).returns(true)
end
it "should autorequire its parent directory" do
file = Puppet::Type::File.new(:path => "X:/foo/bar")
dir = Puppet::Type::File.new(:path => "X:/foo")
@catalog.add_resource file
@catalog.add_resource dir
reqs = file.autorequire
reqs[0].source.must == dir
reqs[0].target.must == file
end
it "should not autorequire its parent dir if its parent dir is itself" do
file = Puppet::Type::File.new(:path => "X:/")
@catalog.add_resource file
file.autorequire.should be_empty
end
it "should remove trailing slashes" do
file = Puppet::Type::File.new(:path => "X:/foo/bar/baz/")
file[:path].should == "X:/foo/bar/baz"
end
it "should remove double slashes" do
file = Puppet::Type::File.new(:path => "X:/foo/bar//baz")
file[:path].should == "X:/foo/bar/baz"
end
it "should remove trailing double slashes" do
file = Puppet::Type::File.new(:path => "X:/foo/bar/baz//")
file[:path].should == "X:/foo/bar/baz"
end
it "should leave a drive letter with a slash alone" do
file = Puppet::Type::File.new(:path => "X:/")
file[:path].should == "X:/"
end
it "should add a slash to a drive letter" do
file = Puppet::Type::File.new(:path => "X:")
file[:path].should == "X:/"
end
end
describe "on POSIX systems" do
before do
Puppet.features.stubs(:posix?).returns(true)
Puppet.features.stubs(:microsoft_windows?).returns(false)
end
it "should refuse to work" do
lambda { Puppet::Type::File.new(:path => "X:/foo/bar") }.should raise_error(Puppet::Error)
end
end
end
describe "when using UNC filenames" do
- describe "on Microsoft Windows systems" do
- confine "Only works on Microsoft Windows" => Puppet.features.microsoft_windows?
+ describe "on Microsoft Windows systems", :if => Puppet.features.microsoft_windows? do
before do
Puppet.features.stubs(:posix?).returns(false)
Puppet.features.stubs(:microsoft_windows?).returns(true)
end
it "should autorequire its parent directory" do
file = Puppet::Type::File.new(:path => "//server/foo/bar")
dir = Puppet::Type::File.new(:path => "//server/foo")
@catalog.add_resource file
@catalog.add_resource dir
reqs = file.autorequire
reqs[0].source.must == dir
reqs[0].target.must == file
end
it "should not autorequire its parent dir if its parent dir is itself" do
file = Puppet::Type::File.new(:path => "//server/foo")
@catalog.add_resource file
puts file.autorequire
file.autorequire.should be_empty
end
it "should remove trailing slashes" do
file = Puppet::Type::File.new(:path => "//server/foo/bar/baz/")
file[:path].should == "//server/foo/bar/baz"
end
it "should remove double slashes" do
file = Puppet::Type::File.new(:path => "//server/foo/bar//baz")
file[:path].should == "//server/foo/bar/baz"
end
it "should remove trailing double slashes" do
file = Puppet::Type::File.new(:path => "//server/foo/bar/baz//")
file[:path].should == "//server/foo/bar/baz"
end
it "should remove a trailing slash from a sharename" do
file = Puppet::Type::File.new(:path => "//server/foo/")
file[:path].should == "//server/foo"
end
it "should not modify a sharename" do
file = Puppet::Type::File.new(:path => "//server/foo")
file[:path].should == "//server/foo"
end
end
describe "on POSIX systems" do
before do
Puppet.features.stubs(:posix?).returns(true)
Puppet.features.stubs(:microsoft_windows?).returns(false)
end
it "should refuse to work" do
lambda { Puppet::Type::File.new(:path => "X:/foo/bar") }.should raise_error(Puppet::Error)
end
end
end
describe "when initializing" do
it "should set a desired 'ensure' value if none is set and 'content' is set" do
file = Puppet::Type::File.new(:name => "/my/file", :content => "/foo/bar")
file[:ensure].should == :file
end
it "should set a desired 'ensure' value if none is set and 'target' is set" do
file = Puppet::Type::File.new(:name => "/my/file", :target => "/foo/bar")
file[:ensure].should == :symlink
end
end
describe "when validating attributes" do
%w{path checksum backup recurse recurselimit source replace force ignore links purge sourceselect}.each do |attr|
it "should have a '#{attr}' parameter" do
Puppet::Type.type(:file).attrtype(attr.intern).should == :param
end
end
%w{content target ensure owner group mode type}.each do |attr|
it "should have a '#{attr}' property" do
Puppet::Type.type(:file).attrtype(attr.intern).should == :property
end
end
it "should have its 'path' attribute set as its namevar" do
Puppet::Type.type(:file).key_attributes.should == [:path]
end
end
describe "when managing links" do
require 'puppettest/support/assertions'
include PuppetTest
require 'tempfile'
if @real_posix
describe "on POSIX systems" do
before do
@basedir = tempfile
Dir.mkdir(@basedir)
@file = File.join(@basedir, "file")
@link = File.join(@basedir, "link")
File.open(@file, "w", 0644) { |f| f.puts "yayness"; f.flush }
File.symlink(@file, @link)
@resource = Puppet::Type.type(:file).new(
-
+
:path => @link,
-
+
:mode => "755"
)
@catalog.add_resource @resource
end
after do
remove_tmp_files
end
it "should default to managing the link" do
@catalog.apply
# I convert them to strings so they display correctly if there's an error.
("%o" % (File.stat(@file).mode & 007777)).should == "%o" % 0644
end
it "should be able to follow links" do
@resource[:links] = :follow
@catalog.apply
("%o" % (File.stat(@file).mode & 007777)).should == "%o" % 0755
end
end
else # @real_posix
# should recode tests using expectations instead of using the filesystem
end
describe "on Microsoft Windows systems" do
before do
Puppet.features.stubs(:posix?).returns(false)
Puppet.features.stubs(:microsoft_windows?).returns(true)
end
it "should refuse to work with links"
end
end
it "should be able to retrieve a stat instance for the file it is managing" do
Puppet::Type.type(:file).new(:path => "/foo/bar", :source => "/bar/foo").should respond_to(:stat)
end
describe "when stat'ing its file" do
before do
@resource = Puppet::Type.type(:file).new(:path => "/foo/bar")
@resource[:links] = :manage # so we always use :lstat
end
it "should use :stat if it is following links" do
@resource[:links] = :follow
File.expects(:stat)
@resource.stat
end
it "should use :lstat if is it not following links" do
@resource[:links] = :manage
File.expects(:lstat)
@resource.stat
end
it "should stat the path of the file" do
File.expects(:lstat).with("/foo/bar")
@resource.stat
end
# This only happens in testing.
it "should return nil if the stat does not exist" do
File.expects(:lstat).returns nil
@resource.stat.should be_nil
end
it "should return nil if the file does not exist" do
File.expects(:lstat).raises(Errno::ENOENT)
@resource.stat.should be_nil
end
it "should return nil if the file cannot be stat'ed" do
File.expects(:lstat).raises(Errno::EACCES)
@resource.stat.should be_nil
end
it "should return the stat instance" do
File.expects(:lstat).returns "mystat"
@resource.stat.should == "mystat"
end
it "should cache the stat instance if it has a catalog and is applying" do
stat = mock 'stat'
File.expects(:lstat).returns stat
catalog = Puppet::Resource::Catalog.new
@resource.catalog = catalog
catalog.stubs(:applying?).returns true
@resource.stat.should equal(@resource.stat)
end
end
describe "when flushing" do
it "should flush all properties that respond to :flush" do
@resource = Puppet::Type.type(:file).new(:path => "/foo/bar", :source => "/bar/foo")
@resource.parameter(:source).expects(:flush)
@resource.flush
end
it "should reset its stat reference" do
@resource = Puppet::Type.type(:file).new(:path => "/foo/bar")
File.expects(:lstat).times(2).returns("stat1").then.returns("stat2")
@resource.stat.should == "stat1"
@resource.flush
@resource.stat.should == "stat2"
end
end
it "should have a method for performing recursion" do
@file.must respond_to(:perform_recursion)
end
describe "when executing a recursive search" do
it "should use Metadata to do its recursion" do
Puppet::FileServing::Metadata.expects(:search)
@file.perform_recursion(@file[:path])
end
it "should use the provided path as the key to the search" do
Puppet::FileServing::Metadata.expects(:search).with { |key, options| key == "/foo" }
@file.perform_recursion("/foo")
end
it "should return the results of the metadata search" do
Puppet::FileServing::Metadata.expects(:search).returns "foobar"
@file.perform_recursion(@file[:path]).should == "foobar"
end
it "should pass its recursion value to the search" do
@file[:recurse] = true
Puppet::FileServing::Metadata.expects(:search).with { |key, options| options[:recurse] == true }
@file.perform_recursion(@file[:path])
end
it "should pass true if recursion is remote" do
@file[:recurse] = :remote
Puppet::FileServing::Metadata.expects(:search).with { |key, options| options[:recurse] == true }
@file.perform_recursion(@file[:path])
end
it "should pass its recursion limit value to the search" do
@file[:recurselimit] = 10
Puppet::FileServing::Metadata.expects(:search).with { |key, options| options[:recurselimit] == 10 }
@file.perform_recursion(@file[:path])
end
it "should configure the search to ignore or manage links" do
@file[:links] = :manage
Puppet::FileServing::Metadata.expects(:search).with { |key, options| options[:links] == :manage }
@file.perform_recursion(@file[:path])
end
it "should pass its 'ignore' setting to the search if it has one" do
@file[:ignore] = %w{.svn CVS}
Puppet::FileServing::Metadata.expects(:search).with { |key, options| options[:ignore] == %w{.svn CVS} }
@file.perform_recursion(@file[:path])
end
end
it "should have a method for performing local recursion" do
@file.must respond_to(:recurse_local)
end
describe "when doing local recursion" do
before do
@metadata = stub 'metadata', :relative_path => "my/file"
end
it "should pass its path to the :perform_recursion method" do
@file.expects(:perform_recursion).with(@file[:path]).returns [@metadata]
@file.stubs(:newchild)
@file.recurse_local
end
it "should return an empty hash if the recursion returns nothing" do
@file.expects(:perform_recursion).returns nil
@file.recurse_local.should == {}
end
it "should create a new child resource with each generated metadata instance's relative path" do
@file.expects(:perform_recursion).returns [@metadata]
@file.expects(:newchild).with(@metadata.relative_path).returns "fiebar"
@file.recurse_local
end
it "should not create a new child resource for the '.' directory" do
@metadata.stubs(:relative_path).returns "."
@file.expects(:perform_recursion).returns [@metadata]
@file.expects(:newchild).never
@file.recurse_local
end
it "should return a hash of the created resources with the relative paths as the hash keys" do
@file.expects(:perform_recursion).returns [@metadata]
@file.expects(:newchild).with("my/file").returns "fiebar"
@file.recurse_local.should == {"my/file" => "fiebar"}
end
it "should set checksum_type to none if this file checksum is none" do
@file[:checksum] = :none
Puppet::FileServing::Metadata.expects(:search).with { |path,params| params[:checksum_type] == :none }.returns [@metadata]
@file.expects(:newchild).with("my/file").returns "fiebar"
@file.recurse_local
end
end
it "should have a method for performing link recursion" do
@file.must respond_to(:recurse_link)
end
describe "when doing link recursion" do
before do
@first = stub 'first', :relative_path => "first", :full_path => "/my/first", :ftype => "directory"
@second = stub 'second', :relative_path => "second", :full_path => "/my/second", :ftype => "file"
@resource = stub 'file', :[]= => nil
end
it "should pass its target to the :perform_recursion method" do
@file[:target] = "mylinks"
@file.expects(:perform_recursion).with("mylinks").returns [@first]
@file.stubs(:newchild).returns @resource
@file.recurse_link({})
end
it "should ignore the recursively-found '.' file and configure the top-level file to create a directory" do
@first.stubs(:relative_path).returns "."
@file[:target] = "mylinks"
@file.expects(:perform_recursion).with("mylinks").returns [@first]
@file.stubs(:newchild).never
@file.expects(:[]=).with(:ensure, :directory)
@file.recurse_link({})
end
it "should create a new child resource for each generated metadata instance's relative path that doesn't already exist in the children hash" do
@file.expects(:perform_recursion).returns [@first, @second]
@file.expects(:newchild).with(@first.relative_path).returns @resource
@file.recurse_link("second" => @resource)
end
it "should not create a new child resource for paths that already exist in the children hash" do
@file.expects(:perform_recursion).returns [@first]
@file.expects(:newchild).never
@file.recurse_link("first" => @resource)
end
it "should set the target to the full path of discovered file and set :ensure to :link if the file is not a directory" do
file = stub 'file'
file.expects(:[]=).with(:target, "/my/second")
file.expects(:[]=).with(:ensure, :link)
@file.stubs(:perform_recursion).returns [@first, @second]
@file.recurse_link("first" => @resource, "second" => file)
end
it "should :ensure to :directory if the file is a directory" do
file = stub 'file'
file.expects(:[]=).with(:ensure, :directory)
@file.stubs(:perform_recursion).returns [@first, @second]
@file.recurse_link("first" => file, "second" => @resource)
end
it "should return a hash with both created and existing resources with the relative paths as the hash keys" do
file = stub 'file', :[]= => nil
@file.expects(:perform_recursion).returns [@first, @second]
@file.stubs(:newchild).returns file
@file.recurse_link("second" => @resource).should == {"second" => @resource, "first" => file}
end
end
it "should have a method for performing remote recursion" do
@file.must respond_to(:recurse_remote)
end
describe "when doing remote recursion" do
before do
@file[:source] = "puppet://foo/bar"
@first = Puppet::FileServing::Metadata.new("/my", :relative_path => "first")
@second = Puppet::FileServing::Metadata.new("/my", :relative_path => "second")
@first.stubs(:ftype).returns "directory"
@second.stubs(:ftype).returns "directory"
@parameter = stub 'property', :metadata= => nil
@resource = stub 'file', :[]= => nil, :parameter => @parameter
end
it "should pass its source to the :perform_recursion method" do
data = Puppet::FileServing::Metadata.new("/whatever", :relative_path => "foobar")
@file.expects(:perform_recursion).with("puppet://foo/bar").returns [data]
@file.stubs(:newchild).returns @resource
@file.recurse_remote({})
end
it "should not recurse when the remote file is not a directory" do
data = Puppet::FileServing::Metadata.new("/whatever", :relative_path => ".")
data.stubs(:ftype).returns "file"
@file.expects(:perform_recursion).with("puppet://foo/bar").returns [data]
@file.expects(:newchild).never
@file.recurse_remote({})
end
it "should set the source of each returned file to the searched-for URI plus the found relative path" do
@first.expects(:source=).with File.join("puppet://foo/bar", @first.relative_path)
@file.expects(:perform_recursion).returns [@first]
@file.stubs(:newchild).returns @resource
@file.recurse_remote({})
end
it "should create a new resource for any relative file paths that do not already have a resource" do
@file.stubs(:perform_recursion).returns [@first]
@file.expects(:newchild).with("first").returns @resource
@file.recurse_remote({}).should == {"first" => @resource}
end
it "should not create a new resource for any relative file paths that do already have a resource" do
@file.stubs(:perform_recursion).returns [@first]
@file.expects(:newchild).never
@file.recurse_remote("first" => @resource)
end
it "should set the source of each resource to the source of the metadata" do
@file.stubs(:perform_recursion).returns [@first]
@resource.stubs(:[]=)
@resource.expects(:[]=).with(:source, File.join("puppet://foo/bar", @first.relative_path))
@file.recurse_remote("first" => @resource)
end
# LAK:FIXME This is a bug, but I can't think of a fix for it. Fortunately it's already
# filed, and when it's fixed, we'll just fix the whole flow.
it "should set the checksum type to :md5 if the remote file is a file" do
@first.stubs(:ftype).returns "file"
@file.stubs(:perform_recursion).returns [@first]
@resource.stubs(:[]=)
@resource.expects(:[]=).with(:checksum, :md5)
@file.recurse_remote("first" => @resource)
end
it "should store the metadata in the source property for each resource so the source does not have to requery the metadata" do
@file.stubs(:perform_recursion).returns [@first]
@resource.expects(:parameter).with(:source).returns @parameter
@parameter.expects(:metadata=).with(@first)
@file.recurse_remote("first" => @resource)
end
it "should not create a new resource for the '.' file" do
@first.stubs(:relative_path).returns "."
@file.stubs(:perform_recursion).returns [@first]
@file.expects(:newchild).never
@file.recurse_remote({})
end
it "should store the metadata in the main file's source property if the relative path is '.'" do
@first.stubs(:relative_path).returns "."
@file.stubs(:perform_recursion).returns [@first]
@file.parameter(:source).expects(:metadata=).with @first
@file.recurse_remote("first" => @resource)
end
describe "and multiple sources are provided" do
describe "and :sourceselect is set to :first" do
it "should create file instances for the results for the first source to return any values" do
data = Puppet::FileServing::Metadata.new("/whatever", :relative_path => "foobar")
@file[:source] = %w{/one /two /three /four}
@file.expects(:perform_recursion).with("/one").returns nil
@file.expects(:perform_recursion).with("/two").returns []
@file.expects(:perform_recursion).with("/three").returns [data]
@file.expects(:perform_recursion).with("/four").never
@file.expects(:newchild).with("foobar").returns @resource
@file.recurse_remote({})
end
end
describe "and :sourceselect is set to :all" do
before do
@file[:sourceselect] = :all
end
it "should return every found file that is not in a previous source" do
klass = Puppet::FileServing::Metadata
@file[:source] = %w{/one /two /three /four}
@file.stubs(:newchild).returns @resource
one = [klass.new("/one", :relative_path => "a")]
@file.expects(:perform_recursion).with("/one").returns one
@file.expects(:newchild).with("a").returns @resource
two = [klass.new("/two", :relative_path => "a"), klass.new("/two", :relative_path => "b")]
@file.expects(:perform_recursion).with("/two").returns two
@file.expects(:newchild).with("b").returns @resource
three = [klass.new("/three", :relative_path => "a"), klass.new("/three", :relative_path => "c")]
@file.expects(:perform_recursion).with("/three").returns three
@file.expects(:newchild).with("c").returns @resource
@file.expects(:perform_recursion).with("/four").returns []
@file.recurse_remote({})
end
end
end
end
describe "when returning resources with :eval_generate" do
before do
@graph = stub 'graph', :add_edge => nil
@catalog.stubs(:relationship_graph).returns @graph
@file.catalog = @catalog
@file[:recurse] = true
end
it "should recurse if recursion is enabled" do
resource = stub('resource', :[] => "resource")
@file.expects(:recurse?).returns true
@file.expects(:recurse).returns [resource]
@file.eval_generate.should == [resource]
end
it "should not recurse if recursion is disabled" do
@file.expects(:recurse?).returns false
@file.expects(:recurse).never
@file.eval_generate.should == []
end
it "should return each resource found through recursion" do
foo = stub 'foo', :[] => "/foo"
bar = stub 'bar', :[] => "/bar"
bar2 = stub 'bar2', :[] => "/bar"
@file.expects(:recurse).returns [foo, bar]
@file.eval_generate.should == [foo, bar]
end
end
describe "when recursing" do
before do
@file[:recurse] = true
@metadata = Puppet::FileServing::Metadata
end
describe "and a source is set" do
before { @file[:source] = "/my/source" }
it "should pass the already-discovered resources to recurse_remote" do
@file.stubs(:recurse_local).returns(:foo => "bar")
@file.expects(:recurse_remote).with(:foo => "bar").returns []
@file.recurse
end
end
describe "and a target is set" do
before { @file[:target] = "/link/target" }
it "should use recurse_link" do
@file.stubs(:recurse_local).returns(:foo => "bar")
@file.expects(:recurse_link).with(:foo => "bar").returns []
@file.recurse
end
end
it "should use recurse_local if recurse is not remote" do
@file.expects(:recurse_local).returns({})
@file.recurse
end
it "should not use recurse_local if recurse remote" do
@file[:recurse] = :remote
@file.expects(:recurse_local).never
@file.recurse
end
it "should return the generated resources as an array sorted by file path" do
one = stub 'one', :[] => "/one"
two = stub 'two', :[] => "/one/two"
three = stub 'three', :[] => "/three"
@file.expects(:recurse_local).returns(:one => one, :two => two, :three => three)
@file.recurse.should == [one, two, three]
end
describe "and purging is enabled" do
before do
@file[:purge] = true
end
it "should configure each file to be removed" do
local = stub 'local'
local.stubs(:[]).with(:source).returns nil # Thus, a local file
local.stubs(:[]).with(:path).returns "foo"
@file.expects(:recurse_local).returns("local" => local)
local.expects(:[]=).with(:ensure, :absent)
@file.recurse
end
it "should not remove files that exist in the remote repository" do
@file["source"] = "/my/file"
@file.expects(:recurse_local).returns({})
remote = stub 'remote'
remote.stubs(:[]).with(:source).returns "/whatever" # Thus, a remote file
remote.stubs(:[]).with(:path).returns "foo"
@file.expects(:recurse_remote).with { |hash| hash["remote"] = remote }
remote.expects(:[]=).with(:ensure, :absent).never
@file.recurse
end
end
describe "and making a new child resource" do
it "should not copy the parent resource's parent" do
Puppet::Type.type(:file).expects(:new).with { |options| ! options.include?(:parent) }
@file.newchild("my/path")
end
{:recurse => true, :target => "/foo/bar", :ensure => :present, :alias => "yay", :source => "/foo/bar"}.each do |param, value|
it "should not pass on #{param} to the sub resource" do
@file = Puppet::Type::File.new(:name => @path, param => value, :catalog => @catalog)
@file.class.expects(:new).with { |params| params[param].nil? }
@file.newchild("sub/file")
end
end
it "should copy all of the parent resource's 'should' values that were set at initialization" do
file = @file.class.new(:path => "/foo/bar", :owner => "root", :group => "wheel")
@catalog.add_resource(file)
file.class.expects(:new).with { |options| options[:owner] == "root" and options[:group] == "wheel" }
file.newchild("my/path")
end
it "should not copy default values to the new child" do
@file.class.expects(:new).with { |params| params[:backup].nil? }
@file.newchild("my/path")
end
it "should not copy values to the child which were set by the source" do
@file[:source] = "/foo/bar"
metadata = stub 'metadata', :owner => "root", :group => "root", :mode => 0755, :ftype => "file", :checksum => "{md5}whatever"
@file.parameter(:source).stubs(:metadata).returns metadata
@file.parameter(:source).copy_source_values
@file.class.expects(:new).with { |params| params[:group].nil? }
@file.newchild("my/path")
end
end
end
describe "when setting the backup" do
it "should default to 'puppet'" do
Puppet::Type::File.new(:name => "/my/file")[:backup].should == "puppet"
end
it "should allow setting backup to 'false'" do
(!Puppet::Type::File.new(:name => "/my/file", :backup => false)[:backup]).should be_true
end
it "should set the backup to '.puppet-bak' if it is set to true" do
Puppet::Type::File.new(:name => "/my/file", :backup => true)[:backup].should == ".puppet-bak"
end
it "should support any other backup extension" do
Puppet::Type::File.new(:name => "/my/file", :backup => ".bak")[:backup].should == ".bak"
end
it "should set the filebucket when backup is set to a string matching the name of a filebucket in the catalog" do
catalog = Puppet::Resource::Catalog.new
bucket_resource = Puppet::Type.type(:filebucket).new :name => "foo", :path => "/my/file/bucket"
catalog.add_resource bucket_resource
file = Puppet::Type::File.new(:name => "/my/file")
catalog.add_resource file
file[:backup] = "foo"
file.bucket.should == bucket_resource.bucket
end
it "should find filebuckets added to the catalog after the file resource was created" do
catalog = Puppet::Resource::Catalog.new
file = Puppet::Type::File.new(:name => "/my/file", :backup => "foo")
catalog.add_resource file
bucket_resource = Puppet::Type.type(:filebucket).new :name => "foo", :path => "/my/file/bucket"
catalog.add_resource bucket_resource
file.bucket.should == bucket_resource.bucket
end
it "should have a nil filebucket if backup is false" do
catalog = Puppet::Resource::Catalog.new
bucket_resource = Puppet::Type.type(:filebucket).new :name => "foo", :path => "/my/file/bucket"
catalog.add_resource bucket_resource
file = Puppet::Type::File.new(:name => "/my/file", :backup => false)
catalog.add_resource file
file.bucket.should be_nil
end
it "should have a nil filebucket if backup is set to a string starting with '.'" do
catalog = Puppet::Resource::Catalog.new
bucket_resource = Puppet::Type.type(:filebucket).new :name => "foo", :path => "/my/file/bucket"
catalog.add_resource bucket_resource
file = Puppet::Type::File.new(:name => "/my/file", :backup => ".foo")
catalog.add_resource file
file.bucket.should be_nil
end
it "should fail if there's no catalog and backup is not false" do
file = Puppet::Type::File.new(:name => "/my/file", :backup => "foo")
lambda { file.bucket }.should raise_error(Puppet::Error)
end
it "should fail if a non-existent catalog is specified" do
file = Puppet::Type::File.new(:name => "/my/file", :backup => "foo")
catalog = Puppet::Resource::Catalog.new
catalog.add_resource file
lambda { file.bucket }.should raise_error(Puppet::Error)
end
it "should be able to use the default filebucket without a catalog" do
file = Puppet::Type::File.new(:name => "/my/file", :backup => "puppet")
file.bucket.should be_instance_of(Puppet::FileBucket::Dipper)
end
it "should look up the filebucket during finish()" do
file = Puppet::Type::File.new(:name => "/my/file", :backup => ".foo")
file.expects(:bucket)
file.finish
end
end
describe "when retrieving the current file state" do
it "should copy the source values if the 'source' parameter is set" do
file = Puppet::Type::File.new(:name => "/my/file", :source => "/foo/bar")
file.parameter(:source).expects(:copy_source_values)
file.retrieve
end
end
describe ".title_patterns" do
before do
@type_class = Puppet::Type.type(:file)
end
-
+
it "should have a regexp that captures the entire string, except for a terminating slash" do
patterns = @type_class.title_patterns
string = "abc/\n\tdef/"
patterns[0][0] =~ string
$1.should == "abc/\n\tdef"
end
end
end
diff --git a/spec/unit/type/package_spec.rb b/spec/unit/type/package_spec.rb
index b0c5d2252..662fe4798 100755
--- a/spec/unit/type/package_spec.rb
+++ b/spec/unit/type/package_spec.rb
@@ -1,240 +1,240 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
describe Puppet::Type.type(:package) do
before do
Puppet::Util::Storage.stubs(:store)
end
it "should have an :installable feature that requires the :install method" do
Puppet::Type.type(:package).provider_feature(:installable).methods.should == [:install]
end
it "should have an :uninstallable feature that requires the :uninstall method" do
Puppet::Type.type(:package).provider_feature(:uninstallable).methods.should == [:uninstall]
end
it "should have an :upgradeable feature that requires :update and :latest methods" do
Puppet::Type.type(:package).provider_feature(:upgradeable).methods.should == [:update, :latest]
end
it "should have a :purgeable feature that requires the :purge latest method" do
Puppet::Type.type(:package).provider_feature(:purgeable).methods.should == [:purge]
end
it "should have a :versionable feature" do
Puppet::Type.type(:package).provider_feature(:versionable).should_not be_nil
end
it "should default to being installed" do
pkg = Puppet::Type.type(:package).new(:name => "yay")
pkg.should(:ensure).should == :present
end
describe "when validating attributes" do
[:name, :source, :instance, :status, :adminfile, :responsefile, :configfiles, :category, :platform, :root, :vendor, :description, :allowcdrom].each do |param|
it "should have a #{param} parameter" do
Puppet::Type.type(:package).attrtype(param).should == :param
end
end
it "should have an ensure property" do
Puppet::Type.type(:package).attrtype(:ensure).should == :property
end
end
describe "when validating attribute values" do
before do
@provider = stub 'provider', :class => Puppet::Type.type(:package).defaultprovider, :clear => nil
Puppet::Type.type(:package).defaultprovider.expects(:new).returns(@provider)
end
it "should support :present as a value to :ensure" do
Puppet::Type.type(:package).new(:name => "yay", :ensure => :present)
end
it "should alias :installed to :present as a value to :ensure" do
pkg = Puppet::Type.type(:package).new(:name => "yay", :ensure => :installed)
pkg.should(:ensure).should == :present
end
it "should support :absent as a value to :ensure" do
Puppet::Type.type(:package).new(:name => "yay", :ensure => :absent)
end
it "should support :purged as a value to :ensure if the provider has the :purgeable feature" do
@provider.expects(:satisfies?).with([:purgeable]).returns(true)
Puppet::Type.type(:package).new(:name => "yay", :ensure => :purged)
end
it "should not support :purged as a value to :ensure if the provider does not have the :purgeable feature" do
@provider.expects(:satisfies?).with([:purgeable]).returns(false)
proc { Puppet::Type.type(:package).new(:name => "yay", :ensure => :purged) }.should raise_error(Puppet::Error)
end
it "should support :latest as a value to :ensure if the provider has the :upgradeable feature" do
@provider.expects(:satisfies?).with([:upgradeable]).returns(true)
Puppet::Type.type(:package).new(:name => "yay", :ensure => :latest)
end
it "should not support :latest as a value to :ensure if the provider does not have the :upgradeable feature" do
@provider.expects(:satisfies?).with([:upgradeable]).returns(false)
proc { Puppet::Type.type(:package).new(:name => "yay", :ensure => :latest) }.should raise_error(Puppet::Error)
end
it "should support version numbers as a value to :ensure if the provider has the :versionable feature" do
@provider.expects(:satisfies?).with([:versionable]).returns(true)
Puppet::Type.type(:package).new(:name => "yay", :ensure => "1.0")
end
it "should not support version numbers as a value to :ensure if the provider does not have the :versionable feature" do
@provider.expects(:satisfies?).with([:versionable]).returns(false)
proc { Puppet::Type.type(:package).new(:name => "yay", :ensure => "1.0") }.should raise_error(Puppet::Error)
end
it "should accept any string as an argument to :source" do
proc { Puppet::Type.type(:package).new(:name => "yay", :source => "stuff") }.should_not raise_error(Puppet::Error)
end
end
module PackageEvaluationTesting
def setprops(properties)
@provider.stubs(:properties).returns(properties)
end
end
describe Puppet::Type.type(:package) do
before :each do
@provider = stub 'provider', :class => Puppet::Type.type(:package).defaultprovider, :clear => nil, :satisfies? => true, :name => :mock
Puppet::Type.type(:package).defaultprovider.stubs(:new).returns(@provider)
Puppet::Type.type(:package).defaultprovider.stubs(:instances).returns([])
@package = Puppet::Type.type(:package).new(:name => "yay")
@catalog = Puppet::Resource::Catalog.new
@catalog.add_resource(@package)
end
describe Puppet::Type.type(:package), "when it should be purged" do
include PackageEvaluationTesting
before { @package[:ensure] = :purged }
it "should do nothing if it is :purged" do
- @provider.expects(:properties).returns(:ensure => :purged)
+ @provider.expects(:properties).returns(:ensure => :purged).at_least_once
@catalog.apply
end
[:absent, :installed, :present, :latest].each do |state|
it "should purge if it is #{state.to_s}" do
@provider.stubs(:properties).returns(:ensure => state)
@provider.expects(:purge)
@catalog.apply
end
end
end
describe Puppet::Type.type(:package), "when it should be absent" do
include PackageEvaluationTesting
before { @package[:ensure] = :absent }
[:purged, :absent].each do |state|
it "should do nothing if it is #{state.to_s}" do
- @provider.expects(:properties).returns(:ensure => state)
+ @provider.expects(:properties).returns(:ensure => state).at_least_once
@catalog.apply
end
end
[:installed, :present, :latest].each do |state|
it "should uninstall if it is #{state.to_s}" do
@provider.stubs(:properties).returns(:ensure => state)
@provider.expects(:uninstall)
@catalog.apply
end
end
end
describe Puppet::Type.type(:package), "when it should be present" do
include PackageEvaluationTesting
before { @package[:ensure] = :present }
[:present, :latest, "1.0"].each do |state|
it "should do nothing if it is #{state.to_s}" do
- @provider.expects(:properties).returns(:ensure => state)
+ @provider.expects(:properties).returns(:ensure => state).at_least_once
@catalog.apply
end
end
[:purged, :absent].each do |state|
it "should install if it is #{state.to_s}" do
@provider.stubs(:properties).returns(:ensure => state)
@provider.expects(:install)
@catalog.apply
end
end
end
describe Puppet::Type.type(:package), "when it should be latest" do
include PackageEvaluationTesting
before { @package[:ensure] = :latest }
[:purged, :absent].each do |state|
it "should upgrade if it is #{state.to_s}" do
@provider.stubs(:properties).returns(:ensure => state)
@provider.expects(:update)
@catalog.apply
end
end
it "should upgrade if the current version is not equal to the latest version" do
@provider.stubs(:properties).returns(:ensure => "1.0")
@provider.stubs(:latest).returns("2.0")
@provider.expects(:update)
@catalog.apply
end
it "should do nothing if it is equal to the latest version" do
@provider.stubs(:properties).returns(:ensure => "1.0")
@provider.stubs(:latest).returns("1.0")
@provider.expects(:update).never
@catalog.apply
end
it "should do nothing if the provider returns :present as the latest version" do
@provider.stubs(:properties).returns(:ensure => :present)
@provider.stubs(:latest).returns("1.0")
@provider.expects(:update).never
@catalog.apply
end
end
describe Puppet::Type.type(:package), "when it should be a specific version" do
include PackageEvaluationTesting
before { @package[:ensure] = "1.0" }
[:purged, :absent].each do |state|
it "should install if it is #{state.to_s}" do
@provider.stubs(:properties).returns(:ensure => state)
@provider.expects(:install)
@catalog.apply
end
end
it "should do nothing if the current version is equal to the desired version" do
@provider.stubs(:properties).returns(:ensure => "1.0")
@provider.expects(:install).never
@catalog.apply
end
it "should install if the current version is not equal to the specified version" do
@provider.stubs(:properties).returns(:ensure => "2.0")
@provider.expects(:install)
@catalog.apply
end
end
end
end
diff --git a/spec/unit/type_spec.rb b/spec/unit/type_spec.rb
index 48b00ec4a..b7a08977e 100755
--- a/spec/unit/type_spec.rb
+++ b/spec/unit/type_spec.rb
@@ -1,573 +1,573 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
describe Puppet::Type do
it "should include the Cacher module" do
Puppet::Type.ancestors.should be_include(Puppet::Util::Cacher)
end
it "should consider a parameter to be valid if it is a valid parameter" do
Puppet::Type.type(:mount).should be_valid_parameter(:path)
end
it "should consider a parameter to be valid if it is a valid property" do
Puppet::Type.type(:mount).should be_valid_parameter(:fstype)
end
it "should consider a parameter to be valid if it is a valid metaparam" do
Puppet::Type.type(:mount).should be_valid_parameter(:noop)
end
it "should use its catalog as its expirer" do
catalog = Puppet::Resource::Catalog.new
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
resource.catalog = catalog
resource.expirer.should equal(catalog)
end
it "should do nothing when asked to expire when it has no catalog" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
lambda { resource.expire }.should_not raise_error
end
it "should be able to retrieve a property by name" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
resource.property(:fstype).must be_instance_of(Puppet::Type.type(:mount).attrclass(:fstype))
end
it "should be able to retrieve a parameter by name" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
resource.parameter(:name).must be_instance_of(Puppet::Type.type(:mount).attrclass(:name))
end
it "should be able to retrieve a property by name using the :parameter method" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
resource.parameter(:fstype).must be_instance_of(Puppet::Type.type(:mount).attrclass(:fstype))
end
it "should be able to retrieve all set properties" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
props = resource.properties
props.should_not be_include(nil)
[:fstype, :ensure, :pass].each do |name|
props.should be_include(resource.parameter(name))
end
end
it "should have a method for setting default values for resources" do
Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:set_default)
end
it "should do nothing for attributes that have no defaults and no specified value" do
Puppet::Type.type(:mount).new(:name => "foo").parameter(:noop).should be_nil
end
it "should have a method for adding tags" do
Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:tags)
end
it "should use the tagging module" do
Puppet::Type.type(:mount).ancestors.should be_include(Puppet::Util::Tagging)
end
it "should delegate to the tagging module when tags are added" do
resource = Puppet::Type.type(:mount).new(:name => "foo")
resource.stubs(:tag).with(:mount)
resource.expects(:tag).with(:tag1, :tag2)
resource.tags = [:tag1,:tag2]
end
it "should add the current type as tag" do
resource = Puppet::Type.type(:mount).new(:name => "foo")
resource.stubs(:tag)
resource.expects(:tag).with(:mount)
resource.tags = [:tag1,:tag2]
end
it "should have a method to know if the resource is exported" do
Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:exported?)
end
it "should have a method to know if the resource is virtual" do
Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:virtual?)
end
it "should consider its version to be its catalog version" do
resource = Puppet::Type.type(:mount).new(:name => "foo")
catalog = Puppet::Resource::Catalog.new
catalog.version = 50
catalog.add_resource resource
resource.version.should == 50
end
it "should consider its version to be zero if it has no catalog" do
Puppet::Type.type(:mount).new(:name => "foo").version.should == 0
end
it "should provide source_descriptors" do
resource = Puppet::Type.type(:mount).new(:name => "foo")
catalog = Puppet::Resource::Catalog.new
catalog.version = 50
catalog.add_resource resource
- resource.source_descriptors.should == {:version=>50, :tags=>["mount", "foo"], :path=>"/Mount[foo]"}
+ resource.source_descriptors.should == {:tags=>["mount", "foo"], :path=>"/Mount[foo]"}
end
it "should consider its type to be the name of its class" do
Puppet::Type.type(:mount).new(:name => "foo").type.should == :mount
end
it "should use any provided noop value" do
Puppet::Type.type(:mount).new(:name => "foo", :noop => true).must be_noop
end
it "should use the global noop value if none is provided" do
Puppet[:noop] = true
Puppet::Type.type(:mount).new(:name => "foo").must be_noop
end
it "should not be noop if in a non-host_config catalog" do
resource = Puppet::Type.type(:mount).new(:name => "foo")
catalog = Puppet::Resource::Catalog.new
catalog.add_resource resource
resource.should_not be_noop
end
describe "when creating an event" do
before do
@resource = Puppet::Type.type(:mount).new :name => "foo"
end
it "should have the resource's reference as the resource" do
@resource.event.resource.should == "Mount[foo]"
end
it "should have the resource's log level as the default log level" do
@resource[:loglevel] = :warning
@resource.event.default_log_level.should == :warning
end
- {:file => "/my/file", :line => 50, :tags => %{foo bar}, :version => 50}.each do |attr, value|
+ {:file => "/my/file", :line => 50, :tags => %{foo bar}}.each do |attr, value|
it "should set the #{attr}" do
@resource.stubs(attr).returns value
@resource.event.send(attr).should == value
end
end
it "should allow specification of event attributes" do
@resource.event(:status => "noop").status.should == "noop"
end
end
describe "when choosing a default provider" do
it "should choose the provider with the highest specificity" do
# Make a fake type
type = Puppet::Type.newtype(:defaultprovidertest) do
newparam(:name) do end
end
basic = type.provide(:basic) {}
greater = type.provide(:greater) {}
basic.stubs(:specificity).returns 1
greater.stubs(:specificity).returns 2
type.defaultprovider.should equal(greater)
end
end
describe "when initializing" do
describe "and passed a TransObject" do
it "should fail" do
trans = Puppet::TransObject.new("/foo", :mount)
lambda { Puppet::Type.type(:mount).new(trans) }.should raise_error(Puppet::DevError)
end
end
describe "and passed a Puppet::Resource instance" do
it "should set its title to the title of the resource if the resource type is equal to the current type" do
resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "/other"})
Puppet::Type.type(:mount).new(resource).title.should == "/foo"
end
it "should set its title to the resource reference if the resource type is not equal to the current type" do
resource = Puppet::Resource.new(:user, "foo")
Puppet::Type.type(:mount).new(resource).title.should == "User[foo]"
end
[:line, :file, :catalog, :exported, :virtual].each do |param|
it "should copy '#{param}' from the resource if present" do
resource = Puppet::Resource.new(:mount, "/foo")
resource.send(param.to_s + "=", "foo")
resource.send(param.to_s + "=", "foo")
Puppet::Type.type(:mount).new(resource).send(param).should == "foo"
end
end
it "should copy any tags from the resource" do
resource = Puppet::Resource.new(:mount, "/foo")
resource.tag "one", "two"
tags = Puppet::Type.type(:mount).new(resource).tags
tags.should be_include("one")
tags.should be_include("two")
end
it "should copy the resource's parameters as its own" do
resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:atboot => true, :fstype => "boo"})
params = Puppet::Type.type(:mount).new(resource).to_hash
params[:fstype].should == "boo"
params[:atboot].should == true
end
end
describe "and passed a Hash" do
it "should extract the title from the hash" do
Puppet::Type.type(:mount).new(:title => "/yay").title.should == "/yay"
end
it "should work when hash keys are provided as strings" do
Puppet::Type.type(:mount).new("title" => "/yay").title.should == "/yay"
end
it "should work when hash keys are provided as symbols" do
Puppet::Type.type(:mount).new(:title => "/yay").title.should == "/yay"
end
it "should use the name from the hash as the title if no explicit title is provided" do
Puppet::Type.type(:mount).new(:name => "/yay").title.should == "/yay"
end
it "should use the Resource Type's namevar to determine how to find the name in the hash" do
Puppet::Type.type(:file).new(:path => "/yay").title.should == "/yay"
end
[:catalog].each do |param|
it "should extract '#{param}' from the hash if present" do
Puppet::Type.type(:mount).new(:name => "/yay", param => "foo").send(param).should == "foo"
end
end
it "should use any remaining hash keys as its parameters" do
resource = Puppet::Type.type(:mount).new(:title => "/foo", :catalog => "foo", :atboot => true, :fstype => "boo")
resource[:fstype].must == "boo"
resource[:atboot].must == true
end
end
it "should fail if any invalid attributes have been provided" do
lambda { Puppet::Type.type(:mount).new(:title => "/foo", :nosuchattr => "whatever") }.should raise_error(Puppet::Error)
end
it "should set its name to the resource's title if the resource does not have a :name or namevar parameter set" do
resource = Puppet::Resource.new(:mount, "/foo")
Puppet::Type.type(:mount).new(resource).name.should == "/foo"
end
it "should fail if no title, name, or namevar are provided" do
lambda { Puppet::Type.type(:file).new(:atboot => true) }.should raise_error(Puppet::Error)
end
it "should set the attributes in the order returned by the class's :allattrs method" do
Puppet::Type.type(:mount).stubs(:allattrs).returns([:name, :atboot, :noop])
resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "myname", :atboot => "myboot", :noop => "whatever"})
set = []
Puppet::Type.type(:mount).any_instance.stubs(:newattr).with do |param, hash|
set << param
true
end.returns(stub_everything("a property"))
Puppet::Type.type(:mount).new(resource)
set[-1].should == :noop
set[-2].should == :atboot
end
it "should always set the name and then default provider before anything else" do
Puppet::Type.type(:mount).stubs(:allattrs).returns([:provider, :name, :atboot])
resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "myname", :atboot => "myboot"})
set = []
Puppet::Type.type(:mount).any_instance.stubs(:newattr).with do |param, hash|
set << param
true
end.returns(stub_everything("a property"))
Puppet::Type.type(:mount).new(resource)
set[0].should == :name
set[1].should == :provider
end
# This one is really hard to test :/
it "should each default immediately if no value is provided" do
defaults = []
Puppet::Type.type(:package).any_instance.stubs(:set_default).with { |value| defaults << value; true }
Puppet::Type.type(:package).new :name => "whatever"
defaults[0].should == :provider
end
it "should retain a copy of the originally provided parameters" do
Puppet::Type.type(:mount).new(:name => "foo", :atboot => true, :noop => false).original_parameters.should == {:atboot => true, :noop => false}
end
it "should delete the name via the namevar from the originally provided parameters" do
Puppet::Type.type(:file).new(:name => "/foo").original_parameters[:path].should be_nil
end
end
it "should have a class method for converting a hash into a Puppet::Resource instance" do
Puppet::Type.type(:mount).must respond_to(:hash2resource)
end
describe "when converting a hash to a Puppet::Resource instance" do
before do
@type = Puppet::Type.type(:mount)
end
it "should treat a :title key as the title of the resource" do
@type.hash2resource(:name => "/foo", :title => "foo").title.should == "foo"
end
it "should use the name from the hash as the title if no explicit title is provided" do
@type.hash2resource(:name => "foo").title.should == "foo"
end
it "should use the Resource Type's namevar to determine how to find the name in the hash" do
@type.stubs(:key_attributes).returns([ :myname ])
@type.hash2resource(:myname => "foo").title.should == "foo"
end
[:catalog].each do |attr|
it "should use any provided #{attr}" do
@type.hash2resource(:name => "foo", attr => "eh").send(attr).should == "eh"
end
end
it "should set all provided parameters on the resource" do
@type.hash2resource(:name => "foo", :fstype => "boo", :boot => "fee").to_hash.should == {:name => "foo", :fstype => "boo", :boot => "fee"}
end
it "should not set the title as a parameter on the resource" do
@type.hash2resource(:name => "foo", :title => "eh")[:title].should be_nil
end
it "should not set the catalog as a parameter on the resource" do
@type.hash2resource(:name => "foo", :catalog => "eh")[:catalog].should be_nil
end
it "should treat hash keys equivalently whether provided as strings or symbols" do
resource = @type.hash2resource("name" => "foo", "title" => "eh", "fstype" => "boo")
resource.title.should == "eh"
resource[:name].should == "foo"
resource[:fstype].should == "boo"
end
end
describe "when retrieving current property values" do
before do
@resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
@resource.property(:ensure).stubs(:retrieve).returns :absent
end
it "should fail if its provider is unsuitable" do
@resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
@resource.provider.class.expects(:suitable?).returns false
lambda { @resource.retrieve_resource }.should raise_error(Puppet::Error)
end
it "should return a Puppet::Resource instance with its type and title set appropriately" do
result = @resource.retrieve_resource
result.should be_instance_of(Puppet::Resource)
result.type.should == "Mount"
result.title.should == "foo"
end
it "should set the name of the returned resource if its own name and title differ" do
@resource[:name] = "my name"
@resource.title = "other name"
@resource.retrieve_resource[:name].should == "my name"
end
it "should provide a value for all set properties" do
values = @resource.retrieve_resource
[:ensure, :fstype, :pass].each { |property| values[property].should_not be_nil }
end
it "should provide a value for 'ensure' even if no desired value is provided" do
@resource = Puppet::Type.type(:file).new(:path => "/my/file/that/can't/exist")
end
it "should not call retrieve on non-ensure properties if the resource is absent and should consider the property absent" do
@resource.property(:ensure).expects(:retrieve).returns :absent
@resource.property(:fstype).expects(:retrieve).never
@resource.retrieve_resource[:fstype].should == :absent
end
it "should include the result of retrieving each property's current value if the resource is present" do
@resource.property(:ensure).expects(:retrieve).returns :present
@resource.property(:fstype).expects(:retrieve).returns 15
@resource.retrieve_resource[:fstype] == 15
end
end
describe ".title_patterns" do
describe "when there's one namevar" do
before do
@type_class = Puppet::Type.type(:notify)
@type_class.stubs(:key_attributes).returns([:one])
end
it "should have a default pattern for when there's one namevar" do
patterns = @type_class.title_patterns
patterns.length.should == 1
patterns[0].length.should == 2
end
it "should have a regexp that captures the entire string" do
patterns = @type_class.title_patterns
string = "abc\n\tdef"
patterns[0][0] =~ string
$1.should == "abc\n\tdef"
end
end
end
describe "when in a catalog" do
before do
@catalog = Puppet::Resource::Catalog.new
@container = Puppet::Type.type(:component).new(:name => "container")
@one = Puppet::Type.type(:file).new(:path => "/file/one")
@two = Puppet::Type.type(:file).new(:path => "/file/two")
@catalog.add_resource @container
@catalog.add_resource @one
@catalog.add_resource @two
@catalog.add_edge @container, @one
@catalog.add_edge @container, @two
end
it "should have no parent if there is no in edge" do
@container.parent.should be_nil
end
it "should set its parent to its in edge" do
@one.parent.ref.should == @container.ref
end
after do
@catalog.clear(true)
end
end
it "should have a 'stage' metaparam" do
Puppet::Type.metaparamclass(:stage).should be_instance_of(Class)
end
end
describe Puppet::Type::RelationshipMetaparam do
it "should be a subclass of Puppet::Parameter" do
Puppet::Type::RelationshipMetaparam.superclass.should equal(Puppet::Parameter)
end
it "should be able to produce a list of subclasses" do
Puppet::Type::RelationshipMetaparam.should respond_to(:subclasses)
end
describe "when munging relationships" do
before do
@resource = Puppet::Type.type(:mount).new :name => "/foo"
@metaparam = Puppet::Type.metaparamclass(:require).new :resource => @resource
end
it "should accept Puppet::Resource instances" do
ref = Puppet::Resource.new(:file, "/foo")
@metaparam.munge(ref)[0].should equal(ref)
end
it "should turn any string into a Puppet::Resource" do
@metaparam.munge("File[/ref]")[0].should be_instance_of(Puppet::Resource)
end
end
it "should be able to validate relationships" do
Puppet::Type.metaparamclass(:require).new(:resource => mock("resource")).should respond_to(:validate_relationship)
end
it "should fail if any specified resource is not found in the catalog" do
catalog = mock 'catalog'
resource = stub 'resource', :catalog => catalog, :ref => "resource"
param = Puppet::Type.metaparamclass(:require).new(:resource => resource, :value => %w{Foo[bar] Class[test]})
catalog.expects(:resource).with("Foo[bar]").returns "something"
catalog.expects(:resource).with("Class[Test]").returns nil
param.expects(:fail).with { |string| string.include?("Class[Test]") }
param.validate_relationship
end
end
describe Puppet::Type.metaparamclass(:check) do
it "should warn and create an instance of ':audit'" do
file = Puppet::Type.type(:file).new :path => "/foo"
file.expects(:warning)
file[:check] = :mode
file[:audit].should == [:mode]
end
end
describe Puppet::Type.metaparamclass(:audit) do
before do
@resource = Puppet::Type.type(:file).new :path => "/foo"
end
it "should default to being nil" do
@resource[:audit].should be_nil
end
it "should specify all possible properties when asked to audit all properties" do
@resource[:audit] = :all
list = @resource.class.properties.collect { |p| p.name }
@resource[:audit].should == list
end
it "should accept the string 'all' to specify auditing all possible properties" do
@resource[:audit] = 'all'
list = @resource.class.properties.collect { |p| p.name }
@resource[:audit].should == list
end
it "should fail if asked to audit an invalid property" do
lambda { @resource[:audit] = :foobar }.should raise_error(Puppet::Error)
end
it "should create an attribute instance for each auditable property" do
@resource[:audit] = :mode
@resource.parameter(:mode).should_not be_nil
end
it "should accept properties specified as a string" do
@resource[:audit] = "mode"
@resource.parameter(:mode).should_not be_nil
end
it "should not create attribute instances for parameters, only properties" do
@resource[:audit] = :noop
@resource.parameter(:noop).should be_nil
end
end
diff --git a/spec/unit/util/checksums_spec.rb b/spec/unit/util/checksums_spec.rb
index e018581af..a8bc12be2 100755
--- a/spec/unit/util/checksums_spec.rb
+++ b/spec/unit/util/checksums_spec.rb
@@ -1,153 +1,161 @@
#!/usr/bin/env ruby
#
# Created by Luke Kanies on 2007-9-22.
# Copyright (c) 2007. All rights reserved.
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/util/checksums'
describe Puppet::Util::Checksums do
before do
@summer = Object.new
@summer.extend(Puppet::Util::Checksums)
end
content_sums = [:md5, :md5lite, :sha1, :sha1lite]
file_only = [:ctime, :mtime, :none]
content_sums.each do |sumtype|
it "should be able to calculate #{sumtype} sums from strings" do
@summer.should be_respond_to(sumtype)
end
end
[content_sums, file_only].flatten.each do |sumtype|
it "should be able to calculate #{sumtype} sums from files" do
@summer.should be_respond_to(sumtype.to_s + "_file")
end
end
[content_sums, file_only].flatten.each do |sumtype|
it "should be able to calculate #{sumtype} sums from stream" do
@summer.should be_respond_to(sumtype.to_s + "_stream")
end
end
it "should have a method for determining whether a given string is a checksum" do
@summer.should respond_to(:checksum?)
end
%w{{md5}asdfasdf {sha1}asdfasdf {ctime}asdasdf {mtime}asdfasdf}.each do |sum|
it "should consider #{sum} to be a checksum" do
@summer.should be_checksum(sum)
end
end
%w{{nosuchsum}asdfasdf {a}asdfasdf {ctime}}.each do |sum|
it "should not consider #{sum} to be a checksum" do
@summer.should_not be_checksum(sum)
end
end
it "should have a method for stripping a sum type from an existing checksum" do
@summer.sumtype("{md5}asdfasdfa").should == "md5"
end
it "should have a method for stripping the data from a checksum" do
@summer.sumdata("{md5}asdfasdfa").should == "asdfasdfa"
end
it "should return a nil sumtype if the checksum does not mention a checksum type" do
@summer.sumtype("asdfasdfa").should be_nil
end
{:md5 => Digest::MD5, :sha1 => Digest::SHA1}.each do |sum, klass|
describe("when using #{sum}") do
it "should use #{klass} to calculate string checksums" do
klass.expects(:hexdigest).with("mycontent").returns "whatever"
@summer.send(sum, "mycontent").should == "whatever"
end
it "should use incremental #{klass} sums to calculate file checksums" do
digest = mock 'digest'
klass.expects(:new).returns digest
file = "/path/to/my/file"
fh = mock 'filehandle'
fh.expects(:read).with(4096).times(3).returns("firstline").then.returns("secondline").then.returns(nil)
#fh.expects(:read).with(512).returns("secondline")
#fh.expects(:read).with(512).returns(nil)
File.expects(:open).with(file, "r").yields(fh)
digest.expects(:<<).with "firstline"
digest.expects(:<<).with "secondline"
digest.expects(:hexdigest).returns :mydigest
@summer.send(sum.to_s + "_file", file).should == :mydigest
end
it "should yield #{klass} to the given block to calculate stream checksums" do
digest = mock 'digest'
klass.expects(:new).returns digest
digest.expects(:hexdigest).returns :mydigest
@summer.send(sum.to_s + "_stream") do |sum|
sum.should == digest
end.should == :mydigest
end
end
end
{:md5lite => Digest::MD5, :sha1lite => Digest::SHA1}.each do |sum, klass|
describe("when using #{sum}") do
it "should use #{klass} to calculate string checksums from the first 512 characters of the string" do
content = "this is a test" * 100
klass.expects(:hexdigest).with(content[0..511]).returns "whatever"
@summer.send(sum, content).should == "whatever"
end
it "should use #{klass} to calculate a sum from the first 512 characters in the file" do
digest = mock 'digest'
klass.expects(:new).returns digest
file = "/path/to/my/file"
fh = mock 'filehandle'
fh.expects(:read).with(512).returns('my content')
File.expects(:open).with(file, "r").yields(fh)
digest.expects(:<<).with "my content"
digest.expects(:hexdigest).returns :mydigest
@summer.send(sum.to_s + "_file", file).should == :mydigest
end
end
end
[:ctime, :mtime].each do |sum|
describe("when using #{sum}") do
it "should use the '#{sum}' on the file to determine the ctime" do
file = "/my/file"
stat = mock 'stat', sum => "mysum"
File.expects(:stat).with(file).returns(stat)
@summer.send(sum.to_s + "_file", file).should == "mysum"
end
it "should return nil for streams" do
- @summer.send(sum.to_s + "_stream").should be_nil
+ expectation = stub "expectation"
+ expectation.expects(:do_something!).at_least_once
+ @summer.send(sum.to_s + "_stream"){ |checksum| checksum << "anything" ; expectation.do_something! }.should be_nil
end
end
end
describe "when using the none checksum" do
it "should return an empty string" do
@summer.none_file("/my/file").should == ""
end
+
+ it "should return an empty string for streams" do
+ expectation = stub "expectation"
+ expectation.expects(:do_something!).at_least_once
+ @summer.none_stream{ |checksum| checksum << "anything" ; expectation.do_something! }.should == ""
+ end
end
end
diff --git a/spec/unit/util/log_spec.rb b/spec/unit/util/log_spec.rb
index 7d96fe190..f3fd1b051 100755
--- a/spec/unit/util/log_spec.rb
+++ b/spec/unit/util/log_spec.rb
@@ -1,215 +1,222 @@
#!/usr/bin/env ruby
Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
require 'puppet/util/log'
describe Puppet::Util::Log do
it "should write a given message to the specified destination" do
arraydest = []
Puppet::Util::Log.newdestination(arraydest)
Puppet::Util::Log.new(:level => :notice, :message => "foo")
message = arraydest.last.message
message.should == "foo"
end
describe Puppet::Util::Log::DestConsole do
before do
@console = Puppet::Util::Log::DestConsole.new
end
it "should colorize if Puppet[:color] is :ansi" do
Puppet[:color] = :ansi
@console.colorize(:alert, "abc").should == "\e[0;31mabc\e[0m"
end
it "should colorize if Puppet[:color] is 'yes'" do
Puppet[:color] = "yes"
@console.colorize(:alert, "abc").should == "\e[0;31mabc\e[0m"
end
it "should htmlize if Puppet[:color] is :html" do
Puppet[:color] = :html
@console.colorize(:alert, "abc").should == "abc"
end
it "should do nothing if Puppet[:color] is false" do
Puppet[:color] = false
@console.colorize(:alert, "abc").should == "abc"
end
it "should do nothing if Puppet[:color] is invalid" do
Puppet[:color] = "invalid option"
@console.colorize(:alert, "abc").should == "abc"
end
end
describe "instances" do
before do
Puppet::Util::Log.stubs(:newmessage)
end
[:level, :message, :time, :remote].each do |attr|
it "should have a #{attr} attribute" do
log = Puppet::Util::Log.new :level => :notice, :message => "A test message"
log.should respond_to(attr)
log.should respond_to(attr.to_s + "=")
end
end
it "should fail if created without a level" do
lambda { Puppet::Util::Log.new(:message => "A test message") }.should raise_error(ArgumentError)
end
it "should fail if created without a message" do
lambda { Puppet::Util::Log.new(:level => :notice) }.should raise_error(ArgumentError)
end
it "should make available the level passed in at initialization" do
Puppet::Util::Log.new(:level => :notice, :message => "A test message").level.should == :notice
end
it "should make available the message passed in at initialization" do
Puppet::Util::Log.new(:level => :notice, :message => "A test message").message.should == "A test message"
end
# LAK:NOTE I don't know why this behavior is here, I'm just testing what's in the code,
# at least at first.
it "should always convert messages to strings" do
Puppet::Util::Log.new(:level => :notice, :message => :foo).message.should == "foo"
end
it "should flush the log queue when the first destination is specified" do
Puppet::Util::Log.close_all
Puppet::Util::Log.expects(:flushqueue)
Puppet::Util::Log.newdestination([])
end
it "should convert the level to a symbol if it's passed in as a string" do
Puppet::Util::Log.new(:level => "notice", :message => :foo).level.should == :notice
end
it "should fail if the level is not a symbol or string" do
lambda { Puppet::Util::Log.new(:level => 50, :message => :foo) }.should raise_error(ArgumentError)
end
it "should fail if the provided level is not valid" do
Puppet::Util::Log.expects(:validlevel?).with(:notice).returns false
lambda { Puppet::Util::Log.new(:level => :notice, :message => :foo) }.should raise_error(ArgumentError)
end
it "should set its time to the initialization time" do
time = mock 'time'
Time.expects(:now).returns time
Puppet::Util::Log.new(:level => "notice", :message => :foo).time.should equal(time)
end
it "should make available any passed-in tags" do
log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :tags => %w{foo bar})
log.tags.should be_include("foo")
log.tags.should be_include("bar")
end
it "should use an passed-in source" do
Puppet::Util::Log.any_instance.expects(:source=).with "foo"
Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => "foo")
end
- [:file, :line, :version].each do |attr|
+ [:file, :line].each do |attr|
it "should use #{attr} if provided" do
Puppet::Util::Log.any_instance.expects(attr.to_s + "=").with "foo"
Puppet::Util::Log.new(:level => "notice", :message => :foo, attr => "foo")
end
end
it "should default to 'Puppet' as its source" do
Puppet::Util::Log.new(:level => "notice", :message => :foo).source.should == "Puppet"
end
it "should register itself with Log" do
Puppet::Util::Log.expects(:newmessage)
Puppet::Util::Log.new(:level => "notice", :message => :foo)
end
it "should have a method for determining if a tag is present" do
Puppet::Util::Log.new(:level => "notice", :message => :foo).should respond_to(:tagged?)
end
it "should match a tag if any of the tags are equivalent to the passed tag as a string" do
Puppet::Util::Log.new(:level => "notice", :message => :foo, :tags => %w{one two}).should be_tagged(:one)
end
it "should tag itself with its log level" do
Puppet::Util::Log.new(:level => "notice", :message => :foo).should be_tagged(:notice)
end
it "should return its message when converted to a string" do
Puppet::Util::Log.new(:level => "notice", :message => :foo).to_s.should == "foo"
end
it "should include its time, source, level, and message when prepared for reporting" do
log = Puppet::Util::Log.new(:level => "notice", :message => :foo)
report = log.to_report
report.should be_include("notice")
report.should be_include("foo")
report.should be_include(log.source)
report.should be_include(log.time.to_s)
end
describe "when setting the source as a RAL object" do
it "should tag itself with any tags the source has" do
source = Puppet::Type.type(:file).new :path => "/foo/bar"
log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source)
source.tags.each do |tag|
log.tags.should be_include(tag)
end
end
it "should use the source_descriptors" do
source = stub "source"
source.stubs(:source_descriptors).returns(:tags => ["tag","tag2"], :path => "path", :version => 100)
log = Puppet::Util::Log.new(:level => "notice", :message => :foo)
log.expects(:tag).with("tag")
log.expects(:tag).with("tag2")
- log.expects(:version=).with(100)
log.source = source
log.source.should == "path"
end
- it "should copy over any version information" do
- catalog = Puppet::Resource::Catalog.new
- catalog.version = 25
- source = Puppet::Type.type(:file).new :path => "/foo/bar"
- catalog.add_resource source
-
- log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source)
- log.version.should == 25
- end
-
it "should copy over any file and line information" do
source = Puppet::Type.type(:file).new :path => "/foo/bar"
source.file = "/my/file"
source.line = 50
log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source)
log.file.should == "/my/file"
log.line.should == 50
end
end
describe "when setting the source as a non-RAL object" do
it "should not try to copy over file, version, line, or tag information" do
source = Puppet::Module.new("foo")
source.expects(:file).never
log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source)
end
end
end
+
+ describe "to_yaml" do
+ it "should not include the @version attribute" do
+ log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :version => 100)
+ log.to_yaml_properties.should_not include('@version')
+ end
+
+ it "should include attributes @level, @message, @source, @tags, and @time" do
+ log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :version => 100)
+ log.to_yaml_properties.should == %w{@level @message @source @tags @time}
+ end
+
+ it "should include attributes @file and @line if specified" do
+ log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :file => "foo", :line => 35)
+ log.to_yaml_properties.should include('@file')
+ log.to_yaml_properties.should include('@line')
+ end
+ end
end
diff --git a/spec/unit/util/logging_spec.rb b/spec/unit/util/logging_spec.rb
index 46ae5386f..411cd17a9 100755
--- a/spec/unit/util/logging_spec.rb
+++ b/spec/unit/util/logging_spec.rb
@@ -1,95 +1,95 @@
#!/usr/bin/env ruby
Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
require 'puppet/util/logging'
class LoggingTester
include Puppet::Util::Logging
end
describe Puppet::Util::Logging do
before do
@logger = LoggingTester.new
end
Puppet::Util::Log.eachlevel do |level|
it "should have a method for sending '#{level}' logs" do
@logger.should respond_to(level)
end
end
it "should have a method for sending a log with a specified log level" do
@logger.expects(:to_s).returns "I'm a string!"
Puppet::Util::Log.expects(:create).with { |args| args[:source] == "I'm a string!" and args[:level] == "loglevel" and args[:message] == "mymessage" }
@logger.send_log "loglevel", "mymessage"
end
describe "when sending a log" do
it "should use the Log's 'create' entrance method" do
Puppet::Util::Log.expects(:create)
@logger.notice "foo"
end
it "should send itself converted to a string as the log source" do
@logger.expects(:to_s).returns "I'm a string!"
Puppet::Util::Log.expects(:create).with { |args| args[:source] == "I'm a string!" }
@logger.notice "foo"
end
it "should queue logs sent without a specified destination" do
Puppet::Util::Log.close_all
Puppet::Util::Log.expects(:queuemessage)
@logger.notice "foo"
end
it "should use the path of any provided resource type" do
resource = Puppet::Type.type(:mount).new :name => "foo"
resource.expects(:path).returns "/path/to/mount".to_sym
Puppet::Util::Log.expects(:create).with { |args| args[:source] == "/path/to/mount" }
resource.notice "foo"
end
it "should use the path of any provided resource parameter" do
resource = Puppet::Type.type(:mount).new :name => "foo"
param = resource.parameter(:name)
param.expects(:path).returns "/path/to/param".to_sym
Puppet::Util::Log.expects(:create).with { |args| args[:source] == "/path/to/param" }
param.notice "foo"
end
it "should send the provided argument as the log message" do
Puppet::Util::Log.expects(:create).with { |args| args[:message] == "foo" }
@logger.notice "foo"
end
it "should join any provided arguments into a single string for the message" do
Puppet::Util::Log.expects(:create).with { |args| args[:message] == "foo bar baz" }
@logger.notice ["foo", "bar", "baz"]
end
- [:file, :line, :version, :tags].each do |attr|
+ [:file, :line, :tags].each do |attr|
it "should include #{attr} if available" do
@logger.singleton_class.send(:attr_accessor, attr)
@logger.send(attr.to_s + "=", "myval")
Puppet::Util::Log.expects(:create).with { |args| args[attr] == "myval" }
@logger.notice "foo"
end
end
end
end
diff --git a/spec/unit/util/metric_spec.rb b/spec/unit/util/metric_spec.rb
index 72571ee4a..600b88f85 100755
--- a/spec/unit/util/metric_spec.rb
+++ b/spec/unit/util/metric_spec.rb
@@ -1,95 +1,95 @@
#!/usr/bin/env ruby
Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
require 'puppet/util/metric'
describe Puppet::Util::Metric do
before do
@metric = Puppet::Util::Metric.new("foo")
end
it "should be aliased to Puppet::Metric" do
Puppet::Util::Metric.should equal(Puppet::Metric)
end
[:type, :name, :value, :label, :basedir].each do |name|
it "should have a #{name} attribute" do
@metric.should respond_to(name)
@metric.should respond_to(name.to_s + "=")
end
end
it "should default to the :rrdir as the basedir "do
Puppet.settings.expects(:value).with(:rrddir).returns "myrrd"
@metric.basedir.should == "myrrd"
end
it "should use any provided basedir" do
@metric.basedir = "foo"
@metric.basedir.should == "foo"
end
it "should require a name at initialization" do
lambda { Puppet::Util::Metric.new }.should raise_error(ArgumentError)
end
it "should always convert its name to a string" do
Puppet::Util::Metric.new(:foo).name.should == "foo"
end
it "should support a label" do
Puppet::Util::Metric.new("foo", "mylabel").label.should == "mylabel"
end
it "should autogenerate a label if none is provided" do
Puppet::Util::Metric.new("foo_bar").label.should == "Foo bar"
end
it "should have a method for adding values" do
@metric.should respond_to(:newvalue)
end
it "should have a method for returning values" do
@metric.should respond_to(:values)
end
it "should require a name and value for its values" do
lambda { @metric.newvalue }.should raise_error(ArgumentError)
end
it "should support a label for values" do
- @metric.newvalue(:foo, 10, "label")
+ @metric.newvalue("foo", 10, "label")
@metric.values[0][1].should == "label"
end
it "should autogenerate value labels if none is provided" do
@metric.newvalue("foo_bar", 10)
@metric.values[0][1].should == "Foo bar"
end
it "should return its values sorted by label" do
- @metric.newvalue(:foo, 10, "b")
- @metric.newvalue(:bar, 10, "a")
+ @metric.newvalue("foo", 10, "b")
+ @metric.newvalue("bar", 10, "a")
- @metric.values.should == [[:bar, "a", 10], [:foo, "b", 10]]
+ @metric.values.should == [["bar", "a", 10], ["foo", "b", 10]]
end
it "should use an array indexer method to retrieve individual values" do
- @metric.newvalue(:foo, 10)
- @metric[:foo].should == 10
+ @metric.newvalue("foo", 10)
+ @metric["foo"].should == 10
end
it "should return nil if the named value cannot be found" do
- @metric[:foo].should == 0
+ @metric["foo"].should == 0
end
# LAK: I'm not taking the time to develop these tests right now.
# I expect they should actually be extracted into a separate class
# anyway.
it "should be able to graph metrics using RRDTool"
it "should be able to create a new RRDTool database"
it "should be able to store metrics into an RRDTool database"
end
diff --git a/spec/unit/util/queue/stomp_spec.rb b/spec/unit/util/queue/stomp_spec.rb
index 9f1d28448..c33f1a670 100755
--- a/spec/unit/util/queue/stomp_spec.rb
+++ b/spec/unit/util/queue/stomp_spec.rb
@@ -1,140 +1,136 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../spec_helper'
require 'puppet/util/queue'
-describe Puppet::Util::Queue do
- confine "Missing Stomp" => Puppet.features.stomp?
-
+describe Puppet::Util::Queue, :if => Puppet.features.stomp? do
it 'should load :stomp client appropriately' do
Puppet.settings.stubs(:value).returns 'faux_queue_source'
Puppet::Util::Queue.queue_type_to_class(:stomp).name.should == 'Puppet::Util::Queue::Stomp'
end
end
-describe 'Puppet::Util::Queue::Stomp' do
- confine "Missing Stomp" => Puppet.features.stomp?
-
+describe 'Puppet::Util::Queue::Stomp', :if => Puppet.features.stomp? do
before do
# So we make sure we never create a real client instance.
# Otherwise we'll try to connect, and that's bad.
Stomp::Client.stubs(:new).returns stub("client")
end
it 'should be registered with Puppet::Util::Queue as :stomp type' do
Puppet::Util::Queue.queue_type_to_class(:stomp).should == Puppet::Util::Queue::Stomp
end
describe "when initializing" do
it "should create a Stomp client instance" do
Stomp::Client.expects(:new).returns stub("stomp_client")
Puppet::Util::Queue::Stomp.new
end
it "should provide helpful failures when the queue source is not a valid source" do
# Stub rather than expect, so we can include the source in the error
Puppet.settings.stubs(:value).with(:queue_source).returns "-----"
lambda { Puppet::Util::Queue::Stomp.new }.should raise_error(ArgumentError)
end
it "should fail unless the queue source is a stomp URL" do
# Stub rather than expect, so we can include the source in the error
Puppet.settings.stubs(:value).with(:queue_source).returns "http://foo/bar"
lambda { Puppet::Util::Queue::Stomp.new }.should raise_error(ArgumentError)
end
it "should fail somewhat helpfully if the Stomp client cannot be created" do
Stomp::Client.expects(:new).raises RuntimeError
lambda { Puppet::Util::Queue::Stomp.new }.should raise_error(ArgumentError)
end
list = %w{user password host port}
{"user" => "myuser", "password" => "mypass", "host" => "foohost", "port" => 42}.each do |name, value|
it "should use the #{name} from the queue source as the queueing #{name}" do
Puppet.settings.expects(:value).with(:queue_source).returns "stomp://myuser:mypass@foohost:42/"
Stomp::Client.expects(:new).with { |*args| args[list.index(name)] == value }
Puppet::Util::Queue::Stomp.new
end
end
it "should create a reliable client instance" do
Puppet.settings.expects(:value).with(:queue_source).returns "stomp://myuser@foohost:42/"
Stomp::Client.expects(:new).with { |*args| args[4] == true }
Puppet::Util::Queue::Stomp.new
end
end
describe "when sending a message" do
before do
@client = stub 'client'
Stomp::Client.stubs(:new).returns @client
@queue = Puppet::Util::Queue::Stomp.new
end
it "should send it to the queue client instance" do
@client.expects(:send).with { |queue, msg, options| msg == "Smite!" }
@queue.send_message('fooqueue', 'Smite!')
end
it "should send it to the transformed queue name" do
@client.expects(:send).with { |queue, msg, options| queue == "/queue/fooqueue" }
@queue.send_message('fooqueue', 'Smite!')
end
it "should send it as a persistent message" do
@client.expects(:send).with { |queue, msg, options| options[:persistent] == true }
@queue.send_message('fooqueue', 'Smite!')
end
end
describe "when subscribing to a queue" do
before do
@client = stub 'client', :acknowledge => true
Stomp::Client.stubs(:new).returns @client
@queue = Puppet::Util::Queue::Stomp.new
end
it "should subscribe via the queue client instance" do
@client.expects(:subscribe)
@queue.subscribe('fooqueue')
end
it "should subscribe to the transformed queue name" do
@client.expects(:subscribe).with { |queue, options| queue == "/queue/fooqueue" }
@queue.subscribe('fooqueue')
end
it "should specify that its messages should be acknowledged" do
@client.expects(:subscribe).with { |queue, options| options[:ack] == :client }
@queue.subscribe('fooqueue')
end
it "should yield the body of any received message" do
message = mock 'message'
message.expects(:body).returns "mybody"
@client.expects(:subscribe).yields(message)
body = nil
@queue.subscribe('fooqueue') { |b| body = b }
body.should == "mybody"
end
it "should acknowledge all successfully processed messages" do
message = stub 'message', :body => "mybode"
@client.stubs(:subscribe).yields(message)
@client.expects(:acknowledge).with(message)
@queue.subscribe('fooqueue') { |b| "eh" }
end
end
it 'should transform the simple queue name to "/queue/"' do
Puppet::Util::Queue::Stomp.new.stompify_target('blah').should == '/queue/blah'
end
end
diff --git a/spec/unit/util/settings/file_setting_spec.rb b/spec/unit/util/settings/file_setting_spec.rb
index 2870fbb57..dcfb6e3b1 100755
--- a/spec/unit/util/settings/file_setting_spec.rb
+++ b/spec/unit/util/settings/file_setting_spec.rb
@@ -1,256 +1,254 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../spec_helper'
require 'puppet/util/settings'
require 'puppet/util/settings/file_setting'
describe Puppet::Util::Settings::FileSetting do
FileSetting = Puppet::Util::Settings::FileSetting
before do
@basepath = Puppet.features.posix? ? "/somepath" : "C:/somepath"
end
describe "when determining whether the service user should be used" do
before do
@settings = mock 'settings'
@settings.stubs(:[]).with(:mkusers).returns false
@settings.stubs(:service_user_available?).returns true
end
it "should be true if the service user is available" do
@settings.expects(:service_user_available?).returns true
setting = FileSetting.new(:settings => @settings, :owner => "root", :desc => "a setting")
setting.should be_use_service_user
end
it "should be true if 'mkusers' is set" do
@settings.expects(:[]).with(:mkusers).returns true
setting = FileSetting.new(:settings => @settings, :owner => "root", :desc => "a setting")
setting.should be_use_service_user
end
it "should be false if the service user is not available and 'mkusers' is unset" do
setting = FileSetting.new(:settings => @settings, :owner => "root", :desc => "a setting")
setting.should be_use_service_user
end
end
describe "when setting the owner" do
it "should allow the file to be owned by root" do
root_owner = lambda { FileSetting.new(:settings => mock("settings"), :owner => "root", :desc => "a setting") }
root_owner.should_not raise_error
end
it "should allow the file to be owned by the service user" do
service_owner = lambda { FileSetting.new(:settings => mock("settings"), :owner => "service", :desc => "a setting") }
service_owner.should_not raise_error
end
it "should allow the ownership of the file to be unspecified" do
no_owner = lambda { FileSetting.new(:settings => mock("settings"), :desc => "a setting") }
no_owner.should_not raise_error
end
it "should not allow other owners" do
invalid_owner = lambda { FileSetting.new(:settings => mock("settings"), :owner => "invalid", :desc => "a setting") }
invalid_owner.should raise_error(FileSetting::SettingError)
end
end
describe "when reading the owner" do
it "should be root when the setting specifies root" do
setting = FileSetting.new(:settings => mock("settings"), :owner => "root", :desc => "a setting")
setting.owner.should == "root"
end
it "should be the owner of the service when the setting specifies service and the service user should be used" do
settings = mock("settings")
settings.stubs(:[]).returns "the_service"
setting = FileSetting.new(:settings => settings, :owner => "service", :desc => "a setting")
setting.expects(:use_service_user?).returns true
setting.owner.should == "the_service"
end
it "should be the root when the setting specifies service and the service user should not be used" do
settings = mock("settings")
settings.stubs(:[]).returns "the_service"
setting = FileSetting.new(:settings => settings, :owner => "service", :desc => "a setting")
setting.expects(:use_service_user?).returns false
setting.owner.should == "root"
end
it "should be nil when the owner is unspecified" do
FileSetting.new(:settings => mock("settings"), :desc => "a setting").owner.should be_nil
end
end
describe "when setting the group" do
it "should allow the group to be service" do
service_group = lambda { FileSetting.new(:settings => mock("settings"), :group => "service", :desc => "a setting") }
service_group.should_not raise_error
end
it "should allow the group to be unspecified" do
no_group = lambda { FileSetting.new(:settings => mock("settings"), :desc => "a setting") }
no_group.should_not raise_error
end
it "should not allow invalid groups" do
invalid_group = lambda { FileSetting.new(:settings => mock("settings"), :group => "invalid", :desc => "a setting") }
invalid_group.should raise_error(FileSetting::SettingError)
end
end
describe "when reading the group" do
it "should be service when the setting specifies service" do
setting = FileSetting.new(:settings => mock("settings", :[] => "the_service"), :group => "service", :desc => "a setting")
setting.group.should == "the_service"
end
it "should be nil when the group is unspecified" do
FileSetting.new(:settings => mock("settings"), :desc => "a setting").group.should be_nil
end
end
it "should be able to be converted into a resource" do
FileSetting.new(:settings => mock("settings"), :desc => "eh").should respond_to(:to_resource)
end
describe "when being converted to a resource" do
before do
@settings = mock 'settings'
@file = Puppet::Util::Settings::FileSetting.new(:settings => @settings, :desc => "eh", :name => :mydir, :section => "mysect")
@settings.stubs(:value).with(:mydir).returns @basepath
end
it "should skip files that cannot determine their types" do
@file.expects(:type).returns nil
@file.to_resource.should be_nil
end
it "should skip non-existent files if 'create_files' is not enabled" do
@file.expects(:create_files?).returns false
@file.expects(:type).returns :file
File.expects(:exist?).with(@basepath).returns false
@file.to_resource.should be_nil
end
it "should manage existent files even if 'create_files' is not enabled" do
@file.expects(:create_files?).returns false
@file.expects(:type).returns :file
File.expects(:exist?).with(@basepath).returns true
@file.to_resource.should be_instance_of(Puppet::Resource)
end
- describe "on POSIX systems" do
- confine "no /dev on Microsoft Windows" => Puppet.features.posix?
-
+ describe "on POSIX systems", :if => Puppet.features.posix? do
it "should skip files in /dev" do
@settings.stubs(:value).with(:mydir).returns "/dev/file"
@file.to_resource.should be_nil
end
end
it "should skip files whose paths are not strings" do
@settings.stubs(:value).with(:mydir).returns :foo
@file.to_resource.should be_nil
end
it "should return a file resource with the path set appropriately" do
resource = @file.to_resource
resource.type.should == "File"
resource.title.should == @basepath
end
it "should fully qualified returned files if necessary (#795)" do
@settings.stubs(:value).with(:mydir).returns "myfile"
@file.to_resource.title.should == File.join(Dir.getwd, "myfile")
end
it "should set the mode on the file if a mode is provided" do
@file.mode = 0755
@file.to_resource[:mode].should == 0755
end
it "should not set the mode on a the file if manage_internal_file_permissions is disabled" do
Puppet[:manage_internal_file_permissions] = false
@file.stubs(:mode).returns(0755)
@file.to_resource[:mode].should == nil
end
it "should set the owner if running as root and the owner is provided" do
Puppet.features.expects(:root?).returns true
@file.stubs(:owner).returns "foo"
@file.to_resource[:owner].should == "foo"
end
it "should not set the owner if manage_internal_file_permissions is disabled" do
Puppet[:manage_internal_file_permissions] = false
Puppet.features.stubs(:root?).returns true
@file.stubs(:owner).returns "foo"
@file.to_resource[:owner].should == nil
end
it "should set the group if running as root and the group is provided" do
Puppet.features.expects(:root?).returns true
@file.stubs(:group).returns "foo"
@file.to_resource[:group].should == "foo"
end
it "should not set the group if manage_internal_file_permissions is disabled" do
Puppet[:manage_internal_file_permissions] = false
Puppet.features.stubs(:root?).returns true
@file.stubs(:group).returns "foo"
@file.to_resource[:group].should == nil
end
it "should not set owner if not running as root" do
Puppet.features.expects(:root?).returns false
@file.stubs(:owner).returns "foo"
@file.to_resource[:owner].should be_nil
end
it "should not set group if not running as root" do
Puppet.features.expects(:root?).returns false
@file.stubs(:group).returns "foo"
@file.to_resource[:group].should be_nil
end
it "should set :ensure to the file type" do
@file.expects(:type).returns :directory
@file.to_resource[:ensure].should == :directory
end
it "should set the loglevel to :debug" do
@file.to_resource[:loglevel].should == :debug
end
it "should set the backup to false" do
@file.to_resource[:backup].should be_false
end
it "should tag the resource with the settings section" do
@file.expects(:section).returns "mysect"
@file.to_resource.should be_tagged("mysect")
end
it "should tag the resource with the setting name" do
@file.to_resource.should be_tagged("mydir")
end
it "should tag the resource with 'settings'" do
@file.to_resource.should be_tagged("settings")
end
end
end
diff --git a/test/lib/puppettest/fileparsing.rb b/test/lib/puppettest/fileparsing.rb
index 914c4bcb3..bd4f9e152 100644
--- a/test/lib/puppettest/fileparsing.rb
+++ b/test/lib/puppettest/fileparsing.rb
@@ -1,26 +1,28 @@
+require 'test/unit'
+
module PuppetTest::FileParsing
# Run an isomorphism test on our parsing process.
def fakedataparse(*files)
files.each do |file|
@provider.stubs(:default_target).returns(file)
@provider.prefetch
text = @provider.to_file(@provider.target_records(file))
text.gsub!(/^# HEADER.+\n/, '')
yield if block_given?
oldlines = File.readlines(file)
newlines = text.chomp.split "\n"
oldlines.zip(newlines).each do |old, new|
if self.is_a?(Test::Unit::TestCase)
assert_equal(old.chomp.gsub(/\s+/, ''), new.gsub(/\s+/, ''), "File was not written back out correctly")
else
new.gsub(/\s+/, '').should == old.chomp.gsub(/\s+/, '')
end
end
end
end
end
diff --git a/test/lib/puppettest/reporttesting.rb b/test/lib/puppettest/reporttesting.rb
index 448a6a9d8..54a2f6799 100644
--- a/test/lib/puppettest/reporttesting.rb
+++ b/test/lib/puppettest/reporttesting.rb
@@ -1,16 +1,16 @@
module PuppetTest::Reporttesting
def fakereport
# Create a bunch of log messages in an array.
- report = Puppet::Transaction::Report.new
+ report = Puppet::Transaction::Report.new("apply")
3.times { |i|
# We have to use warning so that the logs always happen
log = Puppet.warning("Report test message #{i}")
report << log
}
report
end
end
diff --git a/test/other/report.rb b/test/other/report.rb
index 8a909b41c..eacf1632b 100755
--- a/test/other/report.rb
+++ b/test/other/report.rb
@@ -1,135 +1,135 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../lib/puppettest'
require 'puppet'
require 'puppet/reports'
require 'puppet/transaction/report'
require 'puppettest'
require 'puppettest/reporttesting'
class TestReports < Test::Unit::TestCase
include PuppetTest
include PuppetTest::Reporttesting
def mkreport
# First do some work
objects = []
6.times do |i|
file = tempfile
# Make every third file
File.open(file, "w") { |f| f.puts "" } if i % 3 == 0
objects << Puppet::Type.type(:file).new(
:path => file,
:ensure => "file"
)
end
config = mk_catalog(*objects)
# So the report works out.
config.retrieval_duration = 0.001
trans = config.apply
trans.generate_report
end
# Make sure we can use reports as log destinations.
def test_reports_as_log_destinations
report = fakereport
assert_nothing_raised {
Puppet::Util::Log.newdestination(report)
}
# Now make a file for testing logging
file = Puppet::Type.type(:file).new(:path => tempfile, :ensure => "file")
file.finish
log = nil
assert_nothing_raised {
log = file.log "This is a message, yo"
}
assert(report.logs.include?(log), "Report did not get log message")
assert_nothing_raised {
Puppet::Util::Log.close(report)
}
log = file.log "This is another message, yo"
assert(! report.logs.include?(log), "Report got log message after close")
end
def test_store_report
# Create a bunch of log messages in an array.
- report = Puppet::Transaction::Report.new
+ report = Puppet::Transaction::Report.new("apply")
# We have to reuse reporting here because of something going on in the
# server/report.rb file
Puppet.settings.use(:main, :master)
3.times { |i|
log = Puppet.warning("Report test message #{i}")
report << log
}
assert_nothing_raised do
report.extend(Puppet::Reports.report(:store))
end
yaml = YAML.dump(report)
file = report.process
assert(FileTest.exists?(file), "report file did not get created")
assert_equal(yaml, File.read(file), "File did not get written")
end
if Puppet.features.rrd? || Puppet.features.rrd_legacy?
def test_rrdgraph_report
Puppet.settings.use(:main, :metrics)
report = mkreport
assert(! report.metrics.empty?, "Did not receive any metrics")
assert_nothing_raised do
report.extend(Puppet::Reports.report(:rrdgraph))
end
assert_nothing_raised {
report.process
}
hostdir = nil
assert_nothing_raised do
hostdir = report.hostdir
end
assert(hostdir, "Did not get hostdir back")
assert(FileTest.directory?(hostdir), "Host rrd dir did not get created")
index = File.join(hostdir, "index.html")
assert(FileTest.exists?(index), "index file was not created")
# Now make sure it creaets each of the rrd files
%w{changes resources time}.each do |type|
file = File.join(hostdir, "#{type}.rrd")
assert(FileTest.exists?(file), "Did not create rrd file for #{type}")
daily = file.sub ".rrd", "-daily.png"
assert(FileTest.exists?(daily), "Did not make daily graph for #{type}")
end
end
else
$stderr.puts "Install RRD for metric reporting tests"
end
end
diff --git a/test/ral/providers/cron/crontab.rb b/test/ral/providers/cron/crontab.rb
index 0c87a5bba..be2af1e16 100755
--- a/test/ral/providers/cron/crontab.rb
+++ b/test/ral/providers/cron/crontab.rb
@@ -1,648 +1,651 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../lib/puppettest'
require 'puppettest'
require 'mocha'
require 'puppettest/fileparsing'
class TestCronParsedProvider < Test::Unit::TestCase
include PuppetTest
include PuppetTest::FileParsing
FIELDS = {
:crontab => %w{command minute hour month monthday weekday}.collect { |o| o.intern },
:freebsd_special => %w{special command}.collect { |o| o.intern },
:environment => [:line],
:blank => [:line],
:comment => [:line],
}
# These are potentially multi-line records; there's no one-to-one map, but they model
# a full cron job. These tests assume individual record types will always be correctly
# parsed, so all they
def sample_crons
@sample_crons ||= YAML.load(File.read(File.join(@crondir, "crontab_collections.yaml")))
end
# These are simple lines that can appear in the files; there is a one to one
# mapping between records and lines. We have plenty of redundancy here because
# we use these records to build up our complex, multi-line cron jobs below.
def sample_records
@sample_records ||= YAML.load(File.read(File.join(@crondir, "crontab_sample_records.yaml")))
end
def setup
super
@type = Puppet::Type.type(:cron)
@provider = @type.provider(:crontab)
@provider.initvars
@crondir = datadir(File.join(%w{providers cron}))
@oldfiletype = @provider.filetype
end
def teardown
Puppet::Util::FileType.filetype(:ram).clear
@provider.clear
super
end
# Make sure a cron job matches up. Any non-passed fields are considered absent.
def assert_cron_equal(msg, cron, options)
assert_instance_of(@provider, cron, "not an instance of provider in #{msg}")
options.each do |param, value|
assert_equal(value, cron.send(param), "#{param} was not equal in #{msg}")
end
%w{command environment minute hour month monthday weekday}.each do |var|
assert_equal(:absent, cron.send(var), "#{var} was not parsed absent in #{msg}") unless options.include?(var.intern)
end
end
# Make sure a cron record matches. This only works for crontab records.
def assert_record_equal(msg, record, options)
raise ArgumentError, "You must pass the required record type" unless options.include?(:record_type)
assert_instance_of(Hash, record, "not an instance of a hash in #{msg}")
options.each do |param, value|
assert_equal(value, record[param], "#{param} was not equal in #{msg}")
end
FIELDS[record[:record_type]].each do |var|
assert_equal(:absent, record[var], "#{var} was not parsed absent in #{msg}") unless options.include?(var)
end
end
def assert_header(file)
header = []
file.gsub! /^(# HEADER: .+$)\n/ do
header << $1
''
end
assert_equal(4, header.length, "Did not get four header lines")
end
# This handles parsing every possible iteration of cron records. Note that this is only
# single-line stuff and doesn't include multi-line values (e.g., with names and/or envs).
# Those have separate tests.
def test_parse_line
# First just do each sample record one by one
sample_records.each do |name, options|
result = nil
assert_nothing_raised("Could not parse #{name}: '#{options[:text]}'") do
result = @provider.parse_line(options[:text])
end
assert_record_equal("record for #{name}", result, options[:record])
end
# Then do them all at once.
records = []
text = ""
- sample_records.each do |name, options|
+ # Sort sample_records so that the :empty entry does not come last
+ # (if it does, the test will fail because the empty last line will
+ # be ignored)
+ sample_records.sort { |a, b| a.first.to_s <=> b.first.to_s }.each do |name, options|
records << options[:record]
text += options[:text] + "\n"
end
result = nil
assert_nothing_raised("Could not match all records in one file") do
result = @provider.parse(text)
end
records.zip(result).each do |should, record|
assert_record_equal("record for #{should.inspect} in full match", record, should)
end
end
# Here we test that each record generates to the correct text.
def test_generate_line
# First just do each sample record one by one
sample_records.each do |name, options|
result = nil
assert_nothing_raised("Could not generate #{name}: '#{options[:record]}'") do
result = @provider.to_line(options[:record])
end
assert_equal(options[:text], result, "Did not generate correct text for #{name}")
end
# Then do them all at once.
records = []
text = ""
sample_records.each do |name, options|
records << options[:record]
text += options[:text] + "\n"
end
result = nil
assert_nothing_raised("Could not match all records in one file") do
result = @provider.to_file(records)
end
assert_header(result)
assert_equal(text, result, "Did not generate correct full crontab")
end
# Test cronjobs that are made up from multiple records.
def test_multi_line_cronjobs
fulltext = ""
all_records = []
sample_crons.each do |name, record_names|
records = record_names.collect do |record_name|
unless record = sample_records[record_name]
raise "Could not find sample record #{record_name}"
end
record
end
text = records.collect { |r| r[:text] }.join("\n") + "\n"
record_list = records.collect { |r| r[:record] }
# Add it to our full collection
all_records += record_list
fulltext += text
# First make sure we generate each one correctly
result = nil
assert_nothing_raised("Could not generate multi-line cronjob #{name}") do
result = @provider.to_file(record_list)
end
assert_header(result)
assert_equal(text, result, "Did not generate correct text for multi-line cronjob #{name}")
# Now make sure we parse each one correctly
assert_nothing_raised("Could not parse multi-line cronjob #{name}") do
result = @provider.parse(text)
end
record_list.zip(result).each do |should, record|
assert_record_equal("multiline cronjob #{name}", record, should)
end
end
# Make sure we can generate it all correctly
result = nil
assert_nothing_raised("Could not generate all multi-line cronjobs") do
result = @provider.to_file(all_records)
end
assert_header(result)
assert_equal(fulltext, result, "Did not generate correct text for all multi-line cronjobs")
# Now make sure we parse them all correctly
assert_nothing_raised("Could not parse multi-line cronjobs") do
result = @provider.parse(fulltext)
end
all_records.zip(result).each do |should, record|
assert_record_equal("multiline cronjob %s", record, should)
end
end
# Take our sample files, and make sure we can entirely parse them,
# then that we can generate them again and we get the same data.
def test_parse_and_generate_sample_files
@provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
crondir = datadir(File.join(%w{providers cron}))
files = Dir.glob("#{crondir}/crontab.*")
setme
@provider.default_target = @me
target = @provider.target_object(@me)
files.each do |file|
str = args = nil
assert_nothing_raised("could not load #{file}") do
str, args = YAML.load(File.read(file))
end
# Stupid old yaml
args.each do |hash|
hash.each do |param, value|
if param.is_a?(String) and param =~ /^:/
hash.delete(param)
param = param.sub(/^:/,'').intern
hash[param] = value
end
if value.is_a?(String) and value =~ /^:/
value = value.sub(/^:/,'').intern
hash[param] = value
end
end
end
target.write(str)
assert_nothing_raised("could not parse #{file}") do
@provider.prefetch
end
records = @provider.send(:instance_variable_get, "@records")
args.zip(records) do |should, sis|
# Make the values a bit more equal.
should[:target] = @me
should[:ensure] = :present
#should[:environment] ||= []
should[:on_disk] = true
is = sis.dup
sis.dup.each do |p,v|
is.delete(p) if v == :absent
end
assert_equal(
should, is,
"Did not parse #{file} correctly")
end
assert_nothing_raised("could not generate #{file}") do
@provider.flush_target(@me)
end
assert_equal(str, target.read, "#{file} changed")
@provider.clear
end
end
# A simple test to see if we can load the cron from disk.
def test_load
setme
records = nil
assert_nothing_raised {
records = @provider.retrieve(@me)
}
assert_instance_of(Array, records, "did not get correct response")
end
# Test that a cron job turns out as expected, by creating one and generating
# it directly
def test_simple_to_cron
# make the cron
setme
name = "yaytest"
args = {:name => name,
:command => "date > /dev/null",
:minute => "30",
:user => @me,
:record_type => :crontab
}
# generate the text
str = nil
assert_nothing_raised {
str = @provider.to_line(args)
}
assert_equal(
"# Puppet Name: #{name}\n30 * * * * date > /dev/null", str,
"Cron did not generate correctly")
end
# Test that comments are correctly retained
def test_retain_comments
str = "# this is a comment\n#and another comment\n"
user = "fakeuser"
records = nil
@provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
target = @provider.target_object(user)
target.write(str)
assert_nothing_raised {
@provider.prefetch
}
assert_nothing_raised {
newstr = @provider.flush_target(user)
assert(target.read.include?(str), "Comments were lost")
}
end
def test_simpleparsing
@provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
text = "5 1,2 * 1 0 /bin/echo funtest"
records = nil
assert_nothing_raised {
records = @provider.parse(text)
}
should = {
:minute => %w{5},
:hour => %w{1 2},
:monthday => :absent,
:month => %w{1},
:weekday => %w{0},
:command => "/bin/echo funtest"
}
is = records.shift
assert(is, "Did not get record")
should.each do |p, v|
assert_equal(v, is[p], "did not parse #{p} correctly")
end
end
# Make sure we can create a cron in an empty tab.
# LAK:FIXME This actually modifies the user's crontab,
# which is pretty heinous.
def test_mkcron_if_empty
setme
@provider.filetype = @oldfiletype
records = @provider.retrieve(@me)
target = @provider.target_object(@me)
cleanup do
if records.length == 0
target.remove
else
target.write(@provider.to_file(records))
end
end
# Now get rid of it
assert_nothing_raised("Could not remove cron tab") do
target.remove
end
@provider.flush :target => @me, :command => "/do/something",
:record_type => :crontab
created = @provider.retrieve(@me)
assert(created.detect { |r| r[:command] == "/do/something" }, "Did not create cron tab")
end
# Make sure we correctly bidirectionally parse things.
def test_records_and_strings
@provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
setme
target = @provider.target_object(@me)
[
"* * * * * /some/command",
"0,30 * * * * /some/command",
"0-30 * * * * /some/command",
"# Puppet Name: name\n0-30 * * * * /some/command",
"# Puppet Name: name\nVAR=VALUE\n0-30 * * * * /some/command",
"# Puppet Name: name\nVAR=VALUE\nC=D\n0-30 * * * * /some/command",
"0 * * * * /some/command"
].each do |str|
@provider.initvars
str += "\n"
target.write(str)
assert_equal(
str, target.read,
"Did not write correctly")
assert_nothing_raised("Could not prefetch with #{str.inspect}") do
@provider.prefetch
end
assert_nothing_raised("Could not flush with #{str.inspect}") do
@provider.flush_target(@me)
end
assert_equal(
str, target.read,
"Changed in read/write")
@provider.clear
end
end
# Test that a specified cron job will be matched against an existing job
# with no name, as long as all fields match
def test_matchcron
mecron = "0,30 * * * * date
* * * * * funtest
# a comment
0,30 * * 1 * date
"
youcron = "0,30 * * * * date
* * * * * yaytest
# a comment
0,30 * * 1 * fooness
"
setme
@provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
you = "you"
# Write the same tab to multiple targets
@provider.target_object(@me).write(mecron.gsub(/^\s+/, ''))
@provider.target_object(you).write(youcron.gsub(/^\s+/, ''))
# Now make some crons that should match
matchers = [
@type.new(
:name => "yaycron",
:minute => [0, 30],
:command => "date",
:user => @me
),
@type.new(
:name => "youtest",
:command => "yaytest",
:user => you
)
]
nonmatchers = [
@type.new(
:name => "footest",
:minute => [0, 30],
:hour => 1,
:command => "fooness",
:user => @me # wrong target
),
@type.new(
:name => "funtest2",
:command => "funtest",
:user => you # wrong target for this cron
)
]
# Create another cron so we prefetch two of them
@type.new(:name => "testing", :minute => 30, :command => "whatever", :user => "you")
assert_nothing_raised("Could not prefetch cron") do
@provider.prefetch([matchers, nonmatchers].flatten.inject({}) { |crons, cron| crons[cron.name] = cron; crons })
end
matchers.each do |cron|
assert_equal(:present, cron.provider.ensure, "Cron #{cron.name} was not matched")
if value = cron.value(:minute) and value == "*"
value = :absent
end
assert_equal(value, cron.provider.minute, "Minutes were not retrieved, so cron was not matched")
assert_equal(cron.value(:target), cron.provider.target, "Cron #{cron.name} was matched from the wrong target")
end
nonmatchers.each do |cron|
assert_equal(:absent, cron.provider.ensure, "Cron #{cron.name} was incorrectly matched")
end
end
def test_data
setme
@provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
target = @provider.target_object(@me)
fakedata("data/providers/cron/examples").each do |file|
text = File.read(file)
target.write(text)
assert_nothing_raised("Could not parse #{file}") do
@provider.prefetch
end
# mark the provider modified
@provider.modified(@me)
# and zero the text
target.write("")
result = nil
assert_nothing_raised("Could not generate #{file}") do
@provider.flush_target(@me)
end
# Ignore whitespace differences, since those don't affect function.
modtext = text.gsub(/[ \t]+/, " ")
modtarget = target.read.gsub(/[ \t]+/, " ")
assert_equal(modtext, modtarget, "File was not rewritten the same")
@provider.clear
end
end
# Match freebsd's annoying @daily stuff.
def test_match_freebsd_special
@provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
setme
target = @provider.target_object(@me)
[
"@daily /some/command",
"@daily /some/command more"
].each do |str|
@provider.initvars
str += "\n"
target.write(str)
assert_nothing_raised("Could not prefetch with #{str.inspect}") do
@provider.prefetch
end
records = @provider.send(:instance_variable_get, "@records")
records.each do |r|
assert_equal(
:freebsd_special, r[:record_type],
"Did not create lines as freebsd lines")
end
assert_nothing_raised("Could not flush with #{str.inspect}") do
@provider.flush_target(@me)
end
assert_equal(
str, target.read,
"Changed in read/write")
@provider.clear
end
end
# #707
def test_write_freebsd_special
assert_equal(@provider.to_line(:record_type => :crontab, :ensure => :present, :special => "reboot", :command => "/bin/echo something"), "@reboot /bin/echo something")
end
def test_prefetch
cron = @type.new :command => "/bin/echo yay", :name => "test", :hour => 4
assert_nothing_raised("Could not prefetch cron") do
cron.provider.class.prefetch("test" => cron)
end
end
# Testing #669.
def test_environment_settings
@provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
setme
target = @provider.target_object(@me)
# First with no env settings
resource = @type.new :command => "/bin/echo yay", :name => "test", :hour => 4
cron = resource.provider
cron.ensure = :present
cron.command = "/bin/echo yay"
cron.hour = %w{4}
cron.flush
result = target.read
assert_equal("# Puppet Name: test\n* 4 * * * /bin/echo yay\n", result, "Did not write cron out correctly")
# Now set the env
cron.environment = "TEST=foo"
cron.flush
result = target.read
assert_equal("# Puppet Name: test\nTEST=foo\n* 4 * * * /bin/echo yay\n", result, "Did not write out environment setting")
# Modify it
cron.environment = ["TEST=foo", "BLAH=yay"]
cron.flush
result = target.read
assert_equal("# Puppet Name: test\nTEST=foo\nBLAH=yay\n* 4 * * * /bin/echo yay\n", result, "Did not write out environment setting")
# And remove it
cron.environment = :absent
cron.flush
result = target.read
assert_equal("# Puppet Name: test\n* 4 * * * /bin/echo yay\n", result, "Did not write out environment setting")
end
# Testing #1216
def test_strange_lines
@provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram))
text = " 5 \t\t 1,2 * 1 0 /bin/echo funtest"
records = nil
assert_nothing_raised {
records = @provider.parse(text)
}
should = {
:minute => %w{5},
:hour => %w{1 2},
:monthday => :absent,
:month => %w{1},
:weekday => %w{0},
:command => "/bin/echo funtest"
}
is = records.shift
assert(is, "Did not get record")
should.each do |p, v|
assert_equal(v, is[p], "did not parse #{p} correctly")
end
end
end