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