lrucache 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Guardfile +1 -0
- data/README.md +1 -1
- data/lib/lrucache.rb +35 -23
- data/lib/lrucache/version.rb +1 -1
- data/spec/lrucache_spec.rb +449 -80
- metadata +3 -3
data/Guardfile
CHANGED
@@ -10,5 +10,6 @@ guard 'rspec', :cli => '-c --format documentation -r ./spec/spec_helper.rb',
|
|
10
10
|
:version => 2 do
|
11
11
|
watch(%r{^spec/.+_spec\.rb})
|
12
12
|
watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
13
|
+
watch('lib/lrucache.rb') { "spec/lrucache_spec.rb" }
|
13
14
|
watch('spec/spec_helper.rb') { "spec" }
|
14
15
|
end
|
data/README.md
CHANGED
data/lib/lrucache.rb
CHANGED
@@ -4,13 +4,14 @@ require "priority_queue"
|
|
4
4
|
# Not thread-safe!
|
5
5
|
class LRUCache
|
6
6
|
|
7
|
-
attr_reader :default, :max_size
|
7
|
+
attr_reader :default, :max_size, :ttl
|
8
8
|
|
9
9
|
def initialize(opts={})
|
10
|
-
@max_size = (opts[:max_size] || 100)
|
10
|
+
@max_size = Integer(opts[:max_size] || 100)
|
11
11
|
@default = opts[:default]
|
12
|
-
@
|
13
|
-
raise "max_size must be
|
12
|
+
@ttl = Float(opts[:ttl] || 0)
|
13
|
+
raise "max_size must be greater than zero" unless @max_size > 0
|
14
|
+
raise "ttl must be positive or zero" unless @ttl >= 0
|
14
15
|
@pqueue = PriorityQueue.new
|
15
16
|
@data = {}
|
16
17
|
@counter = 0
|
@@ -19,6 +20,7 @@ class LRUCache
|
|
19
20
|
def clear
|
20
21
|
@data.clear
|
21
22
|
@pqueue.delete_min until @pqueue.empty?
|
23
|
+
@counter = 0 #might as well
|
22
24
|
end
|
23
25
|
|
24
26
|
def include?(key)
|
@@ -34,38 +36,48 @@ class LRUCache
|
|
34
36
|
end
|
35
37
|
end
|
36
38
|
|
37
|
-
def store(key, value,
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
expires
|
39
|
+
def store(key, value, ttl=nil)
|
40
|
+
evict_lru! unless @data.include?(key) || @data.size < @max_size
|
41
|
+
ttl ||= @ttl
|
42
|
+
expires =
|
43
|
+
if ttl.is_a?(Time)
|
44
|
+
ttl
|
44
45
|
else
|
45
|
-
|
46
|
-
(
|
46
|
+
ttl = Float(ttl)
|
47
|
+
(ttl > 0) ? (Time.now + ttl) : nil
|
47
48
|
end
|
48
|
-
@data[key] = [value,
|
49
|
+
@data[key] = [value, expires]
|
49
50
|
access(key)
|
50
51
|
end
|
51
52
|
|
52
53
|
alias :[]= :store
|
53
54
|
|
54
|
-
def fetch(key)
|
55
|
+
def fetch(key, ttl=nil)
|
55
56
|
datum = @data[key]
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
57
|
+
unless datum.nil?
|
58
|
+
value, expires = datum
|
59
|
+
if expires.nil? || expires > Time.now # no expiration, or not expired
|
60
|
+
access(key)
|
61
|
+
return value
|
62
|
+
else # expired
|
63
|
+
delete(key)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
if block_given?
|
67
|
+
value = yield
|
68
|
+
store(key, value, ttl)
|
60
69
|
value
|
61
|
-
else
|
62
|
-
delete(key)
|
70
|
+
else
|
63
71
|
@default
|
64
72
|
end
|
65
73
|
end
|
66
74
|
|
67
75
|
alias :[] :fetch
|
68
76
|
|
77
|
+
def empty?
|
78
|
+
size == 0
|
79
|
+
end
|
80
|
+
|
69
81
|
def size
|
70
82
|
@data.size
|
71
83
|
end
|
@@ -75,13 +87,13 @@ class LRUCache
|
|
75
87
|
end
|
76
88
|
|
77
89
|
def delete(key)
|
78
|
-
@data.delete(key)
|
79
90
|
@pqueue.delete(key)
|
91
|
+
@data.delete(key)
|
80
92
|
end
|
81
93
|
|
82
94
|
private
|
83
95
|
|
84
|
-
def
|
96
|
+
def evict_lru!
|
85
97
|
key, priority = @pqueue.delete_min
|
86
98
|
@data.delete(key) unless priority.nil?
|
87
99
|
end
|
data/lib/lrucache/version.rb
CHANGED
data/spec/lrucache_spec.rb
CHANGED
@@ -1,102 +1,471 @@
|
|
1
1
|
describe LRUCache do
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
describe ".new" do
|
3
|
+
|
4
|
+
it "should default :max_size to 100" do
|
5
|
+
LRUCache.new.max_size.should == 100
|
6
|
+
end
|
7
|
+
it "should accept a :max_size parameter" do
|
8
|
+
LRUCache.new(:max_size => 7).max_size.should == 7
|
9
|
+
end
|
10
|
+
it "should raise an exception if :max_size parameter can't be converted to an integer" do
|
11
|
+
expect { LRUCache.new(:max_size => "moocow") }.to raise_exception
|
12
|
+
end
|
13
|
+
it "should raise an exception if :max_size parameter is converted to a negative integer" do
|
14
|
+
expect { LRUCache.new(:max_size => -1) }.to raise_exception
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should default :default to nil" do
|
18
|
+
LRUCache.new.default.should be_nil
|
19
|
+
end
|
20
|
+
it "should accept a :default parameter" do
|
21
|
+
default = double(:default)
|
22
|
+
LRUCache.new(:default => default).default.should == default
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should default :ttl to 0 (no expiration)" do
|
26
|
+
LRUCache.new.ttl.should == 0
|
27
|
+
end
|
28
|
+
it "should accept a :ttl parameter" do
|
29
|
+
LRUCache.new(:ttl => 98.6).ttl.should == 98.6
|
30
|
+
end
|
31
|
+
it "should raise an exception if :ttl parameter can't be converted to a float" do
|
32
|
+
expect { LRUCache.new(:ttl => "moocow") }.to raise_exception
|
33
|
+
end
|
34
|
+
it "should raise an exception if :ttl parameter is converted to a negative float" do
|
35
|
+
expect { LRUCache.new(:ttl => -1) }.to raise_exception
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should initially be empty" do
|
39
|
+
LRUCache.new.should be_empty
|
7
40
|
end
|
8
|
-
c.size.should == 7
|
9
41
|
end
|
10
42
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
43
|
+
describe ".clear" do
|
44
|
+
it "should empty the hash and the priority queue" do
|
45
|
+
c = LRUCache.new
|
46
|
+
10.times { c[rand(2**16)] = :x }
|
47
|
+
c.should_not be_empty
|
48
|
+
c.instance_variable_get(:@data).should_not be_empty
|
49
|
+
c.instance_variable_get(:@pqueue).should_not be_empty
|
50
|
+
|
51
|
+
c.clear
|
52
|
+
|
53
|
+
c.should be_empty
|
54
|
+
c.instance_variable_get(:@data).should be_empty
|
55
|
+
c.instance_variable_get(:@pqueue).should be_empty
|
56
|
+
end
|
25
57
|
end
|
26
58
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
59
|
+
describe ".include?(key)" do
|
60
|
+
before(:each) { @cache = LRUCache.new }
|
61
|
+
context "when the key is not present" do
|
62
|
+
it "should return false" do
|
63
|
+
@cache.include?(:a).should be_false
|
64
|
+
end
|
65
|
+
it "should not affect priorities" do
|
66
|
+
@cache.should_not_receive(:access)
|
67
|
+
@cache.include?(:a)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
context "when the key is present, but the value has expired" do
|
71
|
+
before(:each) do
|
72
|
+
now = Time.now
|
73
|
+
Timecop.freeze(now) { @cache.store(:a, 'a', now + 10) }
|
74
|
+
Timecop.freeze(now + 20)
|
75
|
+
end
|
76
|
+
after(:each) do
|
77
|
+
Timecop.return
|
78
|
+
end
|
79
|
+
it "should return false" do
|
80
|
+
@cache.include?(:a).should be_false
|
81
|
+
end
|
82
|
+
it "should delete the key" do
|
83
|
+
@cache.should_receive(:delete).with(:a)
|
84
|
+
@cache.include?(:a)
|
85
|
+
end
|
86
|
+
it "should not affect priorities" do
|
87
|
+
@cache.should_not_receive(:access)
|
88
|
+
@cache.include?(:a)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
context "when the key is present, and the value has no expiration" do
|
92
|
+
before(:each) do
|
93
|
+
@cache.store(:a, 'a', nil)
|
94
|
+
end
|
95
|
+
it "should return true" do
|
96
|
+
@cache.include?(:a).should be_true
|
97
|
+
end
|
98
|
+
it "should update the key's access stamp" do
|
99
|
+
@cache.should_receive(:access).with(:a)
|
100
|
+
@cache.include?(:a)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
context "when the key is present, and the value has not yet expired" do
|
104
|
+
before(:each) do
|
105
|
+
now = Time.now
|
106
|
+
Timecop.freeze(now)
|
107
|
+
@cache.store(:a, 'a', now + 10)
|
108
|
+
end
|
109
|
+
after(:each) do
|
110
|
+
Timecop.return
|
111
|
+
end
|
112
|
+
it "should return true" do
|
113
|
+
@cache.include?(:a).should be_true
|
114
|
+
end
|
115
|
+
it "should update the key's access stamp" do
|
116
|
+
@cache.should_receive(:access).with(:a)
|
117
|
+
@cache.include?(:a)
|
118
|
+
end
|
119
|
+
end
|
37
120
|
end
|
38
121
|
|
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
|
-
|
122
|
+
describe ".store(key, value, ttl=nil)" do
|
123
|
+
context "regarding evictions" do
|
124
|
+
before(:each) do
|
125
|
+
@cache = LRUCache.new(:max_size => 2)
|
126
|
+
end
|
127
|
+
context "when the cache is not full" do
|
128
|
+
context "and the key is not present" do
|
129
|
+
it "should not evict an entry" do
|
130
|
+
@cache.should_not_receive(:evict_lru!)
|
131
|
+
@cache.store(:a, 'a')
|
132
|
+
end
|
133
|
+
it "should store the value" do
|
134
|
+
@cache.store(:a, 'a')
|
135
|
+
@cache.fetch(:a).should == 'a'
|
136
|
+
end
|
137
|
+
it "should update the key's access stamp" do
|
138
|
+
@cache.should_receive(:access).with(:a)
|
139
|
+
@cache.store(:a, 'a')
|
140
|
+
end
|
141
|
+
end
|
142
|
+
context "and the key is present" do
|
143
|
+
it "should not evict an entry" do
|
144
|
+
@cache.should_not_receive(:evict_lru!)
|
145
|
+
@cache.store(:a, 'a')
|
146
|
+
end
|
147
|
+
it "should store the value" do
|
148
|
+
@cache.store(:a, 'a')
|
149
|
+
@cache.fetch(:a).should == 'a'
|
150
|
+
end
|
151
|
+
it "should update the key's access stamp" do
|
152
|
+
@cache.should_receive(:access).with(:a)
|
153
|
+
@cache.store(:a, 'a')
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
context "when the cache is full" do
|
158
|
+
before(:each) do
|
159
|
+
@cache = LRUCache.new(:max_size => 2)
|
160
|
+
@cache[:b] = 'b'
|
161
|
+
@cache[:c] = 'c'
|
162
|
+
@lru = :b
|
163
|
+
end
|
164
|
+
context "and the key is not present" do
|
165
|
+
it "should evict the least-recently used entry from the cache" do
|
166
|
+
@cache.keys.should include(@lru)
|
167
|
+
@cache.store(:a, 'a')
|
168
|
+
@cache.keys.should_not include(@lru)
|
169
|
+
end
|
170
|
+
it "should store the value" do
|
171
|
+
@cache.store(:a, 'a')
|
172
|
+
@cache.fetch(:a).should == 'a'
|
173
|
+
end
|
174
|
+
it "should update the key's access stamp" do
|
175
|
+
@cache.should_receive(:access).with(:a)
|
176
|
+
@cache.store(:a, 'a')
|
177
|
+
end
|
178
|
+
end
|
179
|
+
context "and the key is present" do
|
180
|
+
it "should not evict an entry" do
|
181
|
+
@cache.should_not_receive(:evict_lru!)
|
182
|
+
@cache.store(:c, 'c')
|
183
|
+
end
|
184
|
+
it "should store the value" do
|
185
|
+
@cache.store(:c, 'c')
|
186
|
+
@cache.fetch(:c).should == 'c'
|
187
|
+
end
|
188
|
+
it "should update the key's access stamp" do
|
189
|
+
@cache.should_receive(:access).with(:c)
|
190
|
+
@cache.store(:c, 'c')
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
context "when ttl is not given and the cache's default ttl is zero" do
|
196
|
+
it "should set the entry with no expiration time" do
|
197
|
+
c = LRUCache.new(:ttl => 0)
|
198
|
+
c.store(:a,'a')
|
199
|
+
stored = c.instance_variable_get(:@data)[:a]
|
200
|
+
stored.should == ['a', nil]
|
201
|
+
end
|
202
|
+
end
|
203
|
+
context "when ttl is not given and the cache's default ttl is greater than zero" do
|
204
|
+
it "should set the entry to expire that many seconds in the future" do
|
205
|
+
c = LRUCache.new(:ttl => 1)
|
206
|
+
now = Time.now
|
207
|
+
Timecop.freeze(now) { c.store(:a,'a') }
|
208
|
+
stored = c.instance_variable_get(:@data)[:a]
|
209
|
+
stored.last.should == now + 1
|
210
|
+
end
|
211
|
+
end
|
212
|
+
context "when ttl is a Time" do
|
213
|
+
it "should set the entry to expire at the given time" do
|
214
|
+
c = LRUCache.new
|
215
|
+
ttl = Time.now + 246
|
216
|
+
c.store(:a, 'a', ttl)
|
217
|
+
stored = c.instance_variable_get(:@data)[:a]
|
218
|
+
stored.last.should == ttl
|
219
|
+
end
|
220
|
+
end
|
221
|
+
context "when ttl can be parsed as a float" do
|
222
|
+
it "should set the entry to expire that many seconds in the future" do
|
223
|
+
c = LRUCache.new
|
224
|
+
now = Time.now
|
225
|
+
Timecop.freeze(now) { c.store(:a, 'a', "98.6") }
|
226
|
+
stored = c.instance_variable_get(:@data)[:a]
|
227
|
+
stored.last.should == now + 98.6
|
228
|
+
end
|
229
|
+
end
|
230
|
+
context "when ttl cannot be parsed as a float" do
|
231
|
+
it "should raise an exception" do
|
232
|
+
c = LRUCache.new
|
233
|
+
expect { c.store(:a, 'a', "moocow") }.to raise_exception
|
234
|
+
end
|
68
235
|
end
|
69
|
-
c[:a].should be_nil
|
70
|
-
c[:b].should be_nil
|
71
|
-
c[:c].should be_nil
|
72
|
-
c.size.should == 0
|
73
236
|
end
|
74
237
|
|
75
|
-
|
76
|
-
|
77
|
-
|
238
|
+
describe ".fetch(key, ttl=nil)" do
|
239
|
+
context "when no block is given" do
|
240
|
+
context "and the key does not exist" do
|
241
|
+
before(:each) do
|
242
|
+
@default = double(:default)
|
243
|
+
@cache = LRUCache.new(:default => @default)
|
244
|
+
end
|
245
|
+
it "should return the default value" do
|
246
|
+
@cache.fetch(:a).should == @default
|
247
|
+
end
|
248
|
+
it "should not affect the priorities" do
|
249
|
+
@cache.should_not_receive(:access)
|
250
|
+
@cache.fetch(:a)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
context "and the key has been evicted" do
|
254
|
+
before(:each) do
|
255
|
+
@cache = LRUCache.new(:max_size => 2)
|
256
|
+
@cache[:a] = 'a'
|
257
|
+
@cache[:b] = 'b'
|
258
|
+
@cache[:c] = 'c'
|
259
|
+
end
|
260
|
+
it "should return the default value" do
|
261
|
+
@cache.fetch(:a).should == @default
|
262
|
+
end
|
263
|
+
it "should not affect the priorities" do
|
264
|
+
@cache.should_not_receive(:access)
|
265
|
+
@cache.fetch(:a)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
context "and the key has expired" do
|
269
|
+
before(:each) do
|
270
|
+
@cache = LRUCache.new(:ttl => 10)
|
271
|
+
now = Time.now
|
272
|
+
Timecop.freeze(now) { @cache[:a] = 'a' }
|
273
|
+
Timecop.freeze(now + 20)
|
274
|
+
end
|
275
|
+
after(:each) do
|
276
|
+
Timecop.return
|
277
|
+
end
|
278
|
+
it "should return the default value" do
|
279
|
+
@cache.fetch(:a).should == @default
|
280
|
+
end
|
281
|
+
it "should not affect the priorities" do
|
282
|
+
@cache.should_not_receive(:access)
|
283
|
+
@cache.fetch(:a)
|
284
|
+
end
|
285
|
+
it "should delete the key" do
|
286
|
+
@cache.should_receive(:delete).with(:a)
|
287
|
+
@cache.fetch(:a)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
context "and the key is present and un-expired" do
|
291
|
+
before(:each) do
|
292
|
+
@cache = LRUCache.new(:ttl => nil)
|
293
|
+
@cache[:a] = 'a'
|
294
|
+
end
|
295
|
+
it "should return the cached value" do
|
296
|
+
@cache.fetch(:a).should == 'a'
|
297
|
+
end
|
298
|
+
it "should update the key's access stamp" do
|
299
|
+
@cache.should_receive(:access).with(:a)
|
300
|
+
@cache.fetch(:a)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
context "when a block is given" do
|
305
|
+
context "when the key does not exist" do
|
306
|
+
it "should call the block and store and return the result" do
|
307
|
+
c = LRUCache.new
|
308
|
+
ttl = double(:ttl)
|
309
|
+
result = double(:result)
|
310
|
+
c.should_receive(:store).with(:a, result, ttl)
|
311
|
+
c.fetch(:a, ttl){ result }.should == result
|
312
|
+
end
|
313
|
+
end
|
314
|
+
context "when the key has been evicted" do
|
315
|
+
it "should call the block and store and return the result" do
|
316
|
+
c = LRUCache.new
|
317
|
+
c[:a] = 'a'
|
318
|
+
c.send(:evict_lru!)
|
319
|
+
ttl = double(:ttl)
|
320
|
+
result = double(:result)
|
321
|
+
c.should_receive(:store).with(:a, result, ttl)
|
322
|
+
c.fetch(:a, ttl){ result }.should == result
|
323
|
+
end
|
324
|
+
end
|
325
|
+
context "when the key has expired" do
|
326
|
+
it "should call the block and store and return the result" do
|
327
|
+
c = LRUCache.new
|
328
|
+
now = Time.now
|
329
|
+
Timecop.freeze(now) { c.store(:a, 'a', now + 10) }
|
330
|
+
Timecop.freeze(now + 20) do
|
331
|
+
ttl = double(:ttl)
|
332
|
+
result = double(:result)
|
333
|
+
c.should_receive(:store).with(:a, result, ttl)
|
334
|
+
c.fetch(:a, ttl){ result }.should == result
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
context "when the key is present and un-expired" do
|
339
|
+
it "should return the cached value without calling the block" do
|
340
|
+
c = LRUCache.new(:ttl => nil)
|
341
|
+
c[:a] = 'a'
|
342
|
+
c.fetch(:a) { raise 'fail' }.should == 'a'
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
78
346
|
end
|
79
347
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
348
|
+
describe ".empty?" do
|
349
|
+
it "should return true if and only if size is zero" do
|
350
|
+
c = LRUCache.new
|
351
|
+
c.empty?.should be_true
|
352
|
+
c[:a] = 'a'
|
353
|
+
c.empty?.should be_false
|
354
|
+
c.clear
|
355
|
+
c.empty?.should be_true
|
356
|
+
end
|
87
357
|
end
|
88
358
|
|
89
|
-
describe ".
|
90
|
-
it "
|
91
|
-
c = LRUCache.new(:max_size =>
|
359
|
+
describe ".size and .keys" do
|
360
|
+
it "should return a count / list of entries in the cache" do
|
361
|
+
c = LRUCache.new(:max_size => 10)
|
362
|
+
(1..10).each do |i|
|
363
|
+
c.size.should == i-1
|
364
|
+
c[i] = :x
|
365
|
+
c.size.should == i
|
366
|
+
c.keys.sort.should == (1..i).to_a
|
367
|
+
end
|
368
|
+
(1..3).each do |i|
|
369
|
+
c.delete(i)
|
370
|
+
c.size.should == 10 - i
|
371
|
+
c.keys.sort.should == ((i+1)..10).to_a
|
372
|
+
end
|
373
|
+
(1..3).each do |i|
|
374
|
+
c[i] = :x
|
375
|
+
c.size.should == 7+i
|
376
|
+
c.keys.sort.should == (1..i).to_a + (4..10).to_a
|
377
|
+
end
|
378
|
+
(1..10).each do |i|
|
379
|
+
c[i] = :x
|
380
|
+
c.size.should == 10
|
381
|
+
c.keys.sort.should == (1..10).to_a
|
382
|
+
end
|
383
|
+
c.clear
|
384
|
+
c.size.should == 0
|
385
|
+
c.keys.should == []
|
386
|
+
end
|
387
|
+
it "may include expired entries" do
|
388
|
+
c = LRUCache.new(:ttl => 10, :max_size => 100)
|
389
|
+
now = Time.now
|
390
|
+
(0..19).each do |i|
|
391
|
+
Timecop.freeze(now + i) do
|
392
|
+
c[i] = :x
|
393
|
+
c.size.should == i+1
|
394
|
+
c.keys.sort.should == (0..i).to_a
|
395
|
+
end
|
396
|
+
end
|
397
|
+
Timecop.freeze(now + 20) do
|
398
|
+
c.size.should == 20
|
399
|
+
(0..19).each do |i|
|
400
|
+
c.include?(i)
|
401
|
+
end
|
402
|
+
c.size.should == 9
|
403
|
+
c.keys.sort.should == (11..19).to_a
|
404
|
+
end
|
405
|
+
end
|
406
|
+
it "should always return a value less than or equal to the cache's max_size" do
|
407
|
+
c = LRUCache.new(:max_size => 7)
|
408
|
+
(1..100).each do |i|
|
409
|
+
c[i] = :x
|
410
|
+
c.size.should <= 7
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
|
416
|
+
describe ".delete(key)" do
|
417
|
+
it "should remove the key from the internal hash and priority queue" do
|
418
|
+
c = LRUCache.new
|
419
|
+
c[:a] = 'a'
|
420
|
+
c.instance_variable_get(:@data).should include(:a)
|
421
|
+
c.instance_variable_get(:@pqueue).should include([:a,1])
|
422
|
+
c.delete(:a)
|
423
|
+
c.instance_variable_get(:@data).should_not include(:a)
|
424
|
+
c.instance_variable_get(:@pqueue).should_not include([:a,1])
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
describe ".evict_lru!" do
|
429
|
+
it "should find the least-recently used entry, and delete it" do
|
430
|
+
c = LRUCache.new
|
92
431
|
c[1] = 'a'
|
93
432
|
c[2] = 'b'
|
94
433
|
c[3] = 'c'
|
95
|
-
c.include?(1).should be_true
|
96
434
|
c[4] = 'd'
|
97
|
-
c[
|
98
|
-
c.
|
99
|
-
c.
|
435
|
+
c[:dne1].should be_nil # Doesn't affect LRU.
|
436
|
+
c[2].should == 'b'
|
437
|
+
c[1].should == 'a'
|
438
|
+
c[3].should == 'c'
|
439
|
+
c[4].should == 'd'
|
440
|
+
c[:dne2].should be_nil # Doesn't affect LRU.
|
441
|
+
c.keys.sort.should == [1,2,3,4]
|
442
|
+
c.send(:evict_lru!)
|
443
|
+
c.keys.sort.should == [1,3,4]
|
444
|
+
c.send(:evict_lru!)
|
445
|
+
c.keys.sort.should == [3,4]
|
446
|
+
c.send(:evict_lru!)
|
447
|
+
c.keys.sort.should == [4]
|
448
|
+
c.send(:evict_lru!)
|
449
|
+
c.keys.should == []
|
450
|
+
c.send(:evict_lru!)
|
451
|
+
c.keys.should == []
|
452
|
+
end
|
453
|
+
it "should leave the internal hash the same size as the priority queue" do
|
454
|
+
c = LRUCache.new
|
455
|
+
expected_size = 0
|
456
|
+
counter = 0
|
457
|
+
100.times do
|
458
|
+
if rand < 0.8
|
459
|
+
c[counter += 1] = :x
|
460
|
+
expected_size += 1
|
461
|
+
else
|
462
|
+
c.send(:evict_lru!)
|
463
|
+
expected_size -= 1
|
464
|
+
end
|
465
|
+
expected_size = [expected_size,0].max
|
466
|
+
c.instance_variable_get(:@data).size.should == expected_size
|
467
|
+
c.instance_variable_get(:@pqueue).count.should == expected_size
|
468
|
+
end
|
100
469
|
end
|
101
470
|
end
|
102
471
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lrucache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
- 0
|
9
8
|
- 1
|
10
|
-
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Chris Johnson
|