aboutsummaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorSilvio Rhatto <rhatto@riseup.net>2017-12-30 00:02:01 -0200
committerSilvio Rhatto <rhatto@riseup.net>2017-12-30 00:02:01 -0200
commitfa635e9150e8e1042f357ad328b43f41e5c804b2 (patch)
tree2a535441b8d92a71d6e4f7c453d3bb479e11df4c /spec
parent309d5859d9c801218075789fe068869ab49065f5 (diff)
parent1020f7ca2be81c64f4a56816731390a852b62e89 (diff)
downloadpuppet-samba-fa635e9150e8e1042f357ad328b43f41e5c804b2.tar.gz
puppet-samba-fa635e9150e8e1042f357ad328b43f41e5c804b2.tar.bz2
Merge remote-tracking branch 'origin/master' into develop
Diffstat (limited to 'spec')
-rw-r--r--spec/acceptance/basic_samba_spec.rb1
-rw-r--r--spec/classes/samba__server__ads_spec.rb19
-rw-r--r--spec/classes/samba__server_spec.rb4
-rw-r--r--spec/defines/samba__server__share_spec.rb687
-rw-r--r--spec/spec.opts6
-rw-r--r--spec/spec_helper.rb7
-rw-r--r--spec/support/augeas.rb69
7 files changed, 792 insertions, 1 deletions
diff --git a/spec/acceptance/basic_samba_spec.rb b/spec/acceptance/basic_samba_spec.rb
index 8a9e363..e50cb2a 100644
--- a/spec/acceptance/basic_samba_spec.rb
+++ b/spec/acceptance/basic_samba_spec.rb
@@ -22,6 +22,7 @@ describe 'basic samba' do
force_group => 'group',
force_user => 'user',
hide_dot_files => false,
+ msdfs_root => true,
}
"}
diff --git a/spec/classes/samba__server__ads_spec.rb b/spec/classes/samba__server__ads_spec.rb
new file mode 100644
index 0000000..a174bef
--- /dev/null
+++ b/spec/classes/samba__server__ads_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe 'samba::server::ads', :type => :class do
+ let( :facts ) { { :osfamily => 'Debian' } }
+ context "Default config" do
+ it { should contain_exec('join-active-directory') }
+ end
+
+ context "No join" do
+ let ( :params ) { { 'perform_join' => false }}
+ it { should_not contain_exec('join-active-directory') }
+ end
+
+ context "Join 'forced'" do
+ let ( :params ) { { 'perform_join' => true }}
+ it { should contain_exec('join-active-directory') }
+ end
+end
+
diff --git a/spec/classes/samba__server_spec.rb b/spec/classes/samba__server_spec.rb
index 46c5b15..37d5aa8 100644
--- a/spec/classes/samba__server_spec.rb
+++ b/spec/classes/samba__server_spec.rb
@@ -30,19 +30,21 @@ describe 'samba::server' do
'writable' => true,
'guest_ok' => true,
'guest_only' => true,
+ 'msdfs_root' => true,
},
'testShare2' => {
'path' => '/some/other/path'
}
}
}}
- it {
+ it {
should contain_samba__server__share( 'testShare' ).with({
'path' => '/path/to/some/share',
'browsable' => true,
'writable' => true,
'guest_ok' => true,
'guest_only' => true,
+ 'msdfs_root' => true,
})
}
it { should contain_samba__server__share( 'testShare2' ).with_path('/some/other/path') }
diff --git a/spec/defines/samba__server__share_spec.rb b/spec/defines/samba__server__share_spec.rb
new file mode 100644
index 0000000..894f334
--- /dev/null
+++ b/spec/defines/samba__server__share_spec.rb
@@ -0,0 +1,687 @@
+require 'spec_helper'
+
+shared_examples "default share" do
+ let(:title) { "test_share" }
+ let(:params) {{ :ensure => 'present' }}
+ let(:default_changes) do
+ set = Augeas::TargetedChangeSet.new(title)
+ set.with("available")
+ set.with("browsable")
+ set.with("comment")
+ set.with("copy")
+ set.with("create mask")
+ set.with("directory mask")
+ set.with("force create mask")
+ set.with("force directory mode")
+ set.with("force group")
+ set.with("force user")
+ set.with("guest ok")
+ set.with("guest only")
+ set.with("hide unreadable")
+ set.with("path", nil, nil)
+ set.with("read only")
+ set.with("public")
+ set.with("writable")
+ set.with("printable")
+ set.with("follow symlinks")
+ set.with("wide links")
+ set.with("acl group control")
+ set.with("map acl inherit")
+ set.with("profile acls")
+ set.with("store dos attributes")
+ set.with("strict allocate")
+ set.with("valid users")
+ set.with("oplocks")
+ set.with("level2 oplocks")
+ set.with("veto oplock files")
+ set.with("read list")
+ set.with("write list")
+ set.with("hide dot files")
+ set.with("root preexec")
+ set.with("inherit permissions")
+ set.with("inherit acls")
+ set.with("delete readonly")
+ set.with("printer name")
+ set.with("msdfs root")
+ set.with("guest account")
+ end
+ let(:change_set) { default_changes }
+ let(:changes) { change_set.to_a }
+
+ it { is_expected.to contain_samba__server__share(title) }
+ it { is_expected.to contain_augeas("#{title}-section").with(
+ :incl => '/etc/samba/smb.conf',
+ :lens => 'Samba.lns',
+ :context => '/files/etc/samba/smb.conf',
+ :changes => ["set target[. = '#{title}'] '#{title}'"],
+ :require => 'Class[Samba::Server::Config]',
+ :notify => 'Class[Samba::Server::Service]')
+ }
+ it { is_expected.to contain_augeas("#{title}-changes").with(
+ :incl => '/etc/samba/smb.conf',
+ :lens => 'Samba.lns',
+ :context => '/files/etc/samba/smb.conf',
+ :changes => changes,
+ :require => 'Augeas[test_share-section]',
+ :notify => 'Class[Samba::Server::Service]')
+ }
+end
+
+describe 'samba::server::share', :type => :define do
+ let(:pre_condition){ 'class{"samba::server":}'}
+ on_supported_os({
+ :hardwaremodels => ['x86_64'],
+ :supported_os => [
+ {
+ "operatingsystem" => "Ubuntu",
+ "operatingsystemrelease" => [
+ "14.04"
+ ]
+ },
+ {
+ "operatingsystem" => "CentOS",
+ "operatingsystemrelease" => [
+ "7"
+ ]
+ }
+ ],
+ }).each do |os, facts|
+ context "for #{os}" do
+ let(:facts) do
+ facts.merge({
+ :concat_basedir => '/tmp',
+ :domain => 'domain.com'
+ })
+ end
+
+ context 'with base options' do
+ let(:title) { 'test_share' }
+ let(:params) {{
+ :ensure => 'present'
+ }}
+
+ it { is_expected.to contain_samba__server__share('test_share') }
+ it { is_expected.to contain_augeas('test_share-section').with(
+ :incl => '/etc/samba/smb.conf',
+ :lens => 'Samba.lns',
+ :context => '/files/etc/samba/smb.conf',
+ :changes => ["set target[. = 'test_share'] 'test_share'"],
+ :require => 'Class[Samba::Server::Config]',
+ :notify => 'Class[Samba::Server::Service]')
+ }
+ it { is_expected.to contain_augeas('test_share-changes').with(
+ :incl => '/etc/samba/smb.conf',
+ :lens => 'Samba.lns',
+ :context => '/files/etc/samba/smb.conf',
+ :require => 'Augeas[test_share-section]',
+ :notify => 'Class[Samba::Server::Service]')
+ }
+ end
+
+ context 'with ensure set to absent' do
+ let(:title) { 'test_share' }
+ let(:params) {{
+ :ensure => 'absent'
+ }}
+
+ it { is_expected.to contain_augeas('test_share-section').with(
+ :incl => '/etc/samba/smb.conf',
+ :lens => 'Samba.lns',
+ :context => '/files/etc/samba/smb.conf',
+ :changes => ["rm target[. = 'test_share'] 'test_share'"],
+ :require => 'Class[Samba::Server::Config]',
+ :notify => 'Class[Samba::Server::Service]')
+ }
+ end
+
+ context 'with available set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :available => true,
+ }}
+ let(:change_set) { default_changes.with("available", "yes") }
+ end
+
+ context 'with available set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :available => false,
+ }}
+ let(:change_set) { default_changes.with("available", "no") }
+ end
+
+ context 'with browsable set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :browsable => true,
+ }}
+ let(:change_set) { default_changes.with("browsable", "yes") }
+ end
+
+ context 'with browsable set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :browsable => false,
+ }}
+ let(:change_set) { default_changes.with("browsable", "no") }
+ end
+
+ context 'with root_preexec set to /bin/true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :root_preexec => '/bin/true',
+ }}
+ let(:change_set) { default_changes.with("root preexec", "'/bin/true'") }
+ end
+
+ context 'with comment set to "testing testing"' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :comment => 'testing testing',
+ }}
+ let(:change_set) { default_changes.with("comment", "'testing testing'") }
+ end
+
+ context 'with copy set to "testing testing"' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :copy => 'testing testing',
+ }}
+ let(:change_set) { default_changes.with("copy", "'testing testing'") }
+ end
+
+ context 'with create_mask set to "755"' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :create_mask => '755',
+ }}
+ let(:change_set) { default_changes.with("create mask", "'755'") }
+ end
+
+ context 'with directory_mask set to "755"' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :directory_mask => '755',
+ }}
+ let(:change_set) { default_changes.with("directory mask", "'755'") }
+ end
+
+ context 'with force_create_mask set to "755"' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :force_create_mask => '755',
+ }}
+ let(:change_set) { default_changes.with("force create mask", "'755'") }
+ end
+
+ context 'with force_directory_mode set to "755"' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :force_directory_mode => '755',
+ }}
+ let(:change_set) { default_changes.with("force directory mode", "'755'") }
+ end
+
+ context 'with force_group set to "nogroup"' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :force_group => 'nogroup',
+ }}
+ let(:change_set) { default_changes.with("force group", "'nogroup'") }
+ end
+
+ context 'with force_user set to "nobody"' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :force_user => 'nobody',
+ }}
+ let(:change_set) { default_changes.with("force user", "'nobody'") }
+ end
+
+ context 'with guest_ok set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :guest_ok => true,
+ }}
+ let(:change_set) { default_changes.with("guest ok", "yes") }
+ end
+
+ context 'with guest_ok set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :guest_ok => false,
+ }}
+ let(:change_set) { default_changes.with("guest ok", "no") }
+ end
+
+ context 'with guest_only set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :guest_only => true,
+ }}
+ let(:change_set) { default_changes.with("guest only", "yes") }
+ end
+
+ context 'with guest_only set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :guest_only => false,
+ }}
+ let(:change_set) { default_changes.with("guest only", "no") }
+ end
+
+ context 'with hide_unreadable set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :hide_unreadable => true,
+ }}
+ let(:change_set) { default_changes.with("hide unreadable", "yes") }
+ end
+
+ context 'with hide_unreadable set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :hide_unreadable => false,
+ }}
+ let(:change_set) { default_changes.with("hide unreadable", "no") }
+ end
+
+ context 'with path set to /tmp' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :path => '/tmp',
+ }}
+ let(:change_set) { default_changes.with("path", "'/tmp'", nil) }
+ end
+
+ context 'with read_only set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :read_only => true,
+ }}
+ let(:change_set) { default_changes.with("read only", "yes") }
+ end
+
+ context 'with read_only set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :read_only => false,
+ }}
+ let(:change_set) { default_changes.with("read only", "no") }
+ end
+
+ context 'with public set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :public => true,
+ }}
+ let(:change_set) { default_changes.with("public", "yes") }
+ end
+
+ context 'with public set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :public => false,
+ }}
+ let(:change_set) { default_changes.with("public", "no") }
+ end
+
+ context 'with writable set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :writable => true,
+ }}
+ let(:change_set) { default_changes.with("writable", "yes") }
+ end
+
+ context 'with writable set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :writable => false,
+ }}
+ let(:change_set) { default_changes.with("writable", "no") }
+ end
+
+ context 'with printable set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :printable => true,
+ }}
+ let(:change_set) { default_changes.with("printable", "yes") }
+ end
+
+ context 'with printable set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :printable => false,
+ }}
+ let(:change_set) { default_changes.with("printable", "no") }
+ end
+
+ context 'with follow_symlinks set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :follow_symlinks => true,
+ }}
+ let(:change_set) { default_changes.with("follow symlinks", "yes") }
+ end
+
+ context 'with follow_symlinks set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :follow_symlinks => false,
+ }}
+ let(:change_set) { default_changes.with("follow symlinks", "no") }
+ end
+
+ context 'with wide_links set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :wide_links => true,
+ }}
+ let(:change_set) { default_changes.with("wide links", "yes") }
+ end
+
+ context 'with wide_links set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :wide_links => false,
+ }}
+ let(:change_set) { default_changes.with("wide links", "no") }
+ end
+
+ context 'with acl_group_control set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :acl_group_control => true,
+ }}
+ let(:change_set) { default_changes.with("acl group control", "yes") }
+ end
+
+ context 'with acl_group_control set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :acl_group_control => false,
+ }}
+ let(:change_set) { default_changes.with("acl group control", "no") }
+ end
+
+ context 'with map_acl_inherit set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :map_acl_inherit => true,
+ }}
+ let(:change_set) { default_changes.with("map acl inherit", "yes") }
+ end
+
+ context 'with map_acl_inherit set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :map_acl_inherit => false,
+ }}
+ let(:change_set) { default_changes.with("map acl inherit", "no") }
+ end
+
+ context 'with profile_acls set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :profile_acls => true,
+ }}
+ let(:change_set) { default_changes.with("profile acls", "yes") }
+ end
+
+ context 'with profile_acls set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :profile_acls => false,
+ }}
+ let(:change_set) { default_changes.with("profile acls", "no") }
+ end
+
+ context 'with store_dos_attributes set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :store_dos_attributes => true,
+ }}
+ let(:change_set) { default_changes.with("store dos attributes", "yes") }
+ end
+
+ context 'with store_dos_attributes set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :store_dos_attributes => false,
+ }}
+ let(:change_set) { default_changes.with("store dos attributes", "no") }
+ end
+
+ context 'with strict_allocate set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :strict_allocate => true,
+ }}
+ let(:change_set) { default_changes.with("strict allocate", "yes") }
+ end
+
+ context 'with strict_allocate set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :strict_allocate => false,
+ }}
+ let(:change_set) { default_changes.with("strict allocate", "no") }
+ end
+
+ context 'with valid_users set to "bill,ben"' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :valid_users => 'bill,ben',
+ }}
+ let(:change_set) { default_changes.with("valid users", "'bill,ben'") }
+ end
+
+ context 'with op_locks set to "testing"' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :op_locks => 'testing',
+ }}
+ let(:change_set) { default_changes.with("oplocks", "'testing'") }
+ end
+
+ context 'with level2_oplocks set to "testing"' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :level2_oplocks => 'testing',
+ }}
+ let(:change_set) { default_changes.with("level2 oplocks", "'testing'") }
+ end
+
+ context 'with veto_oplock_files set to "testing"' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :veto_oplock_files => 'testing',
+ }}
+ let(:change_set) { default_changes.with("veto oplock files", "'testing'") }
+ end
+
+ context 'with read_list set to "bill,ben"' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :read_list => 'bill,ben',
+ }}
+ let(:change_set) { default_changes.with("read list", "'bill,ben'") }
+ end
+
+ context 'with write_list set to "bill,ben"' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :write_list => 'bill,ben',
+ }}
+ let(:change_set) { default_changes.with("write list", "'bill,ben'") }
+ end
+
+ context 'with hide_dot_files set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :hide_dot_files => true,
+ }}
+ let(:change_set) { default_changes.with("hide dot files", "yes") }
+ end
+
+ context 'with hide_dot_files set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :hide_dot_files => false,
+ }}
+ let(:change_set) { default_changes.with("hide dot files", "no") }
+ end
+
+ context 'with root_preexec set to "/bin/test"' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :root_preexec => '/bin/test',
+ }}
+ let(:change_set) { default_changes.with("root preexec", "'/bin/test'") }
+ end
+
+ context 'with inherit_permissions set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :inherit_permissions => true,
+ }}
+ let(:change_set) { default_changes.with("inherit permissions", "yes") }
+ end
+
+ context 'with inherit_permissions set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :inherit_permissions => false,
+ }}
+ let(:change_set) { default_changes.with("inherit permissions", "no") }
+ end
+
+ context 'with inherit_acls set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :inherit_acls => true,
+ }}
+ let(:change_set) { default_changes.with("inherit acls", "yes") }
+ end
+
+ context 'with inherit_acls set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :inherit_acls => false,
+ }}
+ let(:change_set) { default_changes.with("inherit acls", "no") }
+ end
+
+ context 'with delete_readonly set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :delete_readonly => true,
+ }}
+ let(:change_set) { default_changes.with("delete readonly", "yes") }
+ end
+
+ context 'with delete_readonly set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :delete_readonly => false,
+ }}
+ let(:change_set) { default_changes.with("delete readonly", "no") }
+ end
+
+ context 'with printer_name set to "killing trees"' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :printer_name => 'killing trees',
+ }}
+ let(:change_set) { default_changes.with("printer name", "'killing trees'") }
+ end
+
+ context 'with msdfs_root set to true' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :msdfs_root => true,
+ }}
+ let(:change_set) { default_changes.with("msdfs root", "yes") }
+ end
+
+ context 'with msdfs_root set to false' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :msdfs_root => false,
+ }}
+ let(:change_set) { default_changes.with("msdfs root", "no") }
+ end
+
+ context 'with guest_account set to "killing trees"' do
+ include_examples "default share"
+ let(:params) {{
+ :ensure => 'present',
+ :guest_account => 'someone',
+ }}
+ let(:change_set) { default_changes.with("guest account", "'someone'") }
+ end
+ end
+ end
+end
diff --git a/spec/spec.opts b/spec/spec.opts
new file mode 100644
index 0000000..91cd642
--- /dev/null
+++ b/spec/spec.opts
@@ -0,0 +1,6 @@
+--format
+s
+--colour
+--loadby
+mtime
+--backtrace
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 81f98ac..51ef6aa 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,4 +1,9 @@
require 'puppetlabs_spec_helper/module_spec_helper'
+require 'rspec-puppet-facts'
+
+Dir[File.expand_path("../support/**/*.rb", __FILE__)].each { |f| require f }
+
+include RspecPuppetFacts
RSpec.configure do |c|
c.before do
@@ -6,3 +11,5 @@ RSpec.configure do |c|
Puppet.features.stubs(:root? => true)
end
end
+
+at_exit { RSpec::Puppet::Coverage.report! }
diff --git a/spec/support/augeas.rb b/spec/support/augeas.rb
new file mode 100644
index 0000000..7548684
--- /dev/null
+++ b/spec/support/augeas.rb
@@ -0,0 +1,69 @@
+require "delegate"
+
+module Augeas
+ class Change
+ attr_reader :target, :name, :delimiter
+
+ def initialize(target, name, value = nil, delimiter = "\"")
+ @target = target
+ @name = name
+ @value = value
+ @delimiter = delimiter
+ end
+
+ def to_s
+ "#{action} #{delimiter}target[. = '#{target}']/#{name}#{delimiter}#{value}"
+ end
+
+ def hash
+ [target, name, Change].hash
+ end
+
+ def ==(other)
+ other.is_a?(self.class) && [other.target, other.name] == [target, name]
+ end
+ alias_method :eql?, :==
+
+ private
+
+ def action
+ return "set" unless @value.nil?
+ "rm "
+ end
+
+ def value
+ " #{@value}" if @value
+ end
+ end
+
+ class ChangeSet
+ def initialize
+ @set = []
+ end
+
+ def <<(change)
+ index = @set.index(change) || @set.length
+ @set[index] = change
+ end
+
+ def to_a
+ changes
+ end
+
+ def changes
+ @set.map(&:to_s)
+ end
+ end
+
+ class TargetedChangeSet < DelegateClass(ChangeSet)
+ def initialize(target)
+ @target = target
+ super(ChangeSet.new)
+ end
+
+ def with(*args)
+ self << Change.new(@target, *args)
+ self
+ end
+ end
+end