diff --git a/lib/puppet/parameter.rb b/lib/puppet/parameter.rb
index 04b3aec30..ae152de56 100644
--- a/lib/puppet/parameter.rb
+++ b/lib/puppet/parameter.rb
@@ -1,505 +1,519 @@
require 'puppet/util/methodhelper'
require 'puppet/util/log_paths'
require 'puppet/util/logging'
require 'puppet/util/docs'
require 'puppet/util/cacher'
class Puppet::Parameter
include Puppet::Util
include Puppet::Util::Errors
include Puppet::Util::LogPaths
include Puppet::Util::Logging
include Puppet::Util::MethodHelper
include Puppet::Util::Cacher
# A collection of values and regexes, used for specifying
# what values are allowed in a given parameter.
class ValueCollection
class Value
attr_reader :name, :options, :event
attr_accessor :block, :call, :method, :required_features
# Add an alias for this value.
def alias(name)
@aliases << convert(name)
end
# Return all aliases.
def aliases
@aliases.dup
end
# Store the event that our value generates, if it does so.
def event=(value)
@event = convert(value)
end
def initialize(name)
if name.is_a?(Regexp)
@name = name
else
# Convert to a string and then a symbol, so things like true/false
# still show up as symbols.
@name = convert(name)
end
@aliases = []
@call = :instead
end
# Does a provided value match our value?
def match?(value)
if regex?
return true if name =~ value.to_s
else
return true if name == convert(value)
return @aliases.include?(convert(value))
end
end
# Is our value a regex?
def regex?
@name.is_a?(Regexp)
end
private
# A standard way of converting all of our values, so we're always
# comparing apples to apples.
def convert(value)
if value == ''
# We can't intern an empty string, yay.
value
else
value.to_s.to_sym
end
end
end
def aliasvalue(name, other)
other = other.to_sym
unless value = match?(other)
raise Puppet::DevError, "Cannot alias nonexistent value %s" % other
end
value.alias(name)
end
# Return a doc string for all of the values in this parameter/property.
def doc
unless defined?(@doc)
@doc = ""
unless values.empty?
@doc += " Valid values are "
@doc += @strings.collect do |value|
if aliases = value.aliases and ! aliases.empty?
"``%s`` (also called ``%s``)" % [value.name, aliases.join(", ")]
else
"``%s``" % value.name
end
end.join(", ") + "."
end
unless regexes.empty?
@doc += " Values can match ``" + regexes.join("``, ``") + "``."
end
end
@doc
end
# Does this collection contain any value definitions?
def empty?
@values.empty?
end
def initialize
# We often look values up by name, so a hash makes more sense.
@values = {}
# However, we want to retain the ability to match values in order,
# but we always prefer directly equality (i.e., strings) over regex matches.
@regexes = []
@strings = []
end
# Can we match a given value?
def match?(test_value)
# First look for normal values
if value = @strings.find { |v| v.match?(test_value) }
return value
end
# Then look for a regex match
@regexes.find { |v| v.match?(test_value) }
end
# If the specified value is allowed, then munge appropriately.
def munge(value)
return value if empty?
if instance = match?(value)
if instance.regex?
return value
else
return instance.name
end
else
return value
end
end
# Define a new valid value for a property. You must provide the value itself,
# usually as a symbol, or a regex to match the value.
#
# The first argument to the method is either the value itself or a regex.
# The second argument is an option hash; valid options are:
# * :event: The event that should be returned when this value is set.
# * :call: When to call any associated block. The default value
# is ``instead``, which means to call the value instead of calling the
# provider. You can also specify ``before`` or ``after``, which will
# call both the block and the provider, according to the order you specify
# (the ``first`` refers to when the block is called, not the provider).
def newvalue(name, options = {}, &block)
value = Value.new(name)
@values[value.name] = value
if value.regex?
@regexes << value
else
@strings << value
end
options.each { |opt, arg| value.send(opt.to_s + "=", arg) }
if block_given?
value.block = block
else
value.call = options[:call] || :none
end
if block_given? and ! value.regex?
value.method ||= "set_" + value.name.to_s
end
value
end
# Define one or more new values for our parameter.
def newvalues(*names)
names.each { |name| newvalue(name) }
end
def regexes
@regexes.collect { |r| r.name.inspect }
end
# Verify that the passed value is valid.
def validate(value)
return if empty?
unless @values.detect { |name, v| v.match?(value) }
str = "Invalid value %s. " % [value.inspect]
unless values.empty?
str += "Valid values are %s. " % values.join(", ")
end
unless regexes.empty?
str += "Valid values match %s." % regexes.join(", ")
end
raise ArgumentError, str
end
end
# Return a single value instance.
def value(name)
@values[name]
end
# Return the list of valid values.
def values
@strings.collect { |s| s.name }
end
end
class << self
include Puppet::Util
include Puppet::Util::Docs
attr_reader :validater, :munger, :name, :default, :required_features, :value_collection
attr_accessor :metaparam
# Define the default value for a given parameter or parameter. This
# means that 'nil' is an invalid default value. This defines
# the 'default' instance method.
def defaultto(value = nil, &block)
if block
define_method(:default, &block)
else
if value.nil?
raise Puppet::DevError,
"Either a default value or block must be provided"
end
define_method(:default) do value end
end
end
# Return a documentation string. If there are valid values,
# then tack them onto the string.
def doc
@doc ||= ""
unless defined? @addeddocvals
@doc += value_collection.doc
if f = self.required_features
@doc += " Requires features %s." % f.flatten.collect { |f| f.to_s }.join(" ")
end
@addeddocvals = true
end
@doc
end
def nodefault
if public_method_defined? :default
undef_method :default
end
end
# Store documentation for this parameter.
def desc(str)
@doc = str
end
def initvars
@value_collection = ValueCollection.new
end
# This is how we munge the value. Basically, this is our
# opportunity to convert the value from one form into another.
def munge(&block)
# I need to wrap the unsafe version in begin/rescue parameterments,
# but if I directly call the block then it gets bound to the
# class's context, not the instance's, thus the two methods,
# instead of just one.
define_method(:unsafe_munge, &block)
end
+ # Does the parameter supports reverse munge?
+ # This will be called when something wants to access the parameter
+ # in a canonical form different to what the storage form is.
+ def unmunge(&block)
+ define_method(:unmunge, &block)
+ end
+
# Mark whether we're the namevar.
def isnamevar
@isnamevar = true
@required = true
end
# Is this parameter the namevar? Defaults to false.
def isnamevar?
if defined? @isnamevar
return @isnamevar
else
return false
end
end
# This parameter is required.
def isrequired
@required = true
end
# Specify features that are required for this parameter to work.
def required_features=(*args)
@required_features = args.flatten.collect { |a| a.to_s.downcase.intern }
end
# Is this parameter required? Defaults to false.
def required?
if defined? @required
return @required
else
return false
end
end
# Verify that we got a good value
def validate(&block)
define_method(:unsafe_validate, &block)
end
# Define a new value for our parameter.
def newvalues(*names)
@value_collection.newvalues(*names)
end
def aliasvalue(name, other)
@value_collection.aliasvalue(name, other)
end
end
# Just a simple method to proxy instance methods to class methods
def self.proxymethods(*values)
values.each { |val|
define_method(val) do
self.class.send(val)
end
}
end
# And then define one of these proxies for each method in our
# ParamHandler class.
proxymethods("required?", "isnamevar?")
attr_accessor :resource
# LAK 2007-05-09: Keep the @parent around for backward compatibility.
attr_accessor :parent
def devfail(msg)
self.fail(Puppet::DevError, msg)
end
def expirer
resource.catalog
end
def fail(*args)
type = nil
if args[0].is_a?(Class)
type = args.shift
else
type = Puppet::Error
end
error = type.new(args.join(" "))
if defined? @resource and @resource and @resource.line
error.line = @resource.line
end
if defined? @resource and @resource and @resource.file
error.file = @resource.file
end
raise error
end
# Basic parameter initialization.
def initialize(options = {})
options = symbolize_options(options)
if resource = options[:resource]
self.resource = resource
options.delete(:resource)
else
raise Puppet::DevError, "No resource set for %s" % self.class.name
end
set_options(options)
end
# Log a message using the resource's log level.
def log(msg)
unless @resource[:loglevel]
self.devfail "Parent %s has no loglevel" %
@resource.name
end
Puppet::Util::Log.create(
:level => @resource[:loglevel],
:message => msg,
:source => self
)
end
# Is this parameter a metaparam?
def metaparam?
self.class.metaparam
end
# each parameter class must define the name() method, and parameter
# instances do not change that name this implicitly means that a given
# object can only have one parameter instance of a given parameter
# class
def name
return self.class.name
end
# for testing whether we should actually do anything
def noop
unless defined? @noop
@noop = false
end
tmp = @noop || self.resource.noop || Puppet[:noop] || false
#debug "noop is %s" % tmp
return tmp
end
# return the full path to us, for logging and rollback; not currently
# used
def pathbuilder
if defined? @resource and @resource
return [@resource.pathbuilder, self.name]
else
return [self.name]
end
end
# If the specified value is allowed, then munge appropriately.
# If the developer uses a 'munge' hook, this method will get overridden.
def unsafe_munge(value)
self.class.value_collection.munge(value)
end
+ # no unmunge by default
+ def unmunge(value)
+ value
+ end
+
# A wrapper around our munging that makes sure we raise useful exceptions.
def munge(value)
begin
ret = unsafe_munge(value)
rescue Puppet::Error => detail
Puppet.debug "Reraising %s" % detail
raise
rescue => detail
raise Puppet::DevError, "Munging failed for value %s in class %s: %s" % [value.inspect, self.name, detail], detail.backtrace
end
ret
end
# Verify that the passed value is valid.
# If the developer uses a 'validate' hook, this method will get overridden.
def unsafe_validate(value)
self.class.value_collection.validate(value)
end
# A protected validation method that only ever raises useful exceptions.
def validate(value)
begin
unsafe_validate(value)
rescue ArgumentError => detail
fail detail.to_s
rescue Puppet::Error, TypeError
raise
rescue => detail
raise Puppet::DevError, "Validate method failed for class %s: %s" % [self.name, detail], detail.backtrace
end
end
def remove
@resource = nil
end
- attr_reader :value
+ def value
+ unmunge(@value)
+ end
# Store the value provided. All of the checking should possibly be
# late-binding (e.g., users might not exist when the value is assigned
# but might when it is asked for).
def value=(value)
validate(value)
@value = munge(value)
end
# Retrieve the resource's provider. Some types don't have providers, in which
# case we return the resource object itself.
def provider
@resource.provider
end
def to_s
s = "Parameter(%s)" % self.name
end
end
diff --git a/lib/puppet/property.rb b/lib/puppet/property.rb
index 3bb1a4f0c..a144f28d0 100644
--- a/lib/puppet/property.rb
+++ b/lib/puppet/property.rb
@@ -1,507 +1,507 @@
# The virtual base class for properties, which are the self-contained building
# blocks for actually doing work on the system.
require 'puppet'
require 'puppet/parameter'
class Puppet::Property < Puppet::Parameter
# Because 'should' uses an array, we have a special method for handling
# it. We also want to keep copies of the original values, so that
# they can be retrieved and compared later when merging.
attr_reader :shouldorig
attr_writer :noop
class << self
attr_accessor :unmanaged
attr_reader :name
# Return array matching info, defaulting to just matching
# the first value.
def array_matching
unless defined?(@array_matching)
@array_matching = :first
end
@array_matching
end
# Set whether properties should match all values or just the first one.
def array_matching=(value)
value = value.intern if value.is_a?(String)
unless [:first, :all].include?(value)
raise ArgumentError, "Supported values for Property#array_matching are 'first' and 'all'"
end
@array_matching = value
end
def checkable
@checkable = true
end
def uncheckable
@checkable = false
end
def checkable?
if defined? @checkable
return @checkable
else
return true
end
end
end
# Look up a value's name, so we can find options and such.
def self.value_name(name)
if value = value_collection.match?(name)
value.name
end
end
# Retrieve an option set when a value was defined.
def self.value_option(name, option)
if value = value_collection.value(name)
value.send(option)
end
end
# Define a new valid value for a property. You must provide the value itself,
# usually as a symbol, or a regex to match the value.
#
# The first argument to the method is either the value itself or a regex.
# The second argument is an option hash; valid options are:
# * :method: The name of the method to define. Defaults to 'set_'.
# * :required_features: A list of features this value requires.
# * :event: The event that should be returned when this value is set.
# * :call: When to call any associated block. The default value
# is ``instead``, which means to call the value instead of calling the
# provider. You can also specify ``before`` or ``after``, which will
# call both the block and the provider, according to the order you specify
# (the ``first`` refers to when the block is called, not the provider).
def self.newvalue(name, options = {}, &block)
value = value_collection.newvalue(name, options, &block)
if value.method and value.block
define_method(value.method, &value.block)
end
value
end
# Call the provider method.
def call_provider(value)
begin
provider.send(self.class.name.to_s + "=", value)
rescue NoMethodError
self.fail "The %s provider can not handle attribute %s" %
[provider.class.name, self.class.name]
end
end
# Call the dynamically-created method associated with our value, if
# there is one.
def call_valuemethod(name, value)
event = nil
if method = self.class.value_option(name, :method) and self.respond_to?(method)
#self.debug "setting %s (currently %s)" % [value, self.retrieve]
begin
event = self.send(method)
rescue Puppet::Error
raise
rescue => detail
if Puppet[:trace]
puts detail.backtrace
end
error = Puppet::Error.new("Could not set %s on %s: %s" %
[value, self.class.name, detail], @resource.line, @resource.file)
error.set_backtrace detail.backtrace
raise error
end
elsif block = self.class.value_option(name, :block)
# FIXME It'd be better here to define a method, so that
# the blocks could return values.
event = self.instance_eval(&block)
else
devfail "Could not find method for value '%s'" % name
end
return event, name
end
# How should a property change be printed as a string?
def change_to_s(currentvalue, newvalue)
begin
if currentvalue == :absent
return "defined '%s' as '%s'" %
[self.name, self.should_to_s(newvalue)]
elsif newvalue == :absent or newvalue == [:absent]
return "undefined %s from '%s'" %
[self.name, self.is_to_s(currentvalue)]
else
return "%s changed '%s' to '%s'" %
[self.name, self.is_to_s(currentvalue), self.should_to_s(newvalue)]
end
rescue Puppet::Error, Puppet::DevError
raise
rescue => detail
raise Puppet::DevError, "Could not convert change %s to string: %s" %
[self.name, detail]
end
end
# Figure out which event to return.
def event(name, event = nil)
if value_event = self.class.value_option(name, :event)
return value_event
end
if event and event.is_a?(Symbol)
if event == :nochange
return nil
else
return event
end
end
if self.class.name == :ensure
event = case self.should
when :present; (@resource.class.name.to_s + "_created").intern
when :absent; (@resource.class.name.to_s + "_removed").intern
else
(@resource.class.name.to_s + "_changed").intern
end
else
event = (@resource.class.name.to_s + "_changed").intern
end
return event
end
attr_reader :shadow
# initialize our property
def initialize(hash = {})
super
if ! self.metaparam? and klass = Puppet::Type.metaparamclass(self.class.name)
setup_shadow(klass)
end
end
# Determine whether the property is in-sync or not. If @should is
# not defined or is set to a non-true value, then we do not have
# a valid value for it and thus consider the property to be in-sync
# since we cannot fix it. Otherwise, we expect our should value
# to be an array, and if @is matches any of those values, then
# we consider it to be in-sync.
def insync?(is)
#debug "%s value is '%s', should be '%s'" %
# [self,self.is.inspect,self.should.inspect]
unless defined? @should and @should
return true
end
unless @should.is_a?(Array)
self.devfail "%s's should is not array" % self.class.name
end
# an empty array is analogous to no should values
if @should.empty?
return true
end
# Look for a matching value
if match_all?
return (is == @should or is == @should.collect { |v| v.to_s })
else
@should.each { |val|
if is == val or is == val.to_s
return true
end
}
end
# otherwise, return false
return false
end
# because the @should and @is vars might be in weird formats,
# we need to set up a mechanism for pretty printing of the values
# default to just the values, but this way individual properties can
# override these methods
def is_to_s(currentvalue)
currentvalue
end
# Send a log message.
def log(msg)
unless @resource[:loglevel]
self.devfail "Parent %s has no loglevel" % @resource.name
end
Puppet::Util::Log.create(
:level => @resource[:loglevel],
:message => msg,
:source => self
)
end
# Should we match all values, or just the first?
def match_all?
self.class.array_matching == :all
end
# Execute our shadow's munge code, too, if we have one.
def munge(value)
self.shadow.munge(value) if self.shadow
super
end
# each property class must define the name() method, and property instances
# do not change that name
# this implicitly means that a given object can only have one property
# instance of a given property class
def name
return self.class.name
end
# for testing whether we should actually do anything
def noop
# This is only here to make testing easier.
if @resource.respond_to?(:noop?)
@resource.noop?
else
if defined?(@noop)
@noop
else
Puppet[:noop]
end
end
end
# By default, call the method associated with the property name on our
# provider. In other words, if the property name is 'gid', we'll call
# 'provider.gid' to retrieve the current value.
def retrieve
provider.send(self.class.name)
end
# Set our value, using the provider, an associated block, or both.
def set(value)
# Set a name for looking up associated options like the event.
name = self.class.value_name(value)
call = self.class.value_option(name, :call) || :none
if call == :instead
event, tmp = call_valuemethod(name, value)
elsif call == :none
if @resource.provider
call_provider(value)
else
# They haven't provided a block, and our parent does not have
# a provider, so we have no idea how to handle this.
self.fail "%s cannot handle values of type %s" % [self.class.name, value.inspect]
end
else
# LAK:NOTE 20081031 This is a change in behaviour -- you could
# previously specify :call => [;before|:after], which would call
# the setter *in addition to* the block. I'm convinced this
# was never used, and it makes things unecessarily complicated.
# If you want to specify a block and still call the setter, then
# do so in the block.
devfail "Cannot use obsolete :call value '%s' for property '%s'" % [call, self.class.name]
end
return event(name, event)
end
# If there's a shadowing metaparam, instantiate it now.
# This allows us to create a property or parameter with the
# same name as a metaparameter, and the metaparam will only be
# stored as a shadow.
def setup_shadow(klass)
@shadow = klass.new(:resource => self.resource)
end
# Only return the first value
def should
if defined? @should
unless @should.is_a?(Array)
self.devfail "should for %s on %s is not an array" %
[self.class.name, @resource.name]
end
if match_all?
- return @should
+ return @should.collect { |val| self.unmunge(val) }
else
- return @should[0]
+ return self.unmunge(@should[0])
end
else
return nil
end
end
# Set the should value.
def should=(values)
unless values.is_a?(Array)
values = [values]
end
@shouldorig = values
values.each { |val| validate(val) }
@should = values.collect { |val| self.munge(val) }
end
def should_to_s(newvalue)
newvalue = [newvalue] unless newvalue.is_a? Array
if defined? newvalue
newvalue.join(" ")
else
return nil
end
end
def sync
if value = self.should
set(value)
else
self.devfail "Got a nil value for should"
end
end
# The properties need to return tags so that logs correctly collect them.
def tags
unless defined? @tags
@tags = []
# This might not be true in testing
if @resource.respond_to? :tags
@tags = @resource.tags
end
@tags << self.name.to_s
end
@tags
end
def to_s
return "%s(%s)" % [@resource.name,self.name]
end
# Verify that the passed value is valid.
# If the developer uses a 'validate' hook, this method will get overridden.
def unsafe_validate(value)
super
validate_features_per_value(value)
end
# Make sure that we've got all of the required features for a given value.
def validate_features_per_value(value)
if features = self.class.value_option(self.class.value_name(value), :required_features)
raise ArgumentError, "Provider must have features '%s' to set '%s' to '%s'" % [features.collect { |f| f.to_s }.join(", "), self.class.name, value] unless provider.satisfies?(features)
end
end
# Just return any should value we might have.
def value
self.should
end
# Match the Parameter interface, but we really just use 'should' internally.
# Note that the should= method does all of the validation and such.
def value=(value)
self.should = value
end
# This property will get automatically added to any type that responds
# to the methods 'exists?', 'create', and 'destroy'.
class Ensure < Puppet::Property
@name = :ensure
def self.defaultvalues
newvalue(:present) do
if @resource.provider and @resource.provider.respond_to?(:create)
@resource.provider.create
else
@resource.create
end
nil # return nil so the event is autogenerated
end
newvalue(:absent) do
if @resource.provider and @resource.provider.respond_to?(:destroy)
@resource.provider.destroy
else
@resource.destroy
end
nil # return nil so the event is autogenerated
end
defaultto do
if @resource.managed?
:present
else
nil
end
end
# This doc will probably get overridden
@doc ||= "The basic property that the resource should be in."
end
def self.inherited(sub)
# Add in the two properties that everyone will have.
sub.class_eval do
end
end
def change_to_s(currentvalue, newvalue)
begin
if currentvalue == :absent or currentvalue.nil?
return "created"
elsif newvalue == :absent
return "removed"
else
return "%s changed '%s' to '%s'" %
[self.name, self.is_to_s(currentvalue), self.should_to_s(newvalue)]
end
rescue Puppet::Error, Puppet::DevError
raise
rescue => detail
raise Puppet::DevError, "Could not convert change %s to string: %s" %
[self.name, detail]
end
end
def retrieve
# XXX This is a problem -- whether the object exists or not often
# depends on the results of other properties, yet we're the first property
# to get checked, which means that those other properties do not have
# @is values set. This seems to be the source of quite a few bugs,
# although they're mostly logging bugs, not functional ones.
if prov = @resource.provider and prov.respond_to?(:exists?)
result = prov.exists?
elsif @resource.respond_to?(:exists?)
result = @resource.exists?
else
raise Puppet::DevError, "No ability to determine if %s exists" %
@resource.class.name
end
if result
return :present
else
return :absent
end
end
# If they're talking about the thing at all, they generally want to
# say it should exist.
#defaultto :present
defaultto do
if @resource.managed?
:present
else
nil
end
end
end
end
diff --git a/spec/unit/parameter.rb b/spec/unit/parameter.rb
index 94f5cfd7b..42d3103bd 100755
--- a/spec/unit/parameter.rb
+++ b/spec/unit/parameter.rb
@@ -1,373 +1,383 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet/parameter'
describe Puppet::Parameter do
before do
@class = Class.new(Puppet::Parameter)
@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 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
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
end
describe Puppet::Parameter::ValueCollection do
before do
@collection = Puppet::Parameter::ValueCollection.new
end
it "should have a method for defining new values" do
@collection.should respond_to(:newvalues)
end
it "should have a method for adding individual values" do
@collection.should respond_to(:newvalue)
end
it "should be able to retrieve individual values" do
value = @collection.newvalue(:foo)
@collection.value(:foo).should equal(value)
end
it "should be able to add an individual value with a block" do
@collection.newvalue(:foo) { raise "testing" }
@collection.value(:foo).block.should be_instance_of(Proc)
end
it "should be able to add values that are empty strings" do
lambda { @collection.newvalue('') }.should_not raise_error
end
it "should be able to add values that are empty strings" do
value = @collection.newvalue('')
@collection.match?('').should equal(value)
end
it "should set :call to :none when adding a value with no block" do
value = @collection.newvalue(:foo)
value.call.should == :none
end
describe "when adding a value with a block" do
it "should set the method name to 'set_' plus the value name" do
value = @collection.newvalue(:myval) { raise "testing" }
value.method.should == "set_myval"
end
end
it "should be able to add an individual value with options" do
value = @collection.newvalue(:foo, :call => :bar)
value.call.should == :bar
end
it "should have a method for validating a value" do
@collection.should respond_to(:validate)
end
it "should have a method for munging a value" do
@collection.should respond_to(:munge)
end
it "should be able to generate documentation when it has both values and regexes" do
@collection.newvalues :foo, "bar", %r{test}
@collection.doc.should be_instance_of(String)
end
it "should correctly generate documentation for values" do
@collection.newvalues :foo
@collection.doc.should be_include("Valid values are ``foo``")
end
it "should correctly generate documentation for regexes" do
@collection.newvalues %r{\w+}
@collection.doc.should be_include("Values can match ``/\\w+/``")
end
it "should be able to find the first matching value" do
@collection.newvalues :foo, :bar
@collection.match?("foo").should be_instance_of(Puppet::Parameter::ValueCollection::Value)
end
it "should be able to match symbols" do
@collection.newvalues :foo, :bar
@collection.match?(:foo).should be_instance_of(Puppet::Parameter::ValueCollection::Value)
end
it "should be able to match symbols when a regex is provided" do
@collection.newvalues %r{.}
@collection.match?(:foo).should be_instance_of(Puppet::Parameter::ValueCollection::Value)
end
it "should be able to match values using regexes" do
@collection.newvalues %r{.}
@collection.match?("foo").should_not be_nil
end
it "should prefer value matches to regex matches" do
@collection.newvalues %r{.}, :foo
@collection.match?("foo").name.should == :foo
end
describe "when validating values" do
it "should do nothing if no values or regexes have been defined" do
@collection.validate("foo")
end
it "should fail if the value is not a defined value or alias and does not match a regex" do
@collection.newvalues :foo
lambda { @collection.validate("bar") }.should raise_error(ArgumentError)
end
it "should succeed if the value is one of the defined values" do
@collection.newvalues :foo
lambda { @collection.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
@collection.newvalues :foo
lambda { @collection.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
@collection.newvalues "foo"
lambda { @collection.validate(:foo) }.should_not raise_error(ArgumentError)
end
it "should succeed if the value is one of the defined aliases" do
@collection.newvalues :foo
@collection.aliasvalue :bar, :foo
lambda { @collection.validate("bar") }.should_not raise_error(ArgumentError)
end
it "should succeed if the value matches one of the regexes" do
@collection.newvalues %r{\d}
lambda { @collection.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
@collection.munge("foo").should == "foo"
end
it "should return return any matching defined values" do
@collection.newvalues :foo, :bar
@collection.munge("foo").should == :foo
end
it "should return any matching aliases" do
@collection.newvalues :foo
@collection.aliasvalue :bar, :foo
@collection.munge("bar").should == :foo
end
it "should return the value if it matches a regex" do
@collection.newvalues %r{\w}
@collection.munge("bar").should == "bar"
end
it "should return the value if no other option is matched" do
@collection.newvalues :foo
@collection.munge("bar").should == "bar"
end
end
end
describe Puppet::Parameter::ValueCollection::Value do
it "should require a name" do
lambda { Puppet::Parameter::ValueCollection::Value.new }.should raise_error(ArgumentError)
end
it "should set its name" do
Puppet::Parameter::ValueCollection::Value.new(:foo).name.should == :foo
end
it "should support regexes as names" do
lambda { Puppet::Parameter::ValueCollection::Value.new(%r{foo}) }.should_not raise_error
end
it "should mark itself as a regex if its name is a regex" do
Puppet::Parameter::ValueCollection::Value.new(%r{foo}).should be_regex
end
it "should always convert its name to a symbol if it is not a regex" do
Puppet::Parameter::ValueCollection::Value.new("foo").name.should == :foo
Puppet::Parameter::ValueCollection::Value.new(true).name.should == :true
end
it "should support adding aliases" do
Puppet::Parameter::ValueCollection::Value.new("foo").should respond_to(:alias)
end
it "should be able to return its aliases" do
value = Puppet::Parameter::ValueCollection::Value.new("foo")
value.alias("bar")
value.alias("baz")
value.aliases.should == [:bar, :baz]
end
[:block, :call, :method, :event, :required_features].each do |attr|
it "should support a #{attr} attribute" do
value = Puppet::Parameter::ValueCollection::Value.new("foo")
value.should respond_to(attr.to_s + "=")
value.should respond_to(attr)
end
end
it "should default to :instead for :call if a block is provided" do
Puppet::Parameter::ValueCollection::Value.new("foo").call.should == :instead
end
it "should always return events as symbols" do
value = Puppet::Parameter::ValueCollection::Value.new("foo")
value.event = "foo_test"
value.event.should == :foo_test
end
describe "when matching" do
describe "a regex" do
it "should return true if the regex matches the value" do
Puppet::Parameter::ValueCollection::Value.new(/\w/).should be_match("foo")
end
it "should return false if the regex does not match the value" do
Puppet::Parameter::ValueCollection::Value.new(/\d/).should_not be_match("foo")
end
end
describe "a non-regex" do
it "should return true if the value, converted to a symbol, matches the name" do
Puppet::Parameter::ValueCollection::Value.new("foo").should be_match("foo")
Puppet::Parameter::ValueCollection::Value.new(:foo).should be_match(:foo)
Puppet::Parameter::ValueCollection::Value.new(:foo).should be_match("foo")
Puppet::Parameter::ValueCollection::Value.new("foo").should be_match(:foo)
end
it "should return false if the value, converted to a symbol, does not match the name" do
Puppet::Parameter::ValueCollection::Value.new(:foo).should_not be_match(:bar)
end
it "should return true if any of its aliases match" do
value = Puppet::Parameter::ValueCollection::Value.new("foo")
value.alias("bar")
value.should be_match("bar")
end
end
end
end
diff --git a/spec/unit/property.rb b/spec/unit/property.rb
index e2ba83303..f09549ddc 100755
--- a/spec/unit/property.rb
+++ b/spec/unit/property.rb
@@ -1,278 +1,294 @@
#!/usr/bin/env ruby"
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet/property'
describe Puppet::Property do
before do
@class = Class.new(Puppet::Property) do
@name = :foo
end
@class.initvars
@provider = mock 'provider'
@resource = stub 'resource', :provider => @provider
@resource.stub_everything
@property = @class.new :resource => @resource
end
it "should be able to look up the modified name for a given value" do
@class.newvalue(:foo)
@class.value_name("foo").should == :foo
end
it "should be able to look up the modified name for a given value matching a regex" do
@class.newvalue(%r{.})
@class.value_name("foo").should == %r{.}
end
it "should be able to look up a given value option" do
@class.newvalue(:foo, :event => :whatever)
@class.value_option(:foo, :event).should == :whatever
end
it "should return the resource's tags plus its name as its tags" do
@resource.expects(:tags).returns %w{one two}
@property.tags.should == %w{one two foo}
end
it "should be able to specify required features" do
@class.should respond_to(:required_features=)
end
it "should always convert required features into an array of symbols" do
@class.required_features = %w{one two}
@class.required_features.should == [:one, :two]
end
it "should be able to shadow metaparameters" do
@property.must respond_to(:shadow)
end
describe "when shadowing metaparameters" do
before do
@shadow_class = Class.new(Puppet::Property) do
@name = :alias
end
@shadow_class.initvars
end
it "should create an instance of the metaparameter at initialization" do
Puppet::Type.metaparamclass(:alias).expects(:new).with(:resource => @resource)
@shadow_class.new :resource => @resource
end
it "should munge values using the shadow's munge method" do
shadow = mock 'shadow'
Puppet::Type.metaparamclass(:alias).expects(:new).returns shadow
shadow.expects(:munge).with "foo"
property = @shadow_class.new :resource => @resource
property.munge("foo")
end
end
describe "when defining new values" do
it "should define a method for each value created with a block that's not a regex" do
@class.newvalue(:foo) { }
@property.must respond_to(:set_foo)
end
end
describe "when assigning the value" do
it "should just set the 'should' value" do
@property.value = "foo"
@property.should.must == "foo"
end
it "should validate each value separately" do
@property.expects(:validate).with("one")
@property.expects(:validate).with("two")
@property.value = %w{one two}
end
it "should munge each value separately and use any result as the actual value" do
@property.expects(:munge).with("one").returns :one
@property.expects(:munge).with("two").returns :two
# Do this so we get the whole array back.
@class.array_matching = :all
@property.value = %w{one two}
@property.should.must == [:one, :two]
end
it "should return any set value" do
(@property.value = :one).should == :one
end
end
describe "when returning the value" do
it "should return nil if no value is set" do
@property.should.must be_nil
end
it "should return the first set 'should' value if :array_matching is set to :first" do
@class.array_matching = :first
@property.should = %w{one two}
@property.should.must == "one"
end
it "should return all set 'should' values as an array if :array_matching is set to :all" do
@class.array_matching = :all
@property.should = %w{one two}
@property.should.must == %w{one two}
end
it "should default to :first array_matching" do
@class.array_matching.should == :first
end
+
+ it "should unmunge the returned value if :array_matching is set to :first" do
+ @property.class.unmunge do |v| v.to_sym end
+ @class.array_matching = :first
+ @property.should = %w{one two}
+
+ @property.should.must == :one
+ end
+
+ it "should unmunge all the returned values if :array_matching is set to :all" do
+ @property.class.unmunge do |v| v.to_sym end
+ @class.array_matching = :all
+ @property.should = %w{one two}
+
+ @property.should.must == [:one, :two]
+ end
end
describe "when validating values" do
it "should do nothing if no values or regexes have been defined" do
lambda { @property.should = "foo" }.should_not raise_error
end
it "should fail if the value is not a defined value or alias and does not match a regex" do
@class.newvalue(:foo)
lambda { @property.should = "bar" }.should raise_error
end
it "should succeeed if the value is one of the defined values" do
@class.newvalue(:foo)
lambda { @property.should = :foo }.should_not raise_error
end
it "should succeeed if the value is one of the defined values even if the definition uses a symbol and the validation uses a string" do
@class.newvalue(:foo)
lambda { @property.should = "foo" }.should_not raise_error
end
it "should succeeed if the value is one of the defined values even if the definition uses a string and the validation uses a symbol" do
@class.newvalue("foo")
lambda { @property.should = :foo }.should_not raise_error
end
it "should succeed if the value is one of the defined aliases" do
@class.newvalue("foo")
@class.aliasvalue("bar", "foo")
lambda { @property.should = :bar }.should_not raise_error
end
it "should succeed if the value matches one of the regexes" do
@class.newvalue(/./)
lambda { @property.should = "bar" }.should_not raise_error
end
it "should validate that all required features are present" do
@class.newvalue(:foo, :required_features => [:a, :b])
@provider.expects(:satisfies?).with([:a, :b]).returns true
@property.should = :foo
end
it "should fail if required features are missing" do
@class.newvalue(:foo, :required_features => [:a, :b])
@provider.expects(:satisfies?).with([:a, :b]).returns false
lambda { @property.should = :foo }.should raise_error(Puppet::Error)
end
it "should validate that all required features are present for regexes" do
value = @class.newvalue(/./, :required_features => [:a, :b])
@provider.expects(:satisfies?).with([:a, :b]).returns true
@property.should = "foo"
end
end
describe "when munging values" do
it "should do nothing if no values or regexes have been defined" do
@property.munge("foo").should == "foo"
end
it "should return return any matching defined values" do
@class.newvalue(:foo)
@property.munge("foo").should == :foo
end
it "should return any matching aliases" do
@class.newvalue(:foo)
@class.aliasvalue(:bar, :foo)
@property.munge("bar").should == :foo
end
it "should return the value if it matches a regex" do
@class.newvalue(/./)
@property.munge("bar").should == "bar"
end
it "should return the value if no other option is matched" do
@class.newvalue(:foo)
@property.munge("bar").should == "bar"
end
end
describe "when syncing the 'should' value" do
it "should set the value" do
@class.newvalue(:foo)
@property.should = :foo
@property.expects(:set).with(:foo)
@property.sync
end
end
describe "when setting a value" do
it "should catch exceptions and raise Puppet::Error" do
@class.newvalue(:foo) { raise "eh" }
lambda { @property.set(:foo) }.should raise_error(Puppet::Error)
end
describe "that was defined without a block" do
it "should call the settor on the provider" do
@class.newvalue(:bar)
@provider.expects(:foo=).with :bar
@property.set(:bar)
end
it "should return any specified event" do
@class.newvalue(:bar, :event => :whatever)
@property.should = :bar
@provider.expects(:foo=).with :bar
@property.set(:bar).should == :whatever
end
end
describe "that was defined with a block" do
it "should call the method created for the value if the value is not a regex" do
@class.newvalue(:bar) {}
@property.expects(:set_bar)
@property.set(:bar)
end
it "should call the provided block if the value is a regex" do
@class.newvalue(/./) { self.test }
@property.expects(:test)
@property.set("foo")
end
it "should return any specified event" do
@class.newvalue(:bar, :event => :myevent) {}
@property.expects(:set_bar)
@property.set(:bar).should == :myevent
end
end
end
end