diff options
-rw-r--r-- | lib/puppet/util/storage.rb | 4 | ||||
-rwxr-xr-x | spec/unit/util/storage.rb | 237 |
2 files changed, 241 insertions, 0 deletions
diff --git a/lib/puppet/util/storage.rb b/lib/puppet/util/storage.rb index 9358a28e9..dc4e9cd71 100644 --- a/lib/puppet/util/storage.rb +++ b/lib/puppet/util/storage.rb @@ -6,6 +6,10 @@ class Puppet::Util::Storage include Singleton include Puppet::Util + def self.state + return @@state + end + def initialize self.class.load end diff --git a/spec/unit/util/storage.rb b/spec/unit/util/storage.rb new file mode 100755 index 000000000..55d1d1f61 --- /dev/null +++ b/spec/unit/util/storage.rb @@ -0,0 +1,237 @@ +#!/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() + 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 |