#!/usr/bin/env ruby

require File.dirname(__FILE__) + '/../../spec_helper'

require 'yaml'
require 'tempfile'

require 'puppet/util/storage'

describe Puppet::Util::Storage do
    before(:all) do
        Puppet[:statedir] = Dir.tmpdir()
        puts "before(:all)"
    end

    before(:each) do
        Puppet::Util::Storage.clear()
    end

    describe "when caching a symbol" do
        it "should return an empty hash" do
            Puppet::Util::Storage.cache(:yayness).should == {}
            Puppet::Util::Storage.cache(:more_yayness).should == {}
        end

        it "should add the symbol to its internal state" do
            Puppet::Util::Storage.cache(:yayness)
            Puppet::Util::Storage.state().should == {:yayness=>{}}
        end

        it "should not clobber existing state when caching additional objects" do
            Puppet::Util::Storage.cache(:yayness)
            Puppet::Util::Storage.state().should == {:yayness=>{}}
            Puppet::Util::Storage.cache(:bubblyness)
            Puppet::Util::Storage.state().should == {:yayness=>{},:bubblyness=>{}}
        end
    end

    describe "when caching a Puppet::Type" do
        before(:all) do
            @file_test = Puppet.type(:file).create(:name => "/yayness", :check => %w{checksum type})
            @exec_test = Puppet.type(:exec).create(:name => "/bin/ls /yayness")
        end

        it "should return an empty hash" do
            Puppet::Util::Storage.cache(@file_test).should == {}
            Puppet::Util::Storage.cache(@exec_test).should == {}
        end

        it "should add the resource ref to its internal state" do
            Puppet::Util::Storage.state().should == {}
            Puppet::Util::Storage.cache(@file_test)
            Puppet::Util::Storage.state().should == {"File[/yayness]"=>{}}
            Puppet::Util::Storage.cache(@exec_test)
            Puppet::Util::Storage.state().should == {"File[/yayness]"=>{}, "Exec[/bin/ls /yayness]"=>{}}
        end
    end

    describe "when caching invalid objects" do
        before(:all) do
            @bogus_objects = [ {}, [], "foo", 42, nil, Tempfile.new('storage_test') ]
        end

        it "should raise an ArgumentError" do
            @bogus_objects.each do |object|
                proc { Puppet::Util::Storage.cache(object) }.should raise_error()
            end
        end

        it "should not add anything to its internal state" do
            @bogus_objects.each do |object|
                begin
                    Puppet::Util::Storage.cache(object)
                rescue
                    Puppet::Util::Storage.state().should == {}
                end
            end
        end
    end

    it "should clear its internal state when clear() is called" do
        Puppet::Util::Storage.cache(:yayness)
        Puppet::Util::Storage.state().should == {:yayness=>{}}
        Puppet::Util::Storage.clear()
        Puppet::Util::Storage.state().should == {}
    end

    describe "when loading from the state file" do
        describe "when the state file/directory does not exist" do
            before(:each) do
                transient = Tempfile.new('storage_test')
                @path = transient.path()
                transient.close!()
            end

            it "should not fail to load()" do
                FileTest.exists?(@path).should be_false()
                Puppet[:statedir] = @path
                proc { Puppet::Util::Storage.load() }.should_not raise_error()
                Puppet[:statefile] = @path
                proc { Puppet::Util::Storage.load() }.should_not raise_error()
            end

            it "should not lose its internal state when load() is called" do
                FileTest.exists?(@path).should be_false()

                Puppet::Util::Storage.cache(:yayness)
                Puppet::Util::Storage.state().should == {:yayness=>{}}

                Puppet[:statefile] = @path
                proc { Puppet::Util::Storage.load() }.should_not raise_error()

                Puppet::Util::Storage.state().should == {:yayness=>{}}
            end
        end

        describe "when the state file/directory exists" do
            before(:each) do
                @state_file = Tempfile.new('storage_test')
                @saved_statefile = Puppet[:statefile]
                Puppet[:statefile] = @state_file.path()
            end

            it "should overwrite its internal state if load() is called" do
                # Should the state be overwritten even if Puppet[:statefile] is not valid YAML?
                Puppet::Util::Storage.cache(:yayness)
                Puppet::Util::Storage.state().should == {:yayness=>{}}

                proc { Puppet::Util::Storage.load() }.should_not raise_error()
                Puppet::Util::Storage.state().should == {}
            end

            it "should restore its internal state if the state file contains valid YAML" do
                test_yaml = {'File["/yayness"]'=>{"name"=>{:a=>:b,:c=>:d}}}
                YAML.expects(:load).returns(test_yaml)

                proc { Puppet::Util::Storage.load() }.should_not raise_error()
                Puppet::Util::Storage.state().should == test_yaml
            end

            it "should initialize with a clear internal state if the state file does not contain valid YAML" do
                @state_file.write(:booness)

                proc { Puppet::Util::Storage.load() }.should_not raise_error()
                Puppet::Util::Storage.state().should == {}
            end

            it "should raise an error if the state file does not contain valid YAML and cannot be renamed" do
                @state_file.write(:booness)
                File.chmod(0000, @state_file.path())

                proc { Puppet::Util::Storage.load() }.should raise_error()
            end

            it "should attempt to rename the state file if the file is corrupted" do
                # We fake corruption by causing YAML.load to raise an exception
                YAML.expects(:load).raises(Puppet::Error)
                File.expects(:rename).at_least_once

                proc { Puppet::Util::Storage.load() }.should_not raise_error()
            end

            it "should fail gracefully on load() if the state file is not a regular file" do
                @state_file.close!()
                Dir.mkdir(Puppet[:statefile])
                File.expects(:rename).returns(0)

                proc { Puppet::Util::Storage.load() }.should_not raise_error()

                Dir.rmdir(Puppet[:statefile])
            end

            it "should fail gracefully on load() if it cannot get a read lock on the state file" do
                Puppet::Util.expects(:readlock).yields(false)
                test_yaml = {'File["/yayness"]'=>{"name"=>{:a=>:b,:c=>:d}}}
                YAML.expects(:load).returns(test_yaml)

                proc { Puppet::Util::Storage.load() }.should_not raise_error()
                Puppet::Util::Storage.state().should == test_yaml
            end

            after(:each) do
                @state_file.close!()
                Puppet[:statefile] = @saved_statefile
            end
        end
    end

    describe "when storing to the state file" do
        before(:each) do
            @state_file = Tempfile.new('storage_test')
            @saved_statefile = Puppet[:statefile]
            Puppet[:statefile] = @state_file.path()
        end

        it "should create the state file if it does not exist" do
            @state_file.close!()
            FileTest.exists?(Puppet[:statefile]).should be_false()
            Puppet::Util::Storage.cache(:yayness)

            proc { Puppet::Util::Storage.store() }.should_not raise_error()
            FileTest.exists?(Puppet[:statefile]).should be_true()
        end

        it "should raise an exception if the state file is not a regular file" do
            @state_file.close!()
            Dir.mkdir(Puppet[:statefile])
            Puppet::Util::Storage.cache(:yayness)

            proc { Puppet::Util::Storage.store() }.should raise_error()

            Dir.rmdir(Puppet[:statefile])
        end

        it "should raise an exception if it cannot get a write lock on the state file" do
            Puppet::Util.expects(:writelock).yields(false)
            Puppet::Util::Storage.cache(:yayness)

            proc { Puppet::Util::Storage.store() }.should raise_error()
        end

        it "should load() the same information that it store()s" do
            Puppet::Util::Storage.cache(:yayness)

            Puppet::Util::Storage.state().should == {:yayness=>{}}
            proc { Puppet::Util::Storage.store() }.should_not raise_error()
            Puppet::Util::Storage.clear()
            Puppet::Util::Storage.state().should == {}
            proc { Puppet::Util::Storage.load() }.should_not raise_error()
            Puppet::Util::Storage.state().should == {:yayness=>{}}
        end

        after(:each) do
            @state_file.close!()
            Puppet[:statefile] = @saved_statefile
        end
    end
