Please review pull request #420: #2927 symbolic file mode redux opened by (daniel-pittman)

Description:

This restores the code needed to make the Symbolic file mode stuff work.

It includes an acceptance test for symbolic file modes, and the reverted code that my refactor of insync? broke.

It also refactors, and extends, tests around the Puppet::Property object, to make sure it is thoroughly tested.

  • Opened: Sat Jan 28 00:36:23 UTC 2012
  • Based on: puppetlabs:2.7.x (6f394cb38d8c5602cce3e1224e4a0f74e19370fd)
  • Requested merge: daniel-pittman:feature/2.7.x/2927-symbolic-file-mode-redux (6140f7f154d34c34f94e6b0881737b21c64141db)

Diff follows:

diff --git a/acceptance/tests/resource/file/symbolic_modes.rb b/acceptance/tests/resource/file/symbolic_modes.rb
new file mode 100644
index 0000000..ecdc71a
--- /dev/null
+++ b/acceptance/tests/resource/file/symbolic_modes.rb
@@ -0,0 +1,108 @@
+test_name "file resource: symbolic modes"
+
+prefix = '/tmp/symbolic-mode-'
+
+file = prefix +
+  "-file-#{Time.now.strftime("%Y%m%d")}-#{$$}-" +
+  "#{rand(0x100000000).to_s(36)}.test"
+
+dir = prefix +
+  "-dir-#{Time.now.strftime("%Y%m%d")}-#{$$}-" +
+  "#{rand(0x100000000).to_s(36)}.test"
+
+def validate(path, mode)
+  "ruby -e 'File::Stat.new(#{path.inspect}).mode == #{mode}'"
+end
+
+# Infrastructure for a nice, table driven test.  Yum.
+step "test setup: create the scratch file"
+on agents, "touch #{file} && chmod 0444 #{file} && chown root:root #{file}"
+on agents, "mkdir #{dir}  && chmod 0755 #{dir}  && chown root:root #{dir}"
+
+# For your reference:
+# 4000    the set-user-ID-on-execution bit
+# 2000    the set-group-ID-on-execution bit
+# 1000    the sticky bit
+# 0400    Allow read by owner.
+# 0200    Allow write by owner.
+# 0100    For files, allow execution by owner.  For directories, allow the
+#         owner to search in the directory.
+# 0040    Allow read by group members.
+# 0020    Allow write by group members.
+# 0010    For files, allow execution by group members.  For directories, allow
+#         group members to search in the directory.
+# 0004    Allow read by others.
+# 0002    Allow write by others.
+# 0001    For files, allow execution by others.  For directories allow others
+#         to search in the directory.
+#
+# fields are:  start_mode, symbolic_mode, file_mode, dir_mode
+# start_mode is passed to chmod on the target platform
+# symbolic_mode is the mode set in our test
+# the file and dir mode values are the numeric resultant values we should get
+tests = <<END
+0000   u=rwx     0700  0700
+0000   ug=rwx    0770  0770
+0000   ugo=rwx   0777  0777
+0000   u=r       0400  0400
+0000   u=w       0200  0200
+0000   u=x       0100  0100
+
+0500   u+w       0700  0700
+0400   u+w       0600  0600
+0200   u+r       0600  0600
+
+0100   u+X       0100  0100
+0200   u+X       0300  0300
+0400   u+X       0500  0500
+END
+
+tests.split("\n").map {|x| x.split(/\s+/)}.each do |data|
+  # Might as well skip blank lines.
+  next if data.empty? or data.any? {|x| x.nil? or x.empty? }
+
+  # Make sure our interpretation of the data is reasonable.
+  start_mode    = '%04o' % data[0].to_i(8)
+  symbolic_mode =          data[1].inspect
+  file_mode     = '%04o' % data[2].to_i(8)
+  dir_mode      = '%04o' % data[3].to_i(8)
+
+  step "ensure permissions for testing #{symbolic_mode}"
+  on agents, "touch #{file}   && chmod #{start_mode} #{file} && chown root:root #{file}"
+  on agents, "mkdir -p #{dir} && chmod #{start_mode} #{dir}  && chown root:root #{dir}"
+
+  step "test mode #{symbolic_mode} works on a file"
+  manifest = "file { #{file.inspect}: ensure => file, mode => #{symbolic_mode} }"
+  apply_manifest_on(agents, manifest) do
+    assert_match(/mode changed '#{start_mode}' to '#{file_mode}'/, stdout,
+                 "couldn't set file mode to #{symbolic_mode}")
+  end
+
+  step "validate the mode changes applied to the file"
+  on agents, "test -f #{file} && " + validate(file, file_mode)
+
+  # Validate that we don't reapply the changes - that they are stable.
+  apply_manifest_on(agents, manifest) do
+    assert_no_match(/mode changed/, stdout, "reapplied the symbolic mode change")
+  end
+
+  step "test mode #{symbolic_mode} works on a directory"
+  manifest = "file { #{dir.inspect}: ensure => directory, mode => #{symbolic_mode} }"
+  apply_manifest_on(agents, manifest) do
+    assert_match(/mode changed '#{start_mode}' to '#{dir_mode}'/, stdout,
+                 "couldn't set dir mode to #{symbolic_mode}")
+  end
+
+  step "validate the mode changes applied to the dir"
+  on agents, "test -f #{dir} && " + validate(file, dir_mode)
+
+  # Validate that we don't reapply the changes - that they are stable.
+  apply_manifest_on(agents, manifest) do
+    assert_no_match(/mode changed/, stdout, "reapplied the symbolic mode change")
+  end
+
+end
+
+
+step "clean up old test things"
+on agents, "rm -rf #{prefix}*"
diff --git a/lib/puppet/property.rb b/lib/puppet/property.rb
index 69bea7d..f4914fa 100644
--- a/lib/puppet/property.rb
+++ b/lib/puppet/property.rb
@@ -166,22 +166,43 @@ 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 should be overridden by derived classes if necessary
+  # This method may be overridden by derived classes if necessary
   # to provide extra logic to determine whether the property is in
