summaryrefslogtreecommitdiffstats
path: root/spec/unit/util/cacher_spec.rb
blob: fe93afd2b549475d8d517d3635e262df68f2f815 (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
#!/usr/bin/env rspec
require '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", :'fails_on_ruby_1.9.2' => true 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