end

Output:
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -S vendor/gems/rspec/bin/spec -O spec/spec.opts  spec/unit/util/storage.rb

Puppet::Util::Storage
before(:all)
- should clear its internal state when clear() is called

Puppet::Util::Storage when caching a symbol
before(:all)
- should return an empty hash
- should add the symbol to its internal state
- should not clobber existing state when caching additional objects

Puppet::Util::Storage when caching a Puppet::Type
before(:all)
- should return an empty hash
- should add the resource ref to its internal state

Puppet::Util::Storage when caching invalid objects
before(:all)
- should raise an ArgumentError
- should not add anything to its internal state

Puppet::Util::Storage when loading from the state file when the state file/directory does not exist
before(:all)
- should not fail to load()
- should not lose its internal state when load() is called (FAILED - 1)

Puppet::Util::Storage when loading from the state file when the state file/directory exists
before(:all)
- should overwrite its internal state if load() is called
- should restore its internal state if the state file contains valid YAML
- should initialize with a clear internal state if the state file does not contain valid YAML
- should raise an error if the state file does not contain valid YAML and cannot be renamed
- should attempt to rename the state file if the file is corrupted
- should fail gracefully on load() if the state file is not a regular file
- should fail gracefully on load() if it cannot get a read lock on the state file

Puppet::Util::Storage when storing to the state file
before(:all)
- should create the state file if it does not exist
- should raise an exception if the state file is not a regular file
- should raise an exception if it cannot get a write lock on the state file
- should load() the same information that it store()s