-  # sync.
+  # 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
-    return (is == @should or is == @should.collect { |v| v.to_s }) if match_all?
+    # 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
 
-    @should.each { |val| return true if is == val or is == val.to_s }
+      # If they were different lengths, they are not equal.
+      return false unless is.length == @should.length
 
-    # otherwise, return false
-    false
+      # Finally, are all the elements equal?
+      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,
diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb
index e8ab98a..2658cfa 100755
--- a/spec/unit/property_spec.rb
+++ b/spec/unit/property_spec.rb
@@ -3,161 +3,152 @@
 require 'puppet/property'
 
 describe Puppet::Property do
-  before do
-    @class = Class.new(Puppet::Property) do
-      @name = :foo
-    end
-    @class.initvars
-    @provider = mock 'provider'
-    @resource = stub 'resource', :provider => @provider
-    @resource.stub_everything
-    @property = @class.new :resource => @resource
+  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.initvars
+    subclass
   end
 
-  it "should return its name as a string when converted to a string" do
-    @property.to_s.should == @property.name.to_s
-  end
+  let :property do subclass.new :resource => resource end
 
   it "should be able to look up the modified name for a given value" do
-    @class.newvalue(:foo)
-    @class.value_name("foo").should == :foo
+    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
-    @class.newvalue(%r{.})
-    @class.value_name("foo").should == %r{.}
+    subclass.newvalue(%r{.})
+    subclass.value_name("foo").should == %r{.}
   end
 
   it "should be able to look up a given value option" do
-    @class.newvalue(:foo, :event => :whatever)
-    @class.value_option(:foo, :event).should == :whatever
+    subclass.newvalue(:foo, :event => :whatever)
+    subclass.value_option(:foo, :event).should == :whatever
   end
 
   it "should be able to specify required features" do
-    @class.should respond_to(:required_features=)
+    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
-      @class.required_features = in_value
-      @class.required_features.should == out_value
+      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)
+    property.must respond_to(:shadow)
   end
 
   describe "when returning the default event name" do
-    before do
-      @resource = stub 'resource'
-      @instance = @class.new(:resource => @resource)
-      @instance.stubs(:should).returns "myval"
-    end
-
     it "should use the current 'should' value to pick the event name" do
-      @instance.expects(:should).returns "myvalue"
-      @class.expects(:value_option).with('myvalue', :event).returns :event_name
+      property.expects(:should).returns "myvalue"
+      subclass.expects(:value_option).with('myvalue', :event).returns :event_name
 
