diff options
author | Luke Kanies <luke@madstop.com> | 2008-05-07 18:28:35 -0500 |
---|---|---|
committer | Luke Kanies <luke@madstop.com> | 2008-05-07 18:28:35 -0500 |
commit | 68d8d0ae0686939d94dae8ccc70e5582187335dc (patch) | |
tree | cee56d0235229b4682ae1f95e78351a7cb83c7d4 | |
parent | e936ef2426464b901638bf338c1c42590245b58f (diff) | |
download | puppet-68d8d0ae0686939d94dae8ccc70e5582187335dc.tar.gz puppet-68d8d0ae0686939d94dae8ccc70e5582187335dc.tar.xz puppet-68d8d0ae0686939d94dae8ccc70e5582187335dc.zip |
Adding a module for handling caching information.
I keep having issues with integration tests keeping
cached values around, and this module should hopefully
give us a single place to invalidate all caches, thus
making testing this much easier.
-rw-r--r-- | lib/puppet/util/cacher.rb | 67 | ||||
-rwxr-xr-x | spec/unit/util/cacher.rb | 142 |
2 files changed, 209 insertions, 0 deletions
diff --git a/lib/puppet/util/cacher.rb b/lib/puppet/util/cacher.rb new file mode 100644 index 000000000..fbab9ab42 --- /dev/null +++ b/lib/puppet/util/cacher.rb @@ -0,0 +1,67 @@ +module Puppet::Util::Cacher + # Cause all cached values to be considered invalid. + def self.invalidate + @timestamp = Time.now + end + + def self.valid?(timestamp) + unless defined?(@timestamp) and @timestamp + @timestamp = Time.now + return true + end + return timestamp >= @timestamp + end + + def self.extended(other) + other.extend(InstanceMethods) + end + + def self.included(other) + other.extend(ClassMethods) + other.send(:include, InstanceMethods) + end + + module ClassMethods + private + + def cached_attr(name, &block) + define_method(name) do + cache(name, &block) + end + end + end + + module InstanceMethods + private + + def cache(name, &block) + unless defined?(@cacher_caches) and @cacher_caches + @cacher_caches = Cache.new + end + + @cacher_caches.value(name, &block) + end + end + + class Cache + attr_reader :timestamp, :caches + + def initialize + @timestamp = Time.now + @caches = {} + end + + def value(name) + raise ArgumentError, "You must provide a block when using the cache" unless block_given? + + @caches.clear unless Puppet::Util::Cacher.valid?(@timestamp) + + # Use 'include?' here rather than testing for truth, so we + # can cache false values. + unless @caches.include?(name) + @caches[name] = yield + end + @caches[name] + end + end +end diff --git a/spec/unit/util/cacher.rb b/spec/unit/util/cacher.rb new file mode 100755 index 000000000..954de380d --- /dev/null +++ b/spec/unit/util/cacher.rb @@ -0,0 +1,142 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/util/cacher' + +class CacheClassTest + include Puppet::Util::Cacher + + cached_attr(:testing) { Time.now } + + def sa_cache + cache(:ca_cache) { Time.now } + end +end + +class CacheInstanceTest + extend Puppet::Util::Cacher + + def self.sa_cache + cache(:ca_cache) { Time.now } + end +end + +describe "a cacher user using cached values", :shared => true do + it "should use the block to generate a new value if none is present" do + now = Time.now + Time.stubs(:now).returns now + @object.sa_cache.should equal(now) + end + + it "should not consider cached false values to be missing values" do + Puppet::Util::Cacher.stubs(:valid?).returns true + + # This is only necessary in the class, since it has this value kicking + # around. + @object.instance_variable_set("@cacher_caches", nil) + Time.stubs(:now).returns false + @object.sa_cache + @object.sa_cache.should be_false + end + + it "should return cached values if they are still valid" do + Puppet::Util::Cacher.stubs(:valid?).returns true + + @object.sa_cache.should equal(@object.sa_cache) + end + + it "should use the block to generate new values if the cached values are invalid" do + Puppet::Util::Cacher.stubs(:valid?).returns false + + @object.sa_cache.should_not equal(@object.sa_cache) + end +end + +describe Puppet::Util::Cacher do + before do + Puppet::Util::Cacher.invalidate + end + after do + Puppet::Util::Cacher.invalidate + end + + it "should have a method for invalidating caches" do + Puppet::Util::Cacher.should respond_to(:invalidate) + end + + it "should have a method for determining whether a cached value is valid" do + Puppet::Util::Cacher.should respond_to(:valid?) + end + + it "should consider cached values valid if the cached value was created and there was never an invalidation" do + Puppet::Util::Cacher.instance_variable_set("@timestamp", nil) + + Puppet::Util::Cacher.should be_valid(Time.now) + end + + it "should consider cached values valid if the cached value was created since the last invalidation" do + Puppet::Util::Cacher.invalidate + + Puppet::Util::Cacher.should be_valid(Time.now + 1) + end + + it "should consider cached values invalid if the cache was invalidated after the cached value was created" do + Puppet::Util::Cacher.invalidate + + Puppet::Util::Cacher.should_not be_valid(Time.now - 1) + end + + describe "when used to extend a class" do + before do + @object = CacheClassTest.new + end + + it_should_behave_like "a cacher user using cached values" + + it "should provide a class method for defining cached attributes" do + CacheClassTest.private_methods.should be_include("cached_attr") + end + + describe "and defining cached attributes" do + it "should create an accessor for the cached attribute" do + @object.should respond_to(:testing) + end + + it "should return a value calculated from the provided block" do + time = Time.now + Time.stubs(:now).returns time + @object.testing.should equal(time) + end + + it "should return the cached value from the getter if the value is still valid" do + value = @object.testing + Puppet::Util::Cacher.expects(:valid?).returns true + @object.testing.should equal(value) + end + + it "should regenerate and return a new value using the provided block if the value is no longer valid" do + value = @object.testing + Puppet::Util::Cacher.expects(:valid?).returns false + @object.testing.should_not equal(value) + end + end + + it "should provide a private instance method for caching values" do + @object.private_methods.should be_include("cache") + end + + end + + describe "when included in a class" do + before do + @object = CacheInstanceTest + end + + it "should provide a private instance method for caching values" do + CacheInstanceTest.private_methods.should be_include("cache") + end + + it_should_behave_like "a cacher user using cached values" + end +end |