#!/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