-      @instance.event_name
+      property.event_name
     end
 
     it "should return any event defined with the specified value" do
-      @instance.expects(:should).returns :myval
-      @class.expects(:value_option).with(:myval, :event).returns :event_name
+      property.expects(:should).returns :myval
+      subclass.expects(:value_option).with(:myval, :event).returns :event_name
 
-      @instance.event_name.should == :event_name
+      property.event_name.should == :event_name
     end
 
     describe "and the property is 'ensure'" do
-      before do
-        @instance.stubs(:name).returns :ensure
-        @resource.expects(:type).returns :mytype
+      before :each do
+        property.stubs(:name).returns :ensure
+        resource.expects(:type).returns :mytype
       end
 
       it "should use <type>_created if the 'should' value is 'present'" do
-        @instance.expects(:should).returns :present
-        @instance.event_name.should == :mytype_created
+        property.expects(:should).returns :present
+        property.event_name.should == :mytype_created
       end
 
       it "should use <type>_removed if the 'should' value is 'absent'" do
-        @instance.expects(:should).returns :absent
-        @instance.event_name.should == :mytype_removed
+        property.expects(:should).returns :absent
+        property.event_name.should == :mytype_removed
       end
 
       it "should use <type>_changed if the 'should' value is not 'absent' or 'present'" do
-        @instance.expects(:should).returns :foo
-        @instance.event_name.should == :mytype_changed
+        property.expects(:should).returns :foo
+        property.event_name.should == :mytype_changed
       end
 
       it "should use <type>_changed if the 'should value is nil" do
-        @instance.expects(:should).returns nil
-        @instance.event_name.should == :mytype_changed
+        property.expects(:should).returns nil
+        property.event_name.should == :mytype_changed
       end
     end
 
     it "should use <property>_changed if the property is not 'ensure'" do
-      @instance.stubs(:name).returns :myparam
-      @instance.expects(:should).returns :foo
-      @instance.event_name.should == :myparam_changed
+      property.stubs(:name).returns :myparam
+      property.expects(:should).returns :foo
+      property.event_name.should == :myparam_changed
     end
 
     it "should use <property>_changed if no 'should' value is set" do
-      @instance.stubs(:name).returns :myparam
-      @instance.expects(:should).returns nil
-      @instance.event_name.should == :myparam_changed
+      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 do
-      @event = Puppet::Transaction::Event.new
-
-      # Use a real resource so we can test the event creation integration
-      @resource = Puppet::Type.type(:host).new :name => "foo"
-      @instance = @class.new(:resource => @resource)
-      @instance.stubs(:should).returns "myval"
+    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
+      resource.expects(:event).returns event
 
-      @instance.event.should equal(event)
+      property.event.should equal(event)
     end
 
     it "should have the default event name" do
-      @instance.expects(:event_name).returns :my_event
-      @instance.event.name.should == :my_event
+      property.expects(:event_name).returns :my_event
+      property.event.name.should == :my_event
     end
 
     it "should have the property's name" do
-      @instance.event.property.should == @instance.name.to_s
+      property.event.property.should == property.name.to_s
     end
 
     it "should have the 'should' value set" do
-      @instance.stubs(:should).returns "foo"
-      @instance.event.desired_value.should == "foo"
+      property.stubs(:should).returns "foo"
+      property.event.desired_value.should == "foo"
     end
 
     it "should provide its path as the source description" do
-      @instance.stubs(:path).returns "/my/param"
-      @instance.event.source_description.should == "/my/param"
+      property.stubs(:path).returns "/my/param"
+      property.event.source_description.should == "/my/param"
     end
   end
 
   describe "when shadowing metaparameters" do
-    before do
-      @shadow_class = Class.new(Puppet::Property) do
+    let :shadow_class do
+      shadow_class = Class.new(Puppet::Property) do
         @name = :alias
       end
-      @shadow_class.initvars
+      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)
+      Puppet::Type.metaparamclass(:alias).expects(:new).with(:resource => resource)
 
-      @shadow_class.new :resource => @resource
+      shadow_class.new :resource => resource
     end
 
     it "should munge values using the shadow's munge method" do
