| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567 |
- require 'spec_helper'
- require 'puppetx/filemapper'
- describe PuppetX::FileMapper do
- before do
- @ramtype = Puppet::Util::FileType.filetype(:ram)
- @flattype = stub 'Class<FileType<Flat>>'
- Puppet::Util::FileType.stubs(:filetype).with(:flat).returns @flattype
- end
- after :each do
- dummytype.defaultprovider = nil
- end
- let(:dummytype) do
- Puppet::Type.newtype(:dummy) do
- ensurable
- newparam(:name, :namevar => true)
- newparam(:dummy_param)
- newproperty(:dummy_property)
- end
- end
- let(:single_file_provider) do
- dummytype.provide(:single) do
- include PuppetX::FileMapper
- def self.target_files; ['/single/file/provider']; end
- def self.parse_file(filename, content)
- [{:name => 'yay', :dummy_param => :bla, :dummy_property => 'baz'}]
- end
- def select_file; '/single/file/provider'; end
- def self.format_file(filename, providers); 'flushback'; end
- end
- end
- let(:multiple_file_provider) do
- dummytype.provide(:multiple, :resource_type => dummytype) do
- include PuppetX::FileMapper
- def self.target_files; ['/multiple/file/provider-one', '/multiple/file/provider-two']; end
- def self.parse_file(filename, content)
- case filename
- when '/multiple/file/provider-one' then [{:name => 'yay', :dummy_param => :bla, :dummy_property => 'baz'}]
- when '/multiple/file/provider-two' then [{:name => 'whee', :dummy_param => :ohai, :dummy_property => 'wat'}]
- end
- end
- def select_file; '/multiple/file/provider-flush'; end
- def self.format_file(filename, providers); 'multiple flush content'; end
- end
- end
- let(:params_yay) { {:name => 'yay', :dummy_param => :bla, :dummy_property => 'baz'} }
- let(:params_whee) { {:name => 'whee', :dummy_param => :ohai, :dummy_property => 'wat'} }
- let(:params_nope) { {:name => 'dead', :dummy_param => :nofoo, :dummy_property => 'sadprop'} }
- after :each do
- dummytype.provider_hash.clear
- end
- describe 'when included' do
- it 'should initialize the provider as not failed' do
- provider = dummytype.provide(:foo) { include PuppetX::FileMapper }
- provider.should_not be_failed
- end
- describe 'when generating attr_accessors' do
- subject { multiple_file_provider.new(params_yay) }
- describe 'for properties' do
- it { should respond_to :dummy_property }
- it { should respond_to :dummy_property= }
- it { should respond_to :ensure }
- it { should respond_to :ensure= }
- end
- describe 'for parameters' do
- it { should_not respond_to :dummy_param }
- it { should_not respond_to :dummy_param= }
- end
- end
- end
- describe 'when validating the class' do
- describe "and it doesn't implement self.target_files" do
- subject do
- dummytype.provide(:incomplete) { include PuppetX::FileMapper }
- end
- it { expect { subject.validate_class! }.to raise_error Puppet::DevError, /self.target_files/ }
- end
- describe "and it doesn't implement self.parse_file" do
- subject do
- dummytype.provide(:incomplete) do
- include PuppetX::FileMapper
- def self.target_files; end
- end
- end
- it { expect { subject.validate_class! }.to raise_error Puppet::DevError, /self.parse_file/}
- end
- describe "and it doesn't implement #select_file" do
- subject do
- dummytype.provide(:incomplete) do
- include PuppetX::FileMapper
- def self.target_files; end
- def self.parse_file(filename, content); end
- def self.format_file(filename, resources); 'foo'; end
- end
- end
- it { expect { subject.validate_class! }.to raise_error Puppet::DevError, /#select_file/}
- end
- describe "and it doesn't implement self.format_file" do
- subject do
- dummytype.provide(:incomplete) do
- include PuppetX::FileMapper
- def self.target_files; end
- def self.parse_file(filename, content); end
- def select_file; '/single/file/provider'; end
- end
- end
- it { expect { subject.validate_class! }.to raise_error Puppet::DevError, /self\.format_file/}
- end
- end
- describe 'when reading' do
- describe 'a single file' do
- subject { single_file_provider }
- it 'should generate a filetype for that file' do
- @flattype.expects(:new).with('/single/file/provider').once.returns @ramtype.new('/single/file/provider')
- subject.load_all_providers_from_disk
- end
- it 'should parse each file' do
- stub_file = stub(:read => 'file contents')
- @flattype.stubs(:new).with('/single/file/provider').once.returns stub_file
- subject.expects(:parse_file).with('/single/file/provider', 'file contents').returns []
- subject.load_all_providers_from_disk
- end
- it 'should return the generated array' do
- @flattype.stubs(:new).with('/single/file/provider').once.returns @ramtype.new('/single/file/provider')
- subject.load_all_providers_from_disk.should == [params_yay]
- end
- end
- describe 'multiple files' do
- subject { multiple_file_provider }
- it 'should generate a filetype for each file' do
- @flattype.expects(:new).with('/multiple/file/provider-one').once.returns(stub(:read => 'barbar'))
- @flattype.expects(:new).with('/multiple/file/provider-two').once.returns(stub(:read => 'bazbaz'))
- subject.load_all_providers_from_disk
- end
- describe 'when parsing' do
- before do
- @flattype.stubs(:new).with('/multiple/file/provider-one').once.returns(stub(:read => 'barbar'))
- @flattype.stubs(:new).with('/multiple/file/provider-two').once.returns(stub(:read => 'bazbaz'))
- end
- it 'should parse each file' do
- subject.expects(:parse_file).with('/multiple/file/provider-one', 'barbar').returns []
- subject.expects(:parse_file).with('/multiple/file/provider-two', 'bazbaz').returns []
- subject.load_all_providers_from_disk
- end
- it 'should return the generated array' do
- data = subject.load_all_providers_from_disk
- data.should be_include(params_yay)
- data.should be_include(params_whee)
- end
- end
- end
- describe 'validating input' do
- subject { multiple_file_provider }
- before do
- @flattype.stubs(:new).with('/multiple/file/provider-one').once.returns(stub(:read => 'barbar'))
- @flattype.stubs(:new).with('/multiple/file/provider-two').once.returns(stub(:read => 'bazbaz'))
- end
- it 'should ensure that retrieved values are in the right format' do
- subject.stubs(:parse_file).with('/multiple/file/provider-one', 'barbar').returns Hash.new
- subject.stubs(:parse_file).with('/multiple/file/provider-two', 'bazbaz').returns Hash.new
- expect { subject.load_all_providers_from_disk }.to raise_error Puppet::DevError, /expected.*to return an Array, got a Hash/
- end
- end
- end
- describe 'when generating instances' do
- subject { multiple_file_provider }
- before do
- @flattype.stubs(:new).with('/multiple/file/provider-one').once.returns(stub(:read => 'barbar'))
- @flattype.stubs(:new).with('/multiple/file/provider-two').once.returns(stub(:read => 'bazbaz'))
- end
- it 'should generate a provider instance from hashes' do
- params_yay.merge!({:provider => subject.name})
- params_whee.merge!({:provider => subject.name})
- subject.expects(:new).with(params_yay.merge({:ensure => :present})).returns stub()
- subject.expects(:new).with(params_whee.merge({:ensure => :present})).returns stub()
- subject.instances
- end
- it 'should generate a provider instance for each hash' do
- provs = subject.instances
- provs.should have(2).items
- provs.each { |prov| prov.should be_a_kind_of(Puppet::Provider)}
- end
- [
- {:name => 'yay', :dummy_property => 'baz'},
- {:name => 'whee', :dummy_property => 'wat'},
- ].each do |values|
- it "should match hash values to provider properties for #{values[:name]}" do
- provs = subject.instances
- prov = provs.find {|prov| prov.name == values[:name]}
- values.each_pair { |property, value| prov.send(property).should == value }
- end
- end
- end
- describe 'when prefetching' do
- subject { multiple_file_provider }
- let(:provider_yay) { subject.new(params_yay.merge({:provider => subject.name})) }
- let(:provider_whee) { subject.new(params_whee.merge({:provider => subject.name})) }
- before do
- subject.stubs(:instances).returns [provider_yay, provider_whee]
- end
- let(:resources) do
- [params_yay, params_whee, params_nope].inject({}) do |h, params|
- h[params[:name]] = dummytype.new(params)
- h
- end
- end
- it "should update resources with existing providers" do
- resources['yay'].expects(:provider=).with(provider_yay)
- resources['whee'].expects(:provider=).with(provider_whee)
- subject.prefetch(resources)
- end
- it "should not update resources that don't have providers" do
- resources['dead'].expects(:provider=).never
- subject.prefetch(resources)
- end
- end
- describe 'on resource state change' do
- subject { multiple_file_provider }
- before do
- dummytype.defaultprovider = subject
- subject.any_instance.stubs(:resource_type).returns dummytype
- end
- describe 'from absent to present' do
- let(:resource) { dummytype.new(:name => 'boom', :dummy_property => 'bang') }
- it 'should mark the related file as dirty' do
- subject.mapped_files['/multiple/file/provider-flush'][:dirty].should be_false
- resource.property(:ensure).sync
- subject.mapped_files['/multiple/file/provider-flush'][:dirty].should be_true
- end
- end
- describe 'from present to absent' do
- it 'should mark the related file as dirty' do
- resource = dummytype.new(:name => 'boom', :dummy_property => 'bang', :ensure => :absent)
- subject.mapped_files['/multiple/file/provider-flush'][:dirty].should be_false
- resource.property(:ensure).sync
- subject.mapped_files['/multiple/file/provider-flush'][:dirty].should be_true
- end
- end
- describe 'on a property' do
- let(:resource) { resource = dummytype.new(params_yay) }
- before do
- prov = subject.new(params_yay.merge({:ensure => :present}))
- subject.stubs(:instances).returns [prov]
- subject.prefetch({params_yay[:name] => resource})
- end
- it 'should mark the related file as dirty' do
- subject.mapped_files['/multiple/file/provider-flush'][:dirty].should be_false
- resource.property(:dummy_property).value = 'new value'
- resource.property(:dummy_property).sync
- subject.mapped_files['/multiple/file/provider-flush'][:dirty].should be_true
- end
- end
- describe 'on a parameter' do
- let(:resource) { resource = dummytype.new(params_yay) }
- before do
- prov = subject.new(params_yay.merge({:ensure => :present}))
- subject.stubs(:instances).returns [prov]
- subject.prefetch({params_yay[:name] => resource})
- end
- it 'should not mark the related file as dirty' do
- subject.mapped_files['/multiple/file/provider-flush'][:dirty].should be_false
- resource.parameter(:dummy_param).value = 'new value'
- resource.flush
- subject.mapped_files['/multiple/file/provider-flush'][:dirty].should be_false
- end
- end
- end
- describe 'when determining whether to flush' do
- subject { multiple_file_provider }
- before do
- dummytype.defaultprovider = subject
- subject.any_instance.stubs(:resource_type).returns dummytype
- end
- let(:resource) { resource = dummytype.new(params_yay) }
- it 'should refuse to flush if the provider is in a failed state' do
- subject.dirty_file!('/multiple/file/provider-flush')
- subject.failed!
- subject.expects(:collect_resources_for_provider).never
- resource.flush
- end
- it 'should use the provider instance method `select_file` to locate the destination file' do
- resource.provider.expects(:select_file).returns '/multiple/file/provider-flush'
- resource.property(:dummy_property).value = 'zoom'
- resource.property(:dummy_property).sync
- end
- it 'should trigger the class dirty_file! method' do
- subject.expects(:dirty_file!).with('/multiple/file/provider-flush')
- resource.property(:dummy_property).value = 'zoom'
- resource.property(:dummy_property).sync
- end
- end
- describe 'when flushing' do
- subject { multiple_file_provider }
- let(:newtype) { @ramtype.new('/multiple/file/provider-flush') }
- let(:resource) { resource = dummytype.new(params_yay) }
- before { newtype.stubs(:backup) }
- it 'should forward provider#flush to the class' do
- subject.expects(:flush_file).with('/multiple/file/provider-flush')
- resource.flush
- end
- it 'should generate filetypes for new files' do
- subject.dirty_file!('/multiple/file/provider-flush')
- @flattype.expects(:new).with('/multiple/file/provider-flush').returns newtype
- resource.flush
- end
- it 'should use existing filetypes for existing files' do
- stub_filetype = stub()
- stub_filetype.expects(:backup)
- stub_filetype.expects(:write)
- subject.dirty_file!('/multiple/file/provider-flush')
- subject.mapped_files['/multiple/file/provider-flush'][:filetype] = stub_filetype
- resource.flush
- end
- it 'should trigger a flush on dirty files' do
- subject.dirty_file!('/multiple/file/provider-flush')
- subject.expects(:perform_write).with('/multiple/file/provider-flush', 'multiple flush content')
- resource.flush
- end
- it 'should not flush clean files' do
- subject.expects(:perform_write).never
- resource.flush
- end
- end
- describe 'validating the file contents to flush' do
- subject { multiple_file_provider }
- before do
- subject.stubs(:format_file).returns ['definitely', 'not', 'of', 'class', 'String']
- subject.dirty_file!('/multiple/file/provider-flush')
- end
- it 'should raise an error if given an invalid value for file contents' do
- subject.expects(:perform_write).with('/multiple/file/provider-flush', %w{invalid data}).never
- expect { subject.flush_file('/multiple/file/provider-flush') }.to raise_error Puppet::DevError, /expected .* to return a String, got a Array/
- end
- end
- describe 'when unlinking empty files' do
- subject { multiple_file_provider }
- let(:newtype) { @ramtype.new('/multiple/file/provider-flush') }
- before do
- subject.unlink_empty_files = true
- newtype.stubs(:backup)
- File.stubs(:unlink)
- end
- describe 'with empty file contents' do
- before do
- subject.dirty_file!('/multiple/file/provider-flush')
- @flattype.stubs(:new).with('/multiple/file/provider-flush').returns newtype
- File.stubs(:exist?).with('/multiple/file/provider-flush').returns true
- subject.stubs(:format_file).returns ''
- end
- it 'should back up the file' do
- newtype.expects(:backup)
- subject.flush_file('/multiple/file/provider-flush')
- end
- it 'should remove the file' do
- File.expects(:unlink).with('/multiple/file/provider-flush')
- subject.flush_file('/multiple/file/provider-flush')
- end
- it 'should not write to the file' do
- subject.expects(:perform_write).with('/multiple/file/provider-flush', '').never
- subject.flush_file('/multiple/file/provider-flush')
- end
- end
- describe 'with empty file contents and no destination file' do
- before do
- subject.dirty_file!('/multiple/file/provider-flush')
- @flattype.stubs(:new).with('/multiple/file/provider-flush').returns newtype
- File.stubs(:exist?).with('/multiple/file/provider-flush').returns false
- subject.stubs(:format_file).returns ''
- end
- it 'should not try to remove the file' do
- File.expects(:exist?).with('/multiple/file/provider-flush').returns false
- File.expects(:unlink).never
- subject.flush_file('/multiple/file/provider-flush')
- end
- it 'should not try to back up the file' do
- newtype.expects(:backup).never
- subject.flush_file('/multiple/file/provider-flush')
- end
- end
- describe 'with a non-empty file' do
- before do
- subject.dirty_file!('/multiple/file/provider-flush')
- @flattype.stubs(:new).with('/multiple/file/provider-flush').returns newtype
- File.stubs(:exist?).with('/multiple/file/provider-flush').returns true
- subject.stubs(:format_file).returns 'not empty'
- end
- it 'should not remove the file' do
- File.expects(:unlink).never
- subject.flush_file('/multiple/file/provider-flush')
- end
- end
- end
- describe 'flush hooks' do
- subject { multiple_file_provider }
- before :each do
- subject.dirty_file!('/multiple/file/provider-flush')
- end
- let(:newtype) { @ramtype.new('/multiple/file/provider-flush') }
- it 'should be called in order' do
- seq = sequence('flush')
- subject.expects(:respond_to?).with(:pre_flush_hook).returns true
- subject.expects(:respond_to?).with(:post_flush_hook).returns true
- subject.expects(:pre_flush_hook).with('/multiple/file/provider-flush').in_sequence(seq)
- subject.expects(:perform_write).with('/multiple/file/provider-flush', 'multiple flush content').in_sequence(seq)
- subject.expects(:post_flush_hook).with('/multiple/file/provider-flush').in_sequence(seq)
- subject.flush_file '/multiple/file/provider-flush'
- end
- it 'should call post_flush_hook even if an exception is raised' do
- subject.stubs(:respond_to?).with(:pre_flush_hook).returns false
- subject.stubs(:respond_to?).with(:post_flush_hook).returns true
- subject.expects(:perform_write).with('/multiple/file/provider-flush', 'multiple flush content').raises RuntimeError
- subject.expects(:post_flush_hook)
- expect { subject.flush_file '/multiple/file/provider-flush' }.to raise_error RuntimeError
- end
- end
- describe 'when formatting resources for flushing' do
- let(:provider_class) { multiple_file_provider }
- let(:new_resource) { dummytype.new(params_yay) }
- let(:current_provider) { provider_class.new(params_whee) }
- let(:current_resource) { dummytype.new(params_whee) }
- let(:remove_provider) { provider_class.new(params_nope) }
- let(:remove_resource) { dummytype.new(params_nope.merge({:ensure => :absent})) }
- let(:unmanaged_provider) { provider_class.new(:name => 'unmanaged_resource', :dummy_param => 'zoom', :dummy_property => 'squid', :ensure => :present) }
- let(:provider_stubs) { [current_provider, remove_provider, unmanaged_provider] }
- let(:resource_stubs) { [new_resource, current_resource, remove_resource] }
- before do
- dummytype.defaultprovider = provider_class
- provider_class.any_instance.stubs(:resource_type).returns dummytype
- provider_class.stubs(:instances).returns provider_stubs
- provider_class.prefetch(resource_stubs.inject({}) { |h, r| h[r.name] = r; h})
- # Pretend that we're the resource harness and apply the ensure param
- resource_stubs.each { |r| r.property(:ensure).sync }
- end
- it 'should collect all resources for a given file' do
- provider_class.expects(:collect_providers_for_file).with('/multiple/file/provider-flush').returns []
- provider_class.stubs(:perform_write)
- provider_class.flush_file('/multiple/file/provider-flush')
- end
- describe 'and selecting' do
- subject { multiple_file_provider.collect_providers_for_file('/multiple/file/provider-flush').map(&:name) }
- describe 'present resources' do
- it { should be_include 'yay' }
- it { should be_include 'whee' }
- it { should be_include 'unmanaged_resource' }
- end
- describe 'absent resources' do
- it { should_not be_include 'nope' }
- end
- end
- end
- end
|