1)
'Puppet::Util::Storage when loading from the state file when the state file/directory does not exist should not lose its internal state when load() is called' FAILED
expected: {:yayness=>{}},
     got: {"File[/Users/plathrop/.puppet/var/lib]"=>{:checked=>Wed May 21 16:15:50 -0700 2008}, "File[/Users/plathrop/.puppet/var/log]"=>{:checked=>Wed May 21 16:15:50 -0700 2008}, "File[/Users/plathrop/.puppet/var]"=>{:checked=>Wed May 21 16:15:50 -0700 2008}, "File[/Users/plathrop/.puppet/ssl/private]"=>{:checked=>Wed May 21 16:15:50 -0700 2008}, "File[/Users/plathrop/.puppet/ssl/public_keys]"=>{:checked=>Wed May 21 16:15:50 -0700 2008}, "File[/var/folders/Di/Diypa5iWF3iUiCOII-cngk+++TM/-Tmp-/storage_test.1815.1]"=>{:synced=>Wed May 21 16:15:50 -0700 2008, :checked=>Wed May 21 16:15:50 -0700 2008}, "Schedule[weekly]"=>{:checked=>Wed May 21 16:15:50 -0700 2008}, "Filebucket[puppet]"=>{:checked=>Wed May 21 16:15:50 -0700 2008}, "File[/Users/plathrop/.puppet/ssl/certificate_requests]"=>{:checked=>Wed May 21 16:15:50 -0700 2008}, "File[/Users/plathrop/.puppet]"=>{:checked=>Wed May 21 16:15:50 -0700 2008}, :yayness=>{}, "File[/Users/plathrop/.puppet/var/run]"=>{:checked=>Wed May 21 16:15:50 -0700 2008}, "Schedule[hourly]"=>{:checked=>Wed May 21 16:15:50 -0700 2008}, "Schedule[daily]"=>{:checked=>Wed May 21 16:15:50 -0700 2008}, "Schedule[monthly]"=>{:checked=>Wed May 21 16:15:50 -0700 2008}, "File[/Users/plathrop/.puppet/ssl/private_keys]"=>{:checked=>Wed May 21 16:15:50 -0700 2008}, "Schedule[puppet]"=>{:checked=>Wed May 21 16:15:50 -0700 2008}, "File[/Users/plathrop/.puppet/ssl/certs]"=>{:checked=>Wed May 21 16:15:50 -0700 2008}, "File[/Users/plathrop/.puppet/ssl]"=>{:checked=>Wed May 21 16:15:50 -0700 2008}, "Schedule[never]"=>{:checked=>Wed May 21 16:15:50 -0700 2008}} (using ==)
./spec/unit/util/storage.rb:113:
/Users/plathrop/projects/open-source/puppet/spec/monkey_patches/add_confine_and_runnable_to_rspec_dsl.rb:19:in `run'
/Users/plathrop/projects/open-source/puppet/spec/monkey_patches/add_confine_and_runnable_to_rspec_dsl.rb:17:in `each'
/Users/plathrop/projects/open-source/puppet/spec/monkey_patches/add_confine_and_runnable_to_rspec_dsl.rb:17:in `run'

Finished in 0.390422 seconds

21 examples, 1 failure