@@ -166,244 +157,354 @@
 
       shadow.expects(:munge).with "foo"
 
-      property = @shadow_class.new :resource => @resource
+      property = shadow_class.new :resource => resource
       property.munge("foo")
     end
   end
 
   describe "when defining new values" do
     it "should define a method for each value created with a block that's not a regex" do
-      @class.newvalue(:foo) { }
-      @property.must respond_to(:set_foo)
+      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"
+      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.expects(:validate).with("one")
+      property.expects(:validate).with("two")
 
-      @property.value = %w{one 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
+      property.expects(:munge).with("one").returns :one
+      property.expects(:munge).with("two").returns :two
 
       # Do this so we get the whole array back.
-      @class.array_matching = :all
+      subclass.array_matching = :all
 
-      @property.value = %w{one two}
-      @property.should.must == [:one, :two]
+      property.value = %w{one two}
+      property.should.must == [:one, :two]
     end
 
     it "should return any set value" do
-      (@property.value = :one).should == :one
+      (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
+      property.should.must be_nil
     end
 
     it "should return the first set 'should' value if :array_matching is set to :first" do
-      @class.array_matching = :first
-      @property.should = %w{one two}
-      @property.should.must == "one"
+      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
-      @class.array_matching = :all
-      @property.should = %w{one two}
-      @property.should.must == %w{one two}
+      subclass.array_matching = :all
+      property.should = %w{one two}
+      property.should.must == %w{one two}
     end
 
     it "should default to :first array_matching" do
-      @class.array_matching.should == :first
+      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
-      @class.array_matching = :first
-      @property.should = %w{one two}
+      property.class.unmunge do |v| v.to_sym end
+      subclass.array_matching = :first
+      property.should = %w{one two}
 
-      @property.should.must == :one
+      property.should.must == :one
     end
 
     it "should unmunge all the returned values if :array_matching is set to :all" do
-      @property.class.unmunge do |v| v.to_sym end
-      @class.array_matching = :all
-      @property.should = %w{one two}
+      property.class.unmunge do |v| v.to_sym end
+      subclass.array_matching = :all
+      property.should = %w{one two}
 
-      @property.should.must == [: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
+      lambda { property.should = "foo" }.should_not raise_error
     end
 
     it "should fail if the value is not a defined value or alias and does not match a regex" do
-      @class.newvalue(:foo)
+      subclass.newvalue(:foo)
 
-      lambda { @property.should = "bar" }.should raise_error
+      lambda { property.should = "bar" }.should raise_error
     end
 
     it "should succeeed if the value is one of the defined values" do
-      @class.newvalue(:foo)
+      subclass.newvalue(:foo)
 
-      lambda { @property.should = :foo }.should_not raise_error
+      lambda { property.should = :foo }.should_not raise_error
     end
 
     it "should succeeed if the value is one of the defined values even if the definition uses a symbol and the validation uses a string" do
-      @class.newvalue(:foo)
+      subclass.newvalue(:foo)
 
-      lambda { @property.should = "foo" }.should_not raise_error
+      lambda { property.should = "foo" }.should_not raise_error
     end
 
     it "should succeeed if the value is one of the defined values even if the definition uses a string and the validation uses a symbol" do
-      @class.newvalue("foo")
+      subclass.newvalue("foo")
 
-      lambda { @property.should = :foo }.should_not raise_error
+      lambda { property.should = :foo }.should_not raise_error
     end
 
     it "should succeed if the value is one of the defined aliases" do
-      @class.newvalue("foo")
-      @class.aliasvalue("bar", "foo")
+      subclass.newvalue("foo")
+      subclass.aliasvalue("bar", "foo")
 
-      lambda { @property.should = :bar }.should_not raise_error
+      lambda { property.should = :bar }.should_not raise_error
     end
 
     it "should succeed if the value matches one of the regexes" do
-      @class.newvalue(/./)
+      subclass.newvalue(/./)
 
-      lambda { @property.should = "bar" }.should_not raise_error
+      lambda { property.should = "bar" }.should_not raise_error
     end
 
     it "should validate that all required features are present" do
-      @class.newvalue(:foo, :required_features => [:a, :b])
+      subclass.newvalue(:foo, :required_features => [:a, :b])
 
-      @provider.expects(:satisfies?).with([:a, :b]).returns true
+      resource.provider.expects(:satisfies?).with([:a, :b]).returns true
 
-      @property.should = :foo
+      property.should = :foo
     end
 
     it "should fail if required features are missing" do
-      @class.newvalue(:foo, :required_features => [:a, :b])
+      subclass.newvalue(:foo, :required_features => [:a, :b])
 
-      @provider.expects(:satisfies?).with([:a, :b]).returns false
+      resource.provider.expects(:satisfies?).with([:a, :b]).returns false
 
-      lambda { @property.should = :foo }.should raise_error(Puppet::Error)
+      lambda { property.should = :foo }.should raise_error(Puppet::Error)
     end
 
     it "should internally raise an ArgumentError if required features are missing" do
-      @class.newvalue(:foo, :required_features => [:a, :b])
+      subclass.newvalue(:foo, :required_features => [:a, :b])
 
-      @provider.expects(:satisfies?).with([:a, :b]).returns false
+      resource.provider.expects(:satisfies?).with([:a, :b]).returns false
 
-      lambda { @property.validate_features_per_value :foo }.should raise_error(ArgumentError)
+      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 = @class.newvalue(/./, :required_features => [:a, :b])
+      value = subclass.newvalue(/./, :required_features => [:a, :b])
 
-      @provider.expects(:satisfies?).with([:a, :b]).returns true
+      resource.provider.expects(:satisfies?).with([:a, :b]).returns true
 
-      @property.should = "foo"
+      property.should = "foo"
     end
 
     it "should support specifying an individual required feature" do
-      value = @class.newvalue(/./, :required_features => :a)
+      value = subclass.newvalue(/./, :required_features => :a)
 
-      @provider.expects(:satisfies?).returns true
+      resource.provider.expects(:satisfies?).returns true
 
-      @property.should = "foo"
+      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"
+      property.munge("foo").should == "foo"
     end
 
     it "should return return any matching defined values" do
-      @class.newvalue(:foo)
-      @property.munge("foo").should == :foo
+      subclass.newvalue(:foo)
+      property.munge("foo").should == :foo
     end
 
     it "should return any matching aliases" do
-      @class.newvalue(:foo)
-      @class.aliasvalue(:bar, :foo)
-      @property.munge("bar").should == :foo
+      subclass.newvalue(:foo)
+      subclass.aliasvalue(:bar, :foo)
+      property.munge("bar").should == :foo
     end
 
     it "should return the value if it matches a regex" do
-      @class.newvalue(/./)
-      @property.munge("bar").should == "bar"
+      subclass.newvalue(/./)
+      property.munge("bar").should == "bar"
     end
 
     it "should return the value if no other option is matched" do
-      @class.newvalue(:foo)
-      @property.munge("bar").should == "bar"
+      subclass.newvalue(:foo)
+      property.munge("bar").should == "bar"
     end
   end
 
   describe "when syncing the 'should' value" do
     it "should set the value" do
-      @class.newvalue(:foo)
-      @property.should = :foo
-      @property.expects(:set).with(:foo)
-      @property.sync
+      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
-      @class.newvalue(:foo) { raise "eh" }
-      lambda { @property.set(:foo) }.should raise_error(Puppet::Error)
+      subclass.newvalue(:foo) { raise "eh" }
+      lambda { property.set(:foo) }.should raise_error(Puppet::Error)
     end
 
     describe "that was defined without a block" do
       it "should call the settor on the provider" do
-        @class.newvalue(:bar)
-        @provider.expects(:foo=).with :bar
-        @property.set(:bar)
+        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
-        @class.newvalue(:bar) {}
-        @property.expects(:set_bar)
-        @property.set(:bar)
+        subclass.newvalue(:bar) {}
+        property.expects(:set_bar)
+        property.set(:bar)
       end
 
       it "should call the provided block if the value is a regex" do
-        @class.newvalue(/./) { self.test }
-        @property.expects(:test)
-        @property.set("foo")
+        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/
+      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/
+      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/
+      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
+
+      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
 end

    

--
You received this message because you are subscribed to the Google Groups "Puppet Developers" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to [email protected].
For more options, visit this group at http://groups.google.com/group/puppet-dev?hl=en.

Reply via email to