diff --git a/lib/puppet/property.rb b/lib/puppet/property.rb
index 2af4d30b3..37fba664b 100644
--- a/lib/puppet/property.rb
+++ b/lib/puppet/property.rb
@@ -1,371 +1,373 @@
# 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
require 'puppet/property/ensure'
# 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
@array_matching ||= :first
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)
raise ArgumentError, "Supported values for Property#array_matching are 'first' and 'all'" unless [:first, :all].include?(value)
@array_matching = value
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)
define_method(value.method, &value.block) if value.method and value.block
value
end
# Call the provider method.
def call_provider(value)
- provider.send(self.class.name.to_s + "=", value)
- rescue NoMethodError
- self.fail "The #{provider.class.name} provider can not handle attribute #{self.class.name}"
+ method = self.class.name.to_s + "="
+ unless provider.respond_to? method
+ self.fail "The #{provider.class.name} provider can not handle attribute #{self.class.name}"
+ end
+ provider.send(method, value)
end
# Call the dynamically-created method associated with our value, if
# there is one.
def call_valuemethod(name, value)
if method = self.class.value_option(name, :method) and self.respond_to?(method)
begin
event = self.send(method)
rescue Puppet::Error
raise
rescue => detail
puts detail.backtrace if Puppet[:trace]
error = Puppet::Error.new("Could not set '#{value} on #{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.
self.instance_eval(&block)
else
devfail "Could not find method for value '#{name}'"
end
end
# How should a property change be printed as a string?
def change_to_s(current_value, newvalue)
begin
if current_value == :absent
return "defined '#{name}' as #{self.class.format_value_for_display should_to_s(newvalue)}"
elsif newvalue == :absent or newvalue == [:absent]
return "undefined '#{name}' from #{self.class.format_value_for_display is_to_s(current_value)}"
else
return "#{name} changed #{self.class.format_value_for_display is_to_s(current_value)} to #{self.class.format_value_for_display should_to_s(newvalue)}"
end
rescue Puppet::Error, Puppet::DevError
raise
rescue => detail
puts detail.backtrace if Puppet[:trace]
raise Puppet::DevError, "Could not convert change '#{name}' to string: #{detail}"
end
end
# Figure out which event to return.
def event_name
value = self.should
event_name = self.class.value_option(value, :event) and return event_name
name == :ensure or return (name.to_s + "_changed").to_sym
return (resource.type.to_s + case value
when :present; "_created"
when :absent; "_removed"
else
"_changed"
end).to_sym
end
# Return a modified form of the resource event.
def event
resource.event :name => event_name, :desired_value => should, :property => self, :source_description => path
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.
#
# Don't override this method.
def safe_insync?(is)
# If there is no @should value, consider the property to be in sync.
return true unless @should
# Otherwise delegate to the (possibly derived) insync? method.
insync?(is)
end
def self.method_added(sym)
raise "Puppet::Property#safe_insync? shouldn't be overridden; please override insync? instead" if sym == :safe_insync?
end
# This method may be overridden by derived classes if necessary
# to provide extra logic to determine whether the property is in
# sync. In most cases, however, only `property_matches?` needs to be
# overridden to give the correct outcome - without reproducing all the array
# matching logic, etc, found here.
def insync?(is)
self.devfail "#{self.class.name}'s should is not array" unless @should.is_a?(Array)
# an empty array is analogous to no should values
return true if @should.empty?
# Look for a matching value, either for all the @should values, or any of
# them, depending on the configuration of this property.
if match_all? then
# Emulate Array#== using our own comparison function.
# A non-array was not equal to an array, which @should always is.
return false unless is.is_a? Array
# If they were different lengths, they are not equal.
return false unless is.length == @should.length
# Finally, are all the elements equal? In order to preserve the
# behaviour of previous 2.7.x releases, we need to impose some fun rules
# on "equality" here.
#
# Specifically, we need to implement *this* comparison: the two arrays
# are identical if the is values are == the should values, or if the is
# values are == the should values, stringified.
#
# This does mean that property equality is not commutative, and will not
# work unless the `is` value is carefully arranged to match the should.
return (is == @should or is == @should.map(&:to_s))
# When we stop being idiots about this, and actually have meaningful
# semantics, this version is the thing we actually want to do.
#
# return is.zip(@should).all? {|a, b| property_matches?(a, b) }
else
return @should.any? {|want| property_matches?(is, want) }
end
end
# Compare the current and desired value of a property in a property-specific
# way. Invoked by `insync?`; this should be overridden if your property
# has a different comparison type but does not actually differentiate the
# overall insync? logic.
def property_matches?(current, desired)
# This preserves the older Puppet behaviour of doing raw and string
# equality comparisons for all equality. I am not clear this is globally
# desirable, but at least it is not a breaking change. --daniel 2011-11-11
current == desired or current == desired.to_s
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)
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
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
call_valuemethod(name, value)
elsif call == :none
# 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 "#{self.class.name} cannot handle values of type #{value.inspect}" unless @resource.provider
call_provider(value)
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 '#{call}' for property '#{self.class.name}'"
end
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
return nil unless defined?(@should)
self.devfail "should for #{self.class.name} on #{resource.name} is not an array" unless @should.is_a?(Array)
if match_all?
return @should.collect { |val| self.unmunge(val) }
else
return self.unmunge(@should[0])
end
end
# Set the should value.
def should=(values)
values = [values] unless values.is_a?(Array)
@shouldorig = values
values.each { |val| validate(val) }
@should = values.collect { |val| self.munge(val) }
end
def should_to_s(newvalue)
[newvalue].flatten.join(" ")
end
def sync
devfail "Got a nil value for should" unless should
set(should)
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)
features = Array(features)
needed_features = features.collect { |f| f.to_s }.join(", ")
raise ArgumentError, "Provider must have features '#{needed_features}' to set '#{self.class.name}' to '#{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
end
diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb
index 3f25b743b..dc1c07d75 100755
--- a/spec/unit/property_spec.rb
+++ b/spec/unit/property_spec.rb
@@ -1,510 +1,530 @@
#!/usr/bin/env rspec
require 'spec_helper'
require 'puppet/property'
describe Puppet::Property do
let :resource do Puppet::Type.type(:host).new :name => "foo" end
let :subclass do
# We need a completely fresh subclass every time, because we modify both
# class and instance level things inside the tests.
- subclass = Class.new(Puppet::Property) do @name = :foo end
+ subclass = Class.new(Puppet::Property) do
+ class << self
+ attr_accessor :name
+ end
+ @name = :foo
+ end
subclass.initvars
subclass
end
let :property do subclass.new :resource => resource end
it "should be able to look up the modified name for a given value" do
subclass.newvalue(:foo)
subclass.value_name("foo").should == :foo
end
it "should be able to look up the modified name for a given value matching a regex" do
subclass.newvalue(%r{.})
subclass.value_name("foo").should == %r{.}
end
it "should be able to look up a given value option" do
subclass.newvalue(:foo, :event => :whatever)
subclass.value_option(:foo, :event).should == :whatever
end
it "should be able to specify required features" do
subclass.should respond_to(:required_features=)
end
{"one" => [:one],:one => [:one],%w{a} => [:a],[:b] => [:b],%w{one two} => [:one,:two],[:a,:b] => [:a,:b]}.each { |in_value,out_value|
it "should always convert required features into an array of symbols (e.g. #{in_value.inspect} --> #{out_value.inspect})" do
subclass.required_features = in_value
subclass.required_features.should == out_value
end
}
it "should return its name as a string when converted to a string" do
property.to_s.should == property.name.to_s
end
it "should be able to shadow metaparameters" do
property.must respond_to(:shadow)
end
describe "when returning the default event name" do
it "should use the current 'should' value to pick the event name" do
property.expects(:should).returns "myvalue"
subclass.expects(:value_option).with('myvalue', :event).returns :event_name
property.event_name
end
it "should return any event defined with the specified value" do
property.expects(:should).returns :myval
subclass.expects(:value_option).with(:myval, :event).returns :event_name
property.event_name.should == :event_name
end
describe "and the property is 'ensure'" do
before :each do
property.stubs(:name).returns :ensure
resource.expects(:type).returns :mytype
end
it "should use _created if the 'should' value is 'present'" do
property.expects(:should).returns :present
property.event_name.should == :mytype_created
end
it "should use _removed if the 'should' value is 'absent'" do
property.expects(:should).returns :absent
property.event_name.should == :mytype_removed
end
it "should use _changed if the 'should' value is not 'absent' or 'present'" do
property.expects(:should).returns :foo
property.event_name.should == :mytype_changed
end
it "should use _changed if the 'should value is nil" do
property.expects(:should).returns nil
property.event_name.should == :mytype_changed
end
end
it "should use _changed if the property is not 'ensure'" do
property.stubs(:name).returns :myparam
property.expects(:should).returns :foo
property.event_name.should == :myparam_changed
end
it "should use _changed if no 'should' value is set" do
property.stubs(:name).returns :myparam
property.expects(:should).returns nil
property.event_name.should == :myparam_changed
end
end
describe "when creating an event" do
before :each do
property.stubs(:should).returns "myval"
end
it "should use an event from the resource as the base event" do
event = Puppet::Transaction::Event.new
resource.expects(:event).returns event
property.event.should equal(event)
end
it "should have the default event name" do
property.expects(:event_name).returns :my_event
property.event.name.should == :my_event
end
it "should have the property's name" do
property.event.property.should == property.name.to_s
end
it "should have the 'should' value set" do
property.stubs(:should).returns "foo"
property.event.desired_value.should == "foo"
end
it "should provide its path as the source description" do
property.stubs(:path).returns "/my/param"
property.event.source_description.should == "/my/param"
end
end
describe "when shadowing metaparameters" do
let :shadow_class do
shadow_class = Class.new(Puppet::Property) do
@name = :alias
end
shadow_class.initvars
shadow_class
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
subclass.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.
subclass.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
subclass.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
subclass.array_matching = :all
property.should = %w{one two}
property.should.must == %w{one two}
end
it "should default to :first array_matching" do
subclass.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
subclass.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
subclass.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
subclass.newvalue(:foo)
lambda { property.should = "bar" }.should raise_error
end
it "should succeeed if the value is one of the defined values" do
subclass.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
subclass.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
subclass.newvalue("foo")
lambda { property.should = :foo }.should_not raise_error
end
it "should succeed if the value is one of the defined aliases" do
subclass.newvalue("foo")
subclass.aliasvalue("bar", "foo")
lambda { property.should = :bar }.should_not raise_error
end
it "should succeed if the value matches one of the regexes" do
subclass.newvalue(/./)
lambda { property.should = "bar" }.should_not raise_error
end
it "should validate that all required features are present" do
subclass.newvalue(:foo, :required_features => [:a, :b])
resource.provider.expects(:satisfies?).with([:a, :b]).returns true
property.should = :foo
end
it "should fail if required features are missing" do
subclass.newvalue(:foo, :required_features => [:a, :b])
resource.provider.expects(:satisfies?).with([:a, :b]).returns false
lambda { property.should = :foo }.should raise_error(Puppet::Error)
end
it "should internally raise an ArgumentError if required features are missing" do
subclass.newvalue(:foo, :required_features => [:a, :b])
resource.provider.expects(:satisfies?).with([:a, :b]).returns false
lambda { property.validate_features_per_value :foo }.should raise_error(ArgumentError)
end
it "should validate that all required features are present for regexes" do
value = subclass.newvalue(/./, :required_features => [:a, :b])
resource.provider.expects(:satisfies?).with([:a, :b]).returns true
property.should = "foo"
end
it "should support specifying an individual required feature" do
value = subclass.newvalue(/./, :required_features => :a)
resource.provider.expects(:satisfies?).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
subclass.newvalue(:foo)
property.munge("foo").should == :foo
end
it "should return any matching aliases" do
subclass.newvalue(:foo)
subclass.aliasvalue(:bar, :foo)
property.munge("bar").should == :foo
end
it "should return the value if it matches a regex" do
subclass.newvalue(/./)
property.munge("bar").should == "bar"
end
it "should return the value if no other option is matched" do
subclass.newvalue(:foo)
property.munge("bar").should == "bar"
end
end
describe "when syncing the 'should' value" do
it "should set the value" do
subclass.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
subclass.newvalue(:foo) { raise "eh" }
lambda { property.set(:foo) }.should raise_error(Puppet::Error)
end
+ it "fails when the provider does not handle the attribute" do
+ subclass.name = "unknown"
+ lambda { property.set(:a_value) }.should raise_error(Puppet::Error)
+ end
+
+ it "propogates the errors about missing methods from the provider" do
+ provider = resource.provider
+ def provider.bad_method=(value)
+ value.this_method_does_not_exist
+ end
+
+ subclass.name = :bad_method
+ lambda { property.set(:a_value) }.should raise_error(NoMethodError, /this_method_does_not_exist/)
+ end
+
describe "that was defined without a block" do
it "should call the settor on the provider" do
subclass.newvalue(:bar)
resource.provider.expects(:foo=).with :bar
property.set(:bar)
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
subclass.newvalue(:bar) {}
property.expects(:set_bar)
property.set(:bar)
end
it "should call the provided block if the value is a regex" do
subclass.newvalue(/./) { self.test }
property.expects(:test)
property.set("foo")
end
end
end
describe "when producing a change log" do
it "should say 'defined' when the current value is 'absent'" do
property.change_to_s(:absent, "foo").should =~ /^defined/
end
it "should say 'undefined' when the new value is 'absent'" do
property.change_to_s("foo", :absent).should =~ /^undefined/
end
it "should say 'changed' when neither value is 'absent'" do
property.change_to_s("foo", "bar").should =~ /changed/
end
end
shared_examples_for "#insync?" do
# We share a lot of behaviour between the all and first matching, so we
# use a shared behaviour set to emulate that. The outside world makes
# sure the class, etc, point to the right content.
[[], [12], [12, 13]].each do |input|
it "should return true if should is empty with is => #{input.inspect}" do
property.should = []
property.must be_insync(input)
end
end
end
describe "#insync?" do
context "array_matching :all" do
# `@should` is an array of scalar values, and `is` is an array of scalar values.
before :each do
property.class.array_matching = :all
end
it_should_behave_like "#insync?"
context "if the should value is an array" do
before :each do property.should = [1, 2] end
it "should match if is exactly matches" do
property.must be_insync [1, 2]
end
it "should match if it matches, but all stringified" do
property.must be_insync ["1", "2"]
end
it "should not match if some-but-not-all values are stringified" do
property.must_not be_insync ["1", 2]
property.must_not be_insync [1, "2"]
end
it "should not match if order is different but content the same" do
property.must_not be_insync [2, 1]
end
it "should not match if there are more items in should than is" do
property.must_not be_insync [1]
end
it "should not match if there are less items in should than is" do
property.must_not be_insync [1, 2, 3]
end
it "should not match if `is` is empty but `should` isn't" do
property.must_not be_insync []
end
end
end
context "array_matching :first" do
# `@should` is an array of scalar values, and `is` is a scalar value.
before :each do
property.class.array_matching = :first
end
it_should_behave_like "#insync?"
[[1], # only the value
[1, 2], # matching value first
[2, 1], # matching value last
[0, 1, 2], # matching value in the middle
].each do |input|
it "should by true if one unmodified should value of #{input.inspect} matches what is" do
property.should = input
property.must be_insync 1
end
it "should be true if one stringified should value of #{input.inspect} matches what is" do
property.should = input
property.must be_insync "1"
end
end
it "should not match if we expect a string but get the non-stringified value" do
property.should = ["1"]
property.must_not be_insync 1
end
[[0], [0, 2]].each do |input|
it "should not match if no should values match what is" do
property.should = input
property.must_not be_insync 1
property.must_not be_insync "1" # shouldn't match either.
end
end
end
end
describe "#property_matches?" do
[1, "1", [1], :one].each do |input|
it "should treat two equal objects as equal (#{input.inspect})" do
property.property_matches?(input, input).should be_true
end
end
it "should treat two objects as equal if the first argument is the stringified version of the second" do
property.property_matches?("1", 1).should be_true
end
it "should NOT treat two objects as equal if the first argument is not a string, and the second argument is a string, even if it stringifies to the first" do
property.property_matches?(1, "1").should be_false
end
end
end
diff --git a/spec/unit/type/host_spec.rb b/spec/unit/type/host_spec.rb
index 3cbe56943..1b24ac8f0 100755
--- a/spec/unit/type/host_spec.rb
+++ b/spec/unit/type/host_spec.rb
@@ -1,656 +1,665 @@
#!/usr/bin/env rspec
require 'spec_helper'
host = Puppet::Type.type(:host)
describe host do
+ FakeHostProvider = Struct.new(:ip, :host_aliases, :comment)
before do
@class = host
@catalog = Puppet::Resource::Catalog.new
- @provider = stub 'provider'
+ @provider = FakeHostProvider.new
@resource = stub 'resource', :resource => nil, :provider => @provider
end
it "should have :name be its namevar" do
@class.key_attributes.should == [:name]
end
describe "when validating attributes" do
[:name, :provider ].each do |param|
it "should have a #{param} parameter" do
@class.attrtype(param).should == :param
end
end
[:ip, :target, :host_aliases, :comment, :ensure].each do |property|
it "should have a #{property} property" do
@class.attrtype(property).should == :property
end
end
it "should have a list host_aliases" do
@class.attrclass(:host_aliases).ancestors.should be_include(Puppet::Property::OrderedList)
end
end
describe "when validating values" do
it "should support present as a value for ensure" do
proc { @class.new(:name => "foo", :ensure => :present) }.should_not raise_error
end
it "should support absent as a value for ensure" do
proc { @class.new(:name => "foo", :ensure => :absent) }.should_not raise_error
end
it "should accept IPv4 addresses" do
proc { @class.new(:name => "foo", :ip => '10.96.0.1') }.should_not raise_error
end
it "should accept long IPv6 addresses" do
# Taken from wikipedia article about ipv6
proc { @class.new(:name => "foo", :ip => '2001:0db8:85a3:08d3:1319:8a2e:0370:7344') }.should_not raise_error
end
it "should accept one host_alias" do
proc { @class.new(:name => "foo", :host_aliases => 'alias1') }.should_not raise_error
end
it "should accept multiple host_aliases" do
proc { @class.new(:name => "foo", :host_aliases => [ 'alias1', 'alias2' ]) }.should_not raise_error
end
it "should accept shortened IPv6 addresses" do
proc { @class.new(:name => "foo", :ip => '2001:db8:0:8d3:0:8a2e:70:7344') }.should_not raise_error
proc { @class.new(:name => "foo", :ip => '::ffff:192.0.2.128') }.should_not raise_error
proc { @class.new(:name => "foo", :ip => '::1') }.should_not raise_error
end
it "should not accept malformed IPv4 addresses like 192.168.0.300" do
proc { @class.new(:name => "foo", :ip => '192.168.0.300') }.should raise_error
end
it "should reject over-long IPv4 addresses" do
expect { @class.new(:name => "foo", :ip => '10.10.10.10.10') }.to raise_error
end
it "should not accept malformed IP addresses like 2001:0dg8:85a3:08d3:1319:8a2e:0370:7344" do
proc { @class.new(:name => "foo", :ip => '2001:0dg8:85a3:08d3:1319:8a2e:0370:7344') }.should raise_error
end
# Assorted, annotated IPv6 passes.
["::1", # loopback, compressed, non-routable
"::", # unspecified, compressed, non-routable
"0:0:0:0:0:0:0:1", # loopback, full
"0:0:0:0:0:0:0:0", # unspecified, full
"2001:DB8:0:0:8:800:200C:417A", # unicast, full
"FF01:0:0:0:0:0:0:101", # multicast, full
"2001:DB8::8:800:200C:417A", # unicast, compressed
"FF01::101", # multicast, compressed
# Some more test cases that should pass.
"2001:0000:1234:0000:0000:C1C0:ABCD:0876",
"3ffe:0b00:0000:0000:0001:0000:0000:000a",
"FF02:0000:0000:0000:0000:0000:0000:0001",
"0000:0000:0000:0000:0000:0000:0000:0001",
"0000:0000:0000:0000:0000:0000:0000:0000",
# Assorted valid, compressed IPv6 addresses.
"2::10",
"ff02::1",
"fe80::",
"2002::",
"2001:db8::",
"2001:0db8:1234::",
"::ffff:0:0",
"::1",
"1:2:3:4:5:6:7:8",
"1:2:3:4:5:6::8",
"1:2:3:4:5::8",
"1:2:3:4::8",
"1:2:3::8",
"1:2::8",
"1::8",
"1::2:3:4:5:6:7",
"1::2:3:4:5:6",
"1::2:3:4:5",
"1::2:3:4",
"1::2:3",
"1::8",
"::2:3:4:5:6:7:8",
"::2:3:4:5:6:7",
"::2:3:4:5:6",
"::2:3:4:5",
"::2:3:4",
"::2:3",
"::8",
"1:2:3:4:5:6::",
"1:2:3:4:5::",
"1:2:3:4::",
"1:2:3::",
"1:2::",
"1::",
"1:2:3:4:5::7:8",
"1:2:3:4::7:8",
"1:2:3::7:8",
"1:2::7:8",
"1::7:8",
# IPv4 addresses as dotted-quads
"1:2:3:4:5:6:1.2.3.4",
"1:2:3:4:5::1.2.3.4",
"1:2:3:4::1.2.3.4",
"1:2:3::1.2.3.4",
"1:2::1.2.3.4",
"1::1.2.3.4",
"1:2:3:4::5:1.2.3.4",
"1:2:3::5:1.2.3.4",
"1:2::5:1.2.3.4",
"1::5:1.2.3.4",
"1::5:11.22.33.44",
"fe80::217:f2ff:254.7.237.98",
"::ffff:192.168.1.26",
"::ffff:192.168.1.1",
"0:0:0:0:0:0:13.1.68.3", # IPv4-compatible IPv6 address, full, deprecated
"0:0:0:0:0:FFFF:129.144.52.38", # IPv4-mapped IPv6 address, full
"::13.1.68.3", # IPv4-compatible IPv6 address, compressed, deprecated
"::FFFF:129.144.52.38", # IPv4-mapped IPv6 address, compressed
"fe80:0:0:0:204:61ff:254.157.241.86",
"fe80::204:61ff:254.157.241.86",
"::ffff:12.34.56.78",
"::ffff:192.0.2.128", # this is OK, since there's a single zero digit in IPv4
"fe80:0000:0000:0000:0204:61ff:fe9d:f156",
"fe80:0:0:0:204:61ff:fe9d:f156",
"fe80::204:61ff:fe9d:f156",
"::1",
"fe80::",
"fe80::1",
"::ffff:c000:280",
# Additional test cases from http://rt.cpan.org/Public/Bug/Display.html?id=50693
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
"2001:db8:85a3:0:0:8a2e:370:7334",
"2001:db8:85a3::8a2e:370:7334",
"2001:0db8:0000:0000:0000:0000:1428:57ab",
"2001:0db8:0000:0000:0000::1428:57ab",
"2001:0db8:0:0:0:0:1428:57ab",
"2001:0db8:0:0::1428:57ab",
"2001:0db8::1428:57ab",
"2001:db8::1428:57ab",
"0000:0000:0000:0000:0000:0000:0000:0001",
"::1",
"::ffff:0c22:384e",
"2001:0db8:1234:0000:0000:0000:0000:0000",
"2001:0db8:1234:ffff:ffff:ffff:ffff:ffff",
"2001:db8:a::123",
"fe80::",
"1111:2222:3333:4444:5555:6666:7777:8888",
"1111:2222:3333:4444:5555:6666:7777::",
"1111:2222:3333:4444:5555:6666::",
"1111:2222:3333:4444:5555::",
"1111:2222:3333:4444::",
"1111:2222:3333::",
"1111:2222::",
"1111::",
"1111:2222:3333:4444:5555:6666::8888",
"1111:2222:3333:4444:5555::8888",
"1111:2222:3333:4444::8888",
"1111:2222:3333::8888",
"1111:2222::8888",
"1111::8888",
"::8888",
"1111:2222:3333:4444:5555::7777:8888",
"1111:2222:3333:4444::7777:8888",
"1111:2222:3333::7777:8888",
"1111:2222::7777:8888",
"1111::7777:8888",
"::7777:8888",
"1111:2222:3333:4444::6666:7777:8888",
"1111:2222:3333::6666:7777:8888",
"1111:2222::6666:7777:8888",
"1111::6666:7777:8888",
"::6666:7777:8888",
"1111:2222:3333::5555:6666:7777:8888",
"1111:2222::5555:6666:7777:8888",
"1111::5555:6666:7777:8888",
"::5555:6666:7777:8888",
"1111:2222::4444:5555:6666:7777:8888",
"1111::4444:5555:6666:7777:8888",
"::4444:5555:6666:7777:8888",
"1111::3333:4444:5555:6666:7777:8888",
"::3333:4444:5555:6666:7777:8888",
"::2222:3333:4444:5555:6666:7777:8888",
"1111:2222:3333:4444:5555:6666:123.123.123.123",
"1111:2222:3333:4444:5555::123.123.123.123",
"1111:2222:3333:4444::123.123.123.123",
"1111:2222:3333::123.123.123.123",
"1111:2222::123.123.123.123",
"1111::123.123.123.123",
"::123.123.123.123",
"1111:2222:3333:4444::6666:123.123.123.123",
"1111:2222:3333::6666:123.123.123.123",
"1111:2222::6666:123.123.123.123",
"1111::6666:123.123.123.123",
"::6666:123.123.123.123",
"1111:2222:3333::5555:6666:123.123.123.123",
"1111:2222::5555:6666:123.123.123.123",
"1111::5555:6666:123.123.123.123",
"::5555:6666:123.123.123.123",
"1111:2222::4444:5555:6666:123.123.123.123",
"1111::4444:5555:6666:123.123.123.123",
"::4444:5555:6666:123.123.123.123",
"1111::3333:4444:5555:6666:123.123.123.123",
"::2222:3333:4444:5555:6666:123.123.123.123",
# Playing with combinations of "0" and "::"; these are all sytactically
# correct, but are bad form because "0" adjacent to "::" should be
# combined into "::"
"::0:0:0:0:0:0:0",
"::0:0:0:0:0:0",
"::0:0:0:0:0",
"::0:0:0:0",
"::0:0:0",
"::0:0",
"::0",
"0:0:0:0:0:0:0::",
"0:0:0:0:0:0::",
"0:0:0:0:0::",
"0:0:0:0::",
"0:0:0::",
"0:0::",
"0::",
# Additional cases: http://crisp.tweakblogs.net/blog/2031/ipv6-validation-%28and-caveats%29.html
"0:a:b:c:d:e:f::",
"::0:a:b:c:d:e:f", # syntactically correct, but bad form (::0:... could be combined)
"a:b:c:d:e:f:0::",
].each do |ip|
it "should accept #{ip.inspect} as an IPv6 address" do
expect { @class.new(:name => "foo", :ip => ip) }.not_to raise_error
end
end
# ...aaaand, some failure cases.
[":",
"02001:0000:1234:0000:0000:C1C0:ABCD:0876", # extra 0 not allowed!
"2001:0000:1234:0000:00001:C1C0:ABCD:0876", # extra 0 not allowed!
"2001:0000:1234:0000:0000:C1C0:ABCD:0876 0", # junk after valid address
"2001:0000:1234: 0000:0000:C1C0:ABCD:0876", # internal space
"3ffe:0b00:0000:0001:0000:0000:000a", # seven segments
"FF02:0000:0000:0000:0000:0000:0000:0000:0001", # nine segments
"3ffe:b00::1::a", # double "::"
"::1111:2222:3333:4444:5555:6666::", # double "::"
"1:2:3::4:5::7:8", # Double "::"
"12345::6:7:8",
# IPv4 embedded, but bad...
"1::5:400.2.3.4", "1::5:260.2.3.4", "1::5:256.2.3.4", "1::5:1.256.3.4",
"1::5:1.2.256.4", "1::5:1.2.3.256", "1::5:300.2.3.4", "1::5:1.300.3.4",
"1::5:1.2.300.4", "1::5:1.2.3.300", "1::5:900.2.3.4", "1::5:1.900.3.4",
"1::5:1.2.900.4", "1::5:1.2.3.900", "1::5:300.300.300.300", "1::5:3000.30.30.30",
"1::400.2.3.4", "1::260.2.3.4", "1::256.2.3.4", "1::1.256.3.4",
"1::1.2.256.4", "1::1.2.3.256", "1::300.2.3.4", "1::1.300.3.4",
"1::1.2.300.4", "1::1.2.3.300", "1::900.2.3.4", "1::1.900.3.4",
"1::1.2.900.4", "1::1.2.3.900", "1::300.300.300.300", "1::3000.30.30.30",
"::400.2.3.4", "::260.2.3.4", "::256.2.3.4", "::1.256.3.4",
"::1.2.256.4", "::1.2.3.256", "::300.2.3.4", "::1.300.3.4",
"::1.2.300.4", "::1.2.3.300", "::900.2.3.4", "::1.900.3.4",
"::1.2.900.4", "::1.2.3.900", "::300.300.300.300", "::3000.30.30.30",
"2001:1:1:1:1:1:255Z255X255Y255", # garbage instead of "." in IPv4
"::ffff:192x168.1.26", # ditto
"::ffff:2.3.4",
"::ffff:257.1.2.3",
"1.2.3.4:1111:2222:3333:4444::5555",
"1.2.3.4:1111:2222:3333::5555",
"1.2.3.4:1111:2222::5555",
"1.2.3.4:1111::5555",
"1.2.3.4::5555",
"1.2.3.4::",
# Testing IPv4 addresses represented as dotted-quads Leading zero's in
# IPv4 addresses not allowed: some systems treat the leading "0" in
# ".086" as the start of an octal number Update: The BNF in RFC-3986
# explicitly defines the dec-octet (for IPv4 addresses) not to have a
# leading zero
"fe80:0000:0000:0000:0204:61ff:254.157.241.086",
"XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4",
"1111:2222:3333:4444:5555:6666:00.00.00.00",
"1111:2222:3333:4444:5555:6666:000.000.000.000",
"1111:2222:3333:4444:5555:6666:256.256.256.256",
"1111:2222:3333:4444::5555:",
"1111:2222:3333::5555:",
"1111:2222::5555:",
"1111::5555:",
"::5555:",
":::",
"1111:",
":",
":1111:2222:3333:4444::5555",
":1111:2222:3333::5555",
":1111:2222::5555",
":1111::5555",
":::5555",
":::",
# Additional test cases from http://rt.cpan.org/Public/Bug/Display.html?id=50693
"123",
"ldkfj",
"2001::FFD3::57ab",
"2001:db8:85a3::8a2e:37023:7334",
"2001:db8:85a3::8a2e:370k:7334",
"1:2:3:4:5:6:7:8:9",
"1::2::3",
"1:::3:4:5",
"1:2:3::4:5:6:7:8:9",
# Invalid data
"XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX",
# Too many components
"1111:2222:3333:4444:5555:6666:7777:8888:9999",
"1111:2222:3333:4444:5555:6666:7777:8888::",
"::2222:3333:4444:5555:6666:7777:8888:9999",
# Too few components
"1111:2222:3333:4444:5555:6666:7777",
"1111:2222:3333:4444:5555:6666",
"1111:2222:3333:4444:5555",
"1111:2222:3333:4444",
"1111:2222:3333",
"1111:2222",
"1111",
# Missing :
"11112222:3333:4444:5555:6666:7777:8888",
"1111:22223333:4444:5555:6666:7777:8888",
"1111:2222:33334444:5555:6666:7777:8888",
"1111:2222:3333:44445555:6666:7777:8888",
"1111:2222:3333:4444:55556666:7777:8888",
"1111:2222:3333:4444:5555:66667777:8888",
"1111:2222:3333:4444:5555:6666:77778888",
# Missing : intended for ::
"1111:2222:3333:4444:5555:6666:7777:8888:",
"1111:2222:3333:4444:5555:6666:7777:",
"1111:2222:3333:4444:5555:6666:",
"1111:2222:3333:4444:5555:",
"1111:2222:3333:4444:",
"1111:2222:3333:",
"1111:2222:",
"1111:",
":",
":8888",
":7777:8888",
":6666:7777:8888",
":5555:6666:7777:8888",
":4444:5555:6666:7777:8888",
":3333:4444:5555:6666:7777:8888",
":2222:3333:4444:5555:6666:7777:8888",
":1111:2222:3333:4444:5555:6666:7777:8888",
# :::
":::2222:3333:4444:5555:6666:7777:8888",
"1111:::3333:4444:5555:6666:7777:8888",
"1111:2222:::4444:5555:6666:7777:8888",
"1111:2222:3333:::5555:6666:7777:8888",
"1111:2222:3333:4444:::6666:7777:8888",
"1111:2222:3333:4444:5555:::7777:8888",
"1111:2222:3333:4444:5555:6666:::8888",
"1111:2222:3333:4444:5555:6666:7777:::",
# Double ::",
"::2222::4444:5555:6666:7777:8888",
"::2222:3333::5555:6666:7777:8888",
"::2222:3333:4444::6666:7777:8888",
"::2222:3333:4444:5555::7777:8888",
"::2222:3333:4444:5555:7777::8888",
"::2222:3333:4444:5555:7777:8888::",
"1111::3333::5555:6666:7777:8888",
"1111::3333:4444::6666:7777:8888",
"1111::3333:4444:5555::7777:8888",
"1111::3333:4444:5555:6666::8888",
"1111::3333:4444:5555:6666:7777::",
"1111:2222::4444::6666:7777:8888",
"1111:2222::4444:5555::7777:8888",
"1111:2222::4444:5555:6666::8888",
"1111:2222::4444:5555:6666:7777::",
"1111:2222:3333::5555::7777:8888",
"1111:2222:3333::5555:6666::8888",
"1111:2222:3333::5555:6666:7777::",
"1111:2222:3333:4444::6666::8888",
"1111:2222:3333:4444::6666:7777::",
"1111:2222:3333:4444:5555::7777::",
# Too many components"
"1111:2222:3333:4444:5555:6666:7777:8888:1.2.3.4",
"1111:2222:3333:4444:5555:6666:7777:1.2.3.4",
"1111:2222:3333:4444:5555:6666::1.2.3.4",
"::2222:3333:4444:5555:6666:7777:1.2.3.4",
"1111:2222:3333:4444:5555:6666:1.2.3.4.5",
# Too few components
"1111:2222:3333:4444:5555:1.2.3.4",
"1111:2222:3333:4444:1.2.3.4",
"1111:2222:3333:1.2.3.4",
"1111:2222:1.2.3.4",
"1111:1.2.3.4",
# Missing :
"11112222:3333:4444:5555:6666:1.2.3.4",
"1111:22223333:4444:5555:6666:1.2.3.4",
"1111:2222:33334444:5555:6666:1.2.3.4",
"1111:2222:3333:44445555:6666:1.2.3.4",
"1111:2222:3333:4444:55556666:1.2.3.4",
"1111:2222:3333:4444:5555:66661.2.3.4",
# Missing .
"1111:2222:3333:4444:5555:6666:255255.255.255",
"1111:2222:3333:4444:5555:6666:255.255255.255",
"1111:2222:3333:4444:5555:6666:255.255.255255",
# Missing : intended for ::
":1.2.3.4",
":6666:1.2.3.4",
":5555:6666:1.2.3.4",
":4444:5555:6666:1.2.3.4",
":3333:4444:5555:6666:1.2.3.4",
":2222:3333:4444:5555:6666:1.2.3.4",
":1111:2222:3333:4444:5555:6666:1.2.3.4",
# :::
":::2222:3333:4444:5555:6666:1.2.3.4",
"1111:::3333:4444:5555:6666:1.2.3.4",
"1111:2222:::4444:5555:6666:1.2.3.4",
"1111:2222:3333:::5555:6666:1.2.3.4",
"1111:2222:3333:4444:::6666:1.2.3.4",
"1111:2222:3333:4444:5555:::1.2.3.4",
# Double ::
"::2222::4444:5555:6666:1.2.3.4",
"::2222:3333::5555:6666:1.2.3.4",
"::2222:3333:4444::6666:1.2.3.4",
"::2222:3333:4444:5555::1.2.3.4",
"1111::3333::5555:6666:1.2.3.4",
"1111::3333:4444::6666:1.2.3.4",
"1111::3333:4444:5555::1.2.3.4",
"1111:2222::4444::6666:1.2.3.4",
"1111:2222::4444:5555::1.2.3.4",
"1111:2222:3333::5555::1.2.3.4",
# Missing parts
"::.",
"::..",
"::...",
"::1...",
"::1.2..",
"::1.2.3.",
"::.2..",
"::.2.3.",
"::.2.3.4",
"::..3.",
"::..3.4",
"::...4",
# Extra : in front
":1111:2222:3333:4444:5555:6666:7777::",
":1111:2222:3333:4444:5555:6666::",
":1111:2222:3333:4444:5555::",
":1111:2222:3333:4444::",
":1111:2222:3333::",
":1111:2222::",
":1111::",
":::",
":1111:2222:3333:4444:5555:6666::8888",
":1111:2222:3333:4444:5555::8888",
":1111:2222:3333:4444::8888",
":1111:2222:3333::8888",
":1111:2222::8888",
":1111::8888",
":::8888",
":1111:2222:3333:4444:5555::7777:8888",
":1111:2222:3333:4444::7777:8888",
":1111:2222:3333::7777:8888",
":1111:2222::7777:8888",
":1111::7777:8888",
":::7777:8888",
":1111:2222:3333:4444::6666:7777:8888",
":1111:2222:3333::6666:7777:8888",
":1111:2222::6666:7777:8888",
":1111::6666:7777:8888",
":::6666:7777:8888",
":1111:2222:3333::5555:6666:7777:8888",
":1111:2222::5555:6666:7777:8888",
":1111::5555:6666:7777:8888",
":::5555:6666:7777:8888",
":1111:2222::4444:5555:6666:7777:8888",
":1111::4444:5555:6666:7777:8888",
":::4444:5555:6666:7777:8888",
":1111::3333:4444:5555:6666:7777:8888",
":::3333:4444:5555:6666:7777:8888",
":::2222:3333:4444:5555:6666:7777:8888",
":1111:2222:3333:4444:5555:6666:1.2.3.4",
":1111:2222:3333:4444:5555::1.2.3.4",
":1111:2222:3333:4444::1.2.3.4",
":1111:2222:3333::1.2.3.4",
":1111:2222::1.2.3.4",
":1111::1.2.3.4",
":::1.2.3.4",
":1111:2222:3333:4444::6666:1.2.3.4",
":1111:2222:3333::6666:1.2.3.4",
":1111:2222::6666:1.2.3.4",
":1111::6666:1.2.3.4",
":::6666:1.2.3.4",
":1111:2222:3333::5555:6666:1.2.3.4",
":1111:2222::5555:6666:1.2.3.4",
":1111::5555:6666:1.2.3.4",
":::5555:6666:1.2.3.4",
":1111:2222::4444:5555:6666:1.2.3.4",
":1111::4444:5555:6666:1.2.3.4",
":::4444:5555:6666:1.2.3.4",
":1111::3333:4444:5555:6666:1.2.3.4",
":::2222:3333:4444:5555:6666:1.2.3.4",
# Extra : at end
"1111:2222:3333:4444:5555:6666:7777:::",
"1111:2222:3333:4444:5555:6666:::",
"1111:2222:3333:4444:5555:::",
"1111:2222:3333:4444:::",
"1111:2222:3333:::",
"1111:2222:::",
"1111:::",
":::",
"1111:2222:3333:4444:5555:6666::8888:",
"1111:2222:3333:4444:5555::8888:",
"1111:2222:3333:4444::8888:",
"1111:2222:3333::8888:",
"1111:2222::8888:",
"1111::8888:",
"::8888:",
"1111:2222:3333:4444:5555::7777:8888:",
"1111:2222:3333:4444::7777:8888:",
"1111:2222:3333::7777:8888:",
"1111:2222::7777:8888:",
"1111::7777:8888:",
"::7777:8888:",
"1111:2222:3333:4444::6666:7777:8888:",
"1111:2222:3333::6666:7777:8888:",
"1111:2222::6666:7777:8888:",
"1111::6666:7777:8888:",
"::6666:7777:8888:",
"1111:2222:3333::5555:6666:7777:8888:",
"1111:2222::5555:6666:7777:8888:",
"1111::5555:6666:7777:8888:",
"::5555:6666:7777:8888:",
"1111:2222::4444:5555:6666:7777:8888:",
"1111::4444:5555:6666:7777:8888:",
"::4444:5555:6666:7777:8888:",
"1111::3333:4444:5555:6666:7777:8888:",
"::3333:4444:5555:6666:7777:8888:",
"::2222:3333:4444:5555:6666:7777:8888:",
].each do |ip|
it "should reject #{ip.inspect} as an IPv6 address" do
expect { @class.new(:name => "foo", :ip => ip) }.to raise_error
end
end
it "should not accept spaces in resourcename" do
proc { @class.new(:name => "foo bar") }.should raise_error
end
it "should not accept host_aliases with spaces" do
proc { @class.new(:name => "foo", :host_aliases => [ 'well_formed', 'not wellformed' ]) }.should raise_error
end
it "should not accept empty host_aliases" do
proc { @class.new(:name => "foo", :host_aliases => ['alias1','']) }.should raise_error
end
end
describe "when syncing" do
it "should send the first value to the provider for ip property" do
@ip = @class.attrclass(:ip).new(:resource => @resource, :should => %w{192.168.0.1 192.168.0.2})
- @provider.expects(:ip=).with '192.168.0.1'
+
@ip.sync
+
+ @provider.ip.should == '192.168.0.1'
end
it "should send the first value to the provider for comment property" do
@comment = @class.attrclass(:comment).new(:resource => @resource, :should => %w{Bazinga Notme})
- @provider.expects(:comment=).with 'Bazinga'
+
@comment.sync
+
+ @provider.comment.should == 'Bazinga'
end
it "should send the joined array to the provider for host_alias" do
@host_aliases = @class.attrclass(:host_aliases).new(:resource => @resource, :should => %w{foo bar})
- @provider.expects(:host_aliases=).with 'foo bar'
+
@host_aliases.sync
+
+ @provider.host_aliases.should == 'foo bar'
end
it "should also use the specified delimiter for joining" do
@host_aliases = @class.attrclass(:host_aliases).new(:resource => @resource, :should => %w{foo bar})
@host_aliases.stubs(:delimiter).returns "\t"
- @provider.expects(:host_aliases=).with "foo\tbar"
+
@host_aliases.sync
+
+ @provider.host_aliases.should == "foo\tbar"
end
it "should care about the order of host_aliases" do
@host_aliases = @class.attrclass(:host_aliases).new(:resource => @resource, :should => %w{foo bar})
@host_aliases.insync?(%w{foo bar}).should == true
@host_aliases.insync?(%w{bar foo}).should == false
end
it "should not consider aliases to be in sync if should is a subset of current" do
@host_aliases = @class.attrclass(:host_aliases).new(:resource => @resource, :should => %w{foo bar})
@host_aliases.insync?(%w{foo bar anotherone}).should == false
end
end
end
diff --git a/spec/unit/type/mount_spec.rb b/spec/unit/type/mount_spec.rb
index 82eb563e9..d06ccc90d 100755
--- a/spec/unit/type/mount_spec.rb
+++ b/spec/unit/type/mount_spec.rb
@@ -1,329 +1,337 @@
#!/usr/bin/env rspec
require 'spec_helper'
describe Puppet::Type.type(:mount), :unless => Puppet.features.microsoft_windows? do
it "should have a :refreshable feature that requires the :remount method" do
Puppet::Type.type(:mount).provider_feature(:refreshable).methods.should == [:remount]
end
it "should have no default value for :ensure" do
mount = Puppet::Type.type(:mount).new(:name => "yay")
mount.should(:ensure).should be_nil
end
it "should have :name as the only keyattribut" do
Puppet::Type.type(:mount).key_attributes.should == [:name]
end
end
describe Puppet::Type.type(:mount), "when validating attributes" do
[:name, :remounts, :provider].each do |param|
it "should have a #{param} parameter" do
Puppet::Type.type(:mount).attrtype(param).should == :param
end
end
[:ensure, :device, :blockdevice, :fstype, :options, :pass, :dump, :atboot, :target].each do |param|
it "should have a #{param} property" do
Puppet::Type.type(:mount).attrtype(param).should == :property
end
end
end
describe Puppet::Type.type(:mount)::Ensure, "when validating values", :unless => Puppet.features.microsoft_windows? do
before do
@provider = stub 'provider', :class => Puppet::Type.type(:mount).defaultprovider, :clear => nil
Puppet::Type.type(:mount).defaultprovider.expects(:new).returns(@provider)
end
it "should alias :present to :defined as a value to :ensure" do
mount = Puppet::Type.type(:mount).new(:name => "yay", :ensure => :present)
mount.should(:ensure).should == :defined
end
it "should support :present as a value to :ensure" do
Puppet::Type.type(:mount).new(:name => "yay", :ensure => :present)
end
it "should support :defined as a value to :ensure" do
Puppet::Type.type(:mount).new(:name => "yay", :ensure => :defined)
end
it "should support :unmounted as a value to :ensure" do
Puppet::Type.type(:mount).new(:name => "yay", :ensure => :unmounted)
end
it "should support :absent as a value to :ensure" do
Puppet::Type.type(:mount).new(:name => "yay", :ensure => :absent)
end
it "should support :mounted as a value to :ensure" do
Puppet::Type.type(:mount).new(:name => "yay", :ensure => :mounted)
end
end
describe Puppet::Type.type(:mount)::Ensure, :unless => Puppet.features.microsoft_windows? do
before :each do
provider_properties = {}
@provider = stub 'provider', :class => Puppet::Type.type(:mount).defaultprovider, :clear => nil, :satisfies? => true, :name => :mock, :property_hash => provider_properties
Puppet::Type.type(:mount).defaultprovider.stubs(:new).returns(@provider)
@mount = Puppet::Type.type(:mount).new(:name => "yay", :check => :ensure)
@ensure = @mount.property(:ensure)
end
def mount_stub(params)
Puppet::Type.type(:mount).validproperties.each do |prop|
unless params[prop]
params[prop] = :absent
@mount[prop] = :absent
end
end
params.each do |param, value|
@provider.stubs(param).returns(value)
end
end
describe Puppet::Type.type(:mount)::Ensure, "when changing the host" do
def test_ensure_change(options)
@provider.stubs(:get).with(:ensure).returns options[:from]
@provider.stubs(:ensure).returns options[:from]
@provider.stubs(:mounted?).returns([:mounted,:ghost].include? options[:from])
@provider.expects(:create).times(options[:create] || 0)
@provider.expects(:destroy).times(options[:destroy] || 0)
@provider.expects(:mount).never
@provider.expects(:unmount).times(options[:unmount] || 0)
@ensure.stubs(:syncothers)
@ensure.should = options[:to]
@ensure.sync
(!!@provider.property_hash[:needs_mount]).should == (!!options[:mount])
end
it "should create itself when changing from :ghost to :present" do
test_ensure_change(:from => :ghost, :to => :present, :create => 1)
end
it "should create itself when changing from :absent to :present" do
test_ensure_change(:from => :absent, :to => :present, :create => 1)
end
it "should create itself and unmount when changing from :ghost to :unmounted" do
test_ensure_change(:from => :ghost, :to => :unmounted, :create => 1, :unmount => 1)
end
it "should unmount resource when changing from :mounted to :unmounted" do
test_ensure_change(:from => :mounted, :to => :unmounted, :unmount => 1)
end
it "should create itself when changing from :absent to :unmounted" do
test_ensure_change(:from => :absent, :to => :unmounted, :create => 1)
end
it "should unmount resource when changing from :ghost to :absent" do
test_ensure_change(:from => :ghost, :to => :absent, :unmount => 1)
end
it "should unmount and destroy itself when changing from :mounted to :absent" do
test_ensure_change(:from => :mounted, :to => :absent, :destroy => 1, :unmount => 1)
end
it "should destroy itself when changing from :unmounted to :absent" do
test_ensure_change(:from => :unmounted, :to => :absent, :destroy => 1)
end
it "should create itself when changing from :ghost to :mounted" do
test_ensure_change(:from => :ghost, :to => :mounted, :create => 1)
end
it "should create itself and mount when changing from :absent to :mounted" do
test_ensure_change(:from => :absent, :to => :mounted, :create => 1, :mount => 1)
end
it "should mount resource when changing from :unmounted to :mounted" do
test_ensure_change(:from => :unmounted, :to => :mounted, :mount => 1)
end
it "should be in sync if it is :absent and should be :absent" do
@ensure.should = :absent
@ensure.safe_insync?(:absent).should == true
end
it "should be out of sync if it is :absent and should be :defined" do
@ensure.should = :defined
@ensure.safe_insync?(:absent).should == false
end
it "should be out of sync if it is :absent and should be :mounted" do
@ensure.should = :mounted
@ensure.safe_insync?(:absent).should == false
end
it "should be out of sync if it is :absent and should be :unmounted" do
@ensure.should = :unmounted
@ensure.safe_insync?(:absent).should == false
end
it "should be out of sync if it is :mounted and should be :absent" do
@ensure.should = :absent
@ensure.safe_insync?(:mounted).should == false
end
it "should be in sync if it is :mounted and should be :defined" do
@ensure.should = :defined
@ensure.safe_insync?(:mounted).should == true
end
it "should be in sync if it is :mounted and should be :mounted" do
@ensure.should = :mounted
@ensure.safe_insync?(:mounted).should == true
end
it "should be out in sync if it is :mounted and should be :unmounted" do
@ensure.should = :unmounted
@ensure.safe_insync?(:mounted).should == false
end
it "should be out of sync if it is :unmounted and should be :absent" do
@ensure.should = :absent
@ensure.safe_insync?(:unmounted).should == false
end
it "should be in sync if it is :unmounted and should be :defined" do
@ensure.should = :defined
@ensure.safe_insync?(:unmounted).should == true
end
it "should be out of sync if it is :unmounted and should be :mounted" do
@ensure.should = :mounted
@ensure.safe_insync?(:unmounted).should == false
end
it "should be in sync if it is :unmounted and should be :unmounted" do
@ensure.should = :unmounted
@ensure.safe_insync?(:unmounted).should == true
end
it "should be out of sync if it is :ghost and should be :absent" do
@ensure.should = :absent
@ensure.safe_insync?(:ghost).should == false
end
it "should be out of sync if it is :ghost and should be :defined" do
@ensure.should = :defined
@ensure.safe_insync?(:ghost).should == false
end
it "should be out of sync if it is :ghost and should be :mounted" do
@ensure.should = :mounted
@ensure.safe_insync?(:ghost).should == false
end
it "should be out of sync if it is :ghost and should be :unmounted" do
@ensure.should = :unmounted
@ensure.safe_insync?(:ghost).should == false
end
end
describe Puppet::Type.type(:mount), "when responding to refresh" do
pending "2.6.x specifies slightly different behavior and the desired behavior needs to be clarified and revisited. See ticket #4904" do
it "should remount if it is supposed to be mounted" do
@mount[:ensure] = "mounted"
@provider.expects(:remount)
@mount.refresh
end
it "should not remount if it is supposed to be present" do
@mount[:ensure] = "present"
@provider.expects(:remount).never
@mount.refresh
end
it "should not remount if it is supposed to be absent" do
@mount[:ensure] = "absent"
@provider.expects(:remount).never
@mount.refresh
end
it "should not remount if it is supposed to be defined" do
@mount[:ensure] = "defined"
@provider.expects(:remount).never
@mount.refresh
end
it "should not remount if it is supposed to be unmounted" do
@mount[:ensure] = "unmounted"
@provider.expects(:remount).never
@mount.refresh
end
it "should not remount swap filesystems" do
@mount[:ensure] = "mounted"
@mount[:fstype] = "swap"
@provider.expects(:remount).never
@mount.refresh
end
end
end
end
describe Puppet::Type.type(:mount), "when modifying an existing mount entry", :unless => Puppet.features.microsoft_windows? do
before do
@provider = stub 'provider', :class => Puppet::Type.type(:mount).defaultprovider, :clear => nil, :satisfies? => true, :name => :mock, :remount => nil
Puppet::Type.type(:mount).defaultprovider.stubs(:new).returns(@provider)
+
@mount = Puppet::Type.type(:mount).new(:name => "yay", :ensure => :mounted)
{:device => "/foo/bar", :blockdevice => "/other/bar", :target => "/what/ever", :fstype => 'eh', :options => "", :pass => 0, :dump => 0, :atboot => 0,
:ensure => :mounted}.each do
|param, value|
@mount.provider.stubs(param).returns value
@mount[param] = value
end
@mount.provider.stubs(:mounted?).returns true
# stub this to not try to create state.yaml
Puppet::Util::Storage.stubs(:store)
@catalog = Puppet::Resource::Catalog.new
@catalog.add_resource @mount
end
it "should use the provider to change the dump value" do
@mount.provider.expects(:dump).returns 0
@mount.provider.expects(:dump=).with(1)
+ @mount.provider.stubs(:respond_to?).returns(false)
+ @mount.provider.stubs(:respond_to?).with("dump=").returns(true)
@mount[:dump] = 1
@catalog.apply
end
it "should umount before flushing changes to disk" do
syncorder = sequence('syncorder')
@mount.provider.expects(:options).returns 'soft'
@mount.provider.expects(:ensure).returns :mounted
+ @mount.provider.stubs(:respond_to?).returns(false)
+ @mount.provider.stubs(:respond_to?).with("options=").returns(true)
+
@mount.provider.expects(:unmount).in_sequence(syncorder)
@mount.provider.expects(:options=).in_sequence(syncorder).with 'hard'
@mount.expects(:flush).in_sequence(syncorder) # Call inside syncothers
@mount.expects(:flush).in_sequence(syncorder) # I guess transaction or anything calls flush again
+ @mount.provider.stubs(:respond_to?).with(:options=).returns(true)
+
@mount[:ensure] = :unmounted
@mount[:options] = 'hard'
@catalog.apply
end
end