summaryrefslogtreecommitdiffstats
path: root/spec/unit/util/cacher_spec_spec.rb
blob: eb8515e4d63076b352a2b87806a4b5e7c192048f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#!/usr/bin/env ruby

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

require 'puppet/util/cacher'

class ExpirerTest
    include Puppet::Util::Cacher::Expirer
end

class CacheTest
    @@init_count = 0

    include Puppet::Util::Cacher
    cached_attr(:instance_cache) { Time.now }
end

describe Puppet::Util::Cacher::Expirer do
    before do
        @expirer = ExpirerTest.new
    end

    it "should be able to test whether a timestamp is expired" do
        @expirer.should respond_to(:dependent_data_expired?)
    end

    it "should be able to expire all values" do
        @expirer.should respond_to(:expire)
    end

    it "should consider any value to be valid if it has never been expired" do
        @expirer.should_not be_dependent_data_expired(Time.now)
    end

    it "should consider any value created after expiration to be expired" do
        @expirer.expire
        @expirer.should be_dependent_data_expired(Time.now - 1)
    end
end

describe Puppet::Util::Cacher do
    it "should be extended with the Expirer module" do
        Puppet::Util::Cacher.singleton_class.ancestors.should be_include(Puppet::Util::Cacher::Expirer)
    end

    it "should support defining cached attributes" do
        CacheTest.methods.should be_include("cached_attr")
    end

    it "should default to the Cacher module as its expirer" do
        CacheTest.new.expirer.should equal(Puppet::Util::Cacher)
    end

    describe "when using cached attributes" do
        before do
            @expirer = ExpirerTest.new
            @object = CacheTest.new

            @object.stubs(:expirer).returns @expirer
        end

        it "should create a getter for the cached attribute" do
            @object.should respond_to(:instance_cache)
        end

        it "should return a value calculated from the provided block" do
            time = Time.now
            Time.stubs(:now).returns time
            @object.instance_cache.should equal(time)
        end

        it "should return the cached value from the getter every time if the value is not expired" do
            @object.instance_cache.should equal(@object.instance_cache)
        end

        it "should regenerate and return a new value using the provided block if the value has been expired" do
            value = @object.instance_cache
            @expirer.expire
            @object.instance_cache.should_not equal(value)
        end

        it "should be able to trigger expiration on its expirer" do
            @expirer.expects(:expire)
            @object.expire
        end

        it "should do nothing when asked to expire when no expirer is available" do
            cacher = CacheTest.new
            class << cacher
                def expirer
                    nil
                end
            end
            lambda { cacher.expire }.should_not raise_error
        end

        it "should be able to cache false values" do
            @object.expects(:init_instance_cache).returns false
            @object.instance_cache.should be_false
            @object.instance_cache.should be_false
        end

        it "should cache values again after expiration" do
            @object.instance_cache
            @expirer.expire
            @object.instance_cache.should equal(@object.instance_cache)
        end

        it "should always consider a value expired if it has no expirer" do
            @object.stubs(:expirer).returns nil
            @object.instance_cache.should_not equal(@object.instance_cache)
        end

        it "should allow writing of the attribute" do
            @object.should respond_to(:instance_cache=)
        end

        it "should correctly configure timestamps for expiration when the cached attribute is written to" do
            @object.instance_cache = "foo"
            @expirer.expire
            @object.instance_cache.should_not == "foo"
        end

        it "should allow specification of a ttl for cached attributes" do
            klass = Class.new do
                include Puppet::Util::Cacher
            end

            klass.cached_attr(:myattr, :ttl => 5)  { Time.now }

            klass.attr_ttl(:myattr).should == 5
        end

        it "should allow specification of a ttl as a string" do
            klass = Class.new do
                include Puppet::Util::Cacher
            end

            klass.cached_attr(:myattr, :ttl => "5")  { Time.now }

            klass.attr_ttl(:myattr).should == 5
        end

        it "should fail helpfully if the ttl cannot be converted to an integer" do
            klass = Class.new do
                include Puppet::Util::Cacher
            end

            lambda { klass.cached_attr(:myattr, :ttl => "yep")  { Time.now } }.should raise_error(ArgumentError)
        end

        it "should not check for a ttl expiration if the class does not support that method" do
            klass = Class.new do
                extend Puppet::Util::Cacher
            end

            klass.singleton_class.cached_attr(:myattr) { "eh" }
            klass.myattr
        end

        it "should automatically expire cached attributes whose ttl has expired, even if no expirer is present" do
            klass = Class.new do
                def self.to_s
                    "CacheTestClass"
                end
                include Puppet::Util::Cacher
                attr_accessor :value
            end

            klass.cached_attr(:myattr, :ttl => 5)  { self.value += 1; self.value }

            now = Time.now
            later = Time.now + 15

            instance = klass.new
            instance.value = 0
            instance.myattr.should == 1

            Time.expects(:now).returns later

            # This call should get the new Time value, which should expire the old value
            instance.myattr.should == 2
        end
    end
end