nkallen-cache-money 0.2.1

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.
@@ -0,0 +1,166 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ module Cash
4
+ describe 'Ordering' do
5
+ before :suite do
6
+ FairyTale = Class.new(Story)
7
+ end
8
+
9
+ describe '#create!' do
10
+ describe 'the records are written-through in sorted order', :shared => true do
11
+ describe 'when there are not already records matching the index' do
12
+ it 'initializes the index' do
13
+ fairy_tale = FairyTale.create!(:title => 'title')
14
+ FairyTale.get("title/#{fairy_tale.title}").should == [fairy_tale.id]
15
+ end
16
+ end
17
+
18
+ describe 'when there are already records matching the index' do
19
+ before do
20
+ @fairy_tale1 = FairyTale.create!(:title => 'title')
21
+ FairyTale.get("title/#{@fairy_tale1.title}").should == sorted_and_serialized_records(@fairy_tale1)
22
+ end
23
+
24
+ describe 'when the index is populated' do
25
+ it 'appends to the index' do
26
+ fairy_tale2 = FairyTale.create!(:title => @fairy_tale1.title)
27
+ FairyTale.get("title/#{@fairy_tale1.title}").should == sorted_and_serialized_records(@fairy_tale1, fairy_tale2)
28
+ end
29
+ end
30
+
31
+ describe 'when the index is not populated' do
32
+ before do
33
+ $memcache.flush_all
34
+ end
35
+
36
+ it 'initializes the index' do
37
+ fairy_tale2 = FairyTale.create!(:title => @fairy_tale1.title)
38
+ FairyTale.get("title/#{@fairy_tale1.title}").should == sorted_and_serialized_records(@fairy_tale1, fairy_tale2)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ describe 'when the order is ascending' do
45
+ it_should_behave_like 'the records are written-through in sorted order'
46
+
47
+ before :all do
48
+ FairyTale.index :title, :order => :asc
49
+ end
50
+
51
+ def sorted_and_serialized_records(*records)
52
+ records.collect(&:id).sort
53
+ end
54
+ end
55
+
56
+ describe 'when the order is descending' do
57
+ it_should_behave_like 'the records are written-through in sorted order'
58
+
59
+ before :all do
60
+ FairyTale.index :title, :order => :desc
61
+ end
62
+
63
+ def sorted_and_serialized_records(*records)
64
+ records.collect(&:id).sort.reverse
65
+ end
66
+ end
67
+ end
68
+
69
+ describe "#find(..., :order => ...)" do
70
+ before :each do
71
+ @fairy_tales = [FairyTale.create!(:title => @title = 'title'), FairyTale.create!(:title => @title)]
72
+ end
73
+
74
+ describe 'when the order is ascending' do
75
+ before :all do
76
+ FairyTale.index :title, :order => :asc
77
+ end
78
+
79
+ describe "#find(..., :order => 'id ASC')" do
80
+ describe 'when the cache is populated' do
81
+ it 'does not use the database' do
82
+ mock(FairyTale.connection).execute.never
83
+ FairyTale.find(:all, :conditions => { :title => @title }, :order => 'id ASC').should == @fairy_tales
84
+ FairyTale.find(:all, :conditions => { :title => @title }, :order => 'id').should == @fairy_tales
85
+ FairyTale.find(:all, :conditions => { :title => @title }, :order => '`id`').should == @fairy_tales
86
+ FairyTale.find(:all, :conditions => { :title => @title }, :order => 'stories.id').should == @fairy_tales
87
+ FairyTale.find(:all, :conditions => { :title => @title }, :order => '`stories`.id').should == @fairy_tales
88
+ FairyTale.find(:all, :conditions => { :title => @title }, :order => '`stories`.`id`').should == @fairy_tales
89
+ end
90
+ end
91
+
92
+ describe 'when the cache is not populated' do
93
+ it 'populates the cache' do
94
+ $memcache.flush_all
95
+ FairyTale.find(:all, :conditions => { :title => @title }, :order => 'id ASC').should == @fairy_tales
96
+ FairyTale.get("title/#{@title}").should == @fairy_tales.collect(&:id)
97
+ end
98
+ end
99
+ end
100
+
101
+ describe "#find(..., :order => 'id DESC')" do
102
+ describe 'when the cache is populated' do
103
+ it 'uses the database, not the cache' do
104
+ mock(FairyTale).get.never
105
+ FairyTale.find(:all, :conditions => { :title => @title }, :order => 'id DESC').should == @fairy_tales.reverse
106
+ end
107
+ end
108
+
109
+ describe 'when the cache is not populated' do
110
+ it 'does not populate the cache' do
111
+ $memcache.flush_all
112
+ FairyTale.find(:all, :conditions => { :title => @title }, :order => 'id DESC').should == @fairy_tales.reverse
113
+ FairyTale.get("title/#{@title}").should be_nil
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ describe 'when the order is descending' do
120
+ before :all do
121
+ FairyTale.index :title, :order => :desc
122
+ end
123
+
124
+ describe "#find(..., :order => 'id DESC')" do
125
+ describe 'when the cache is populated' do
126
+ it 'does not use the database' do
127
+ mock(FairyTale.connection).execute.never
128
+ FairyTale.find(:all, :conditions => { :title => @title }, :order => 'id DESC').should == @fairy_tales.reverse
129
+ FairyTale.find(:all, :conditions => { :title => @title }, :order => 'id DESC').should == @fairy_tales.reverse
130
+ FairyTale.find(:all, :conditions => { :title => @title }, :order => '`id` DESC').should == @fairy_tales.reverse
131
+ FairyTale.find(:all, :conditions => { :title => @title }, :order => 'stories.id DESC').should == @fairy_tales.reverse
132
+ FairyTale.find(:all, :conditions => { :title => @title }, :order => '`stories`.id DESC').should == @fairy_tales.reverse
133
+ FairyTale.find(:all, :conditions => { :title => @title }, :order => '`stories`.`id` DESC').should == @fairy_tales.reverse
134
+ end
135
+ end
136
+
137
+ describe 'when the cache is not populated' do
138
+ it 'populates the cache' do
139
+ $memcache.flush_all
140
+ FairyTale.find(:all, :conditions => { :title => @title }, :order => 'id DESC')
141
+ FairyTale.get("title/#{@title}").should == @fairy_tales.collect(&:id).reverse
142
+ end
143
+ end
144
+ end
145
+
146
+ describe "#find(..., :order => 'id ASC')" do
147
+ describe 'when the cache is populated' do
148
+ it 'uses the database, not the cache' do
149
+ mock(FairyTale).get.never
150
+ FairyTale.find(:all, :conditions => { :title => @title }, :order => 'id ASC').should == @fairy_tales
151
+ FairyTale.find(:all, :conditions => { :title => @title }, :order => 'id').should == @fairy_tales
152
+ end
153
+ end
154
+
155
+ describe 'when the cache is not populated' do
156
+ it 'does not populate the cache' do
157
+ $memcache.flush_all
158
+ FairyTale.find(:all, :conditions => { :title => @title }, :order => 'id ASC').should == @fairy_tales
159
+ FairyTale.get("title/#{@title}").should be_nil
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,574 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ module Cash
4
+ describe Transactional do
5
+ before do
6
+ @cache = Transactional.new($memcache, $lock)
7
+ @value = "stuff to be cached"
8
+ @key = "key"
9
+ end
10
+
11
+ describe 'Basic Operations' do
12
+ it "gets through the real cache" do
13
+ $memcache.set(@key, @value)
14
+ @cache.get(@key).should == @value
15
+ end
16
+
17
+ it "sets through the real cache" do
18
+ mock($memcache).set(@key, @value, :option1, :option2)
19
+ @cache.set(@key, @value, :option1, :option2)
20
+ end
21
+
22
+ it "increments through the real cache" do
23
+ @cache.set(@key, 0)
24
+ @cache.incr(@key, 3)
25
+
26
+ @cache.get(@key, true).to_i.should == 3
27
+ $memcache.get(@key, true).to_i.should == 3
28
+ end
29
+
30
+ it "decrements through the real cache" do
31
+ @cache.set(@key, 0)
32
+ @cache.incr(@key, 3)
33
+ @cache.decr(@key, 2)
34
+
35
+ @cache.get(@key, true).to_i.should == 1
36
+ $memcache.get(@key, true).to_i.should == 1
37
+ end
38
+
39
+ it "adds through the real cache" do
40
+ @cache.add(@key, @value)
41
+ $memcache.get(@key).should == @value
42
+ @cache.get(@key).should == @value
43
+
44
+ @cache.add(@key, "another value")
45
+ $memcache.get(@key).should == @value
46
+ @cache.get(@key).should == @value
47
+ end
48
+
49
+ it "deletes through the real cache" do
50
+ $memcache.add(@key, @value)
51
+ $memcache.get(@key).should == @value
52
+
53
+ @cache.delete(@key)
54
+ $memcache.get(@key).should be_nil
55
+ end
56
+
57
+ it "returns true for respond_to? with what it responds to" do
58
+ @cache.respond_to?(:get).should be_true
59
+ @cache.respond_to?(:set).should be_true
60
+ @cache.respond_to?(:get_multi).should be_true
61
+ @cache.respond_to?(:incr).should be_true
62
+ @cache.respond_to?(:decr).should be_true
63
+ @cache.respond_to?(:add).should be_true
64
+ end
65
+
66
+ it "delegates unsupported messages back to the real cache" do
67
+ mock($memcache).foo(:bar)
68
+ @cache.foo(:bar)
69
+ end
70
+
71
+ describe '#get_multi' do
72
+ describe 'when everything is a hit' do
73
+ it 'returns a hash' do
74
+ @cache.set('key1', @value)
75
+ @cache.set('key2', @value)
76
+ @cache.get_multi(['key1', 'key2']).should == { 'key1' => @value, 'key2' => @value }
77
+ end
78
+ end
79
+
80
+ describe 'when there are misses' do
81
+ it 'only returns results for hits' do
82
+ @cache.set('key1', @value)
83
+ @cache.get_multi(['key1', 'key2']).should == { 'key1' => @value }
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ describe 'In a Transaction' do
90
+ it "commits to the real cache" do
91
+ $memcache.get(@key).should == nil
92
+ @cache.transaction do
93
+ @cache.set(@key, @value)
94
+ end
95
+ $memcache.get(@key).should == @value
96
+ end
97
+
98
+ describe 'when there is a return/next/break in the transaction' do
99
+ it 'commits to the real cache' do
100
+ $memcache.get(@key).should == nil
101
+ @cache.transaction do
102
+ @cache.set(@key, @value)
103
+ next
104
+ end
105
+ $memcache.get(@key).should == @value
106
+ end
107
+ end
108
+
109
+ it "reads through the real cache if key has not been written to" do
110
+ $memcache.set(@key, @value)
111
+ @cache.transaction do
112
+ @cache.get(@key).should == @value
113
+ end
114
+ @cache.get(@key).should == @value
115
+ end
116
+
117
+ it "delegates unsupported messages back to the real cache" do
118
+ @cache.transaction do
119
+ mock($memcache).foo(:bar)
120
+ @cache.foo(:bar)
121
+ end
122
+ end
123
+
124
+ it "returns the result of the block passed to the transaction" do
125
+ @cache.transaction do
126
+ :result
127
+ end.should == :result
128
+ end
129
+
130
+ describe 'Increment and Decrement' do
131
+ describe '#incr' do
132
+ it "works" do
133
+ @cache.set(@key, 0)
134
+ @cache.incr(@key)
135
+ @cache.transaction do
136
+ @cache.incr(@key).should == 2
137
+ end
138
+ end
139
+
140
+ it "is buffered" do
141
+ @cache.transaction do
142
+ @cache.set(@key, 0)
143
+ @cache.incr(@key, 2).should == 2
144
+ @cache.get(@key).should == 2
145
+ $memcache.get(@key).should == nil
146
+ end
147
+ @cache.get(@key, true).to_i.should == 2
148
+ $memcache.get(@key, true).to_i.should == 2
149
+ end
150
+
151
+ it "returns nil if there is no key already at that value" do
152
+ @cache.transaction do
153
+ @cache.incr(@key).should == nil
154
+ end
155
+ end
156
+
157
+ end
158
+
159
+ describe '#decr' do
160
+ it "works" do
161
+ @cache.set(@key, 0)
162
+ @cache.incr(@key)
163
+ @cache.transaction do
164
+ @cache.decr(@key).should == 0
165
+ end
166
+ end
167
+
168
+ it "is buffered" do
169
+ @cache.transaction do
170
+ @cache.set(@key, 0)
171
+ @cache.incr(@key, 3)
172
+ @cache.decr(@key, 2).should == 1
173
+ @cache.get(@key, true).to_i.should == 1
174
+ $memcache.get(@key).should == nil
175
+ end
176
+ @cache.get(@key, true).to_i.should == 1
177
+ $memcache.get(@key, true).to_i.should == 1
178
+ end
179
+
180
+ it "returns nil if there is no key already at that value" do
181
+ @cache.transaction do
182
+ @cache.decr(@key).should == nil
183
+ end
184
+ end
185
+
186
+ it "bottoms out at zero" do
187
+ @cache.transaction do
188
+ @cache.set(@key, 0)
189
+ @cache.incr(@key, 1)
190
+ @cache.get(@key, true).should == 1
191
+ @cache.decr(@key)
192
+ @cache.get(@key, true).should == 0
193
+ @cache.decr(@key)
194
+ @cache.get(@key, true).should == 0
195
+ end
196
+ end
197
+ end
198
+ end
199
+
200
+ describe '#get_multi' do
201
+ describe 'when a hit value is the empty array' do
202
+ it 'returns a hash' do
203
+ @cache.transaction do
204
+ @cache.set('key1', @value)
205
+ @cache.set('key2', [])
206
+ @cache.get_multi(['key1', 'key2']).should == { 'key1' => @value, 'key2' => [] }
207
+ end
208
+ end
209
+ end
210
+
211
+ describe 'when everything is a hit' do
212
+ it 'returns a hash' do
213
+ @cache.transaction do
214
+ @cache.set('key1', @value)
215
+ @cache.set('key2', @value)
216
+ @cache.get_multi(['key1', 'key2']).should == { 'key1' => @value, 'key2' => @value }
217
+ end
218
+ end
219
+ end
220
+
221
+ describe 'when there are misses' do
222
+ it 'only returns results for hits' do
223
+ @cache.transaction do
224
+ @cache.set('key1', @value)
225
+ @cache.get_multi(['key1', 'key2']).should == { 'key1' => @value }
226
+ end
227
+ end
228
+ end
229
+ end
230
+
231
+ describe 'Lock Acquisition' do
232
+ it "locks @keys to be written before writing to memcache and release them after" do
233
+ mock($lock).acquire_lock(@key)
234
+ mock($memcache).set(@key, @value)
235
+ mock($lock).release_lock(@key)
236
+
237
+ @cache.transaction do
238
+ @cache.set(@key, @value)
239
+ end
240
+ end
241
+
242
+ it "does not acquire locks on reads" do
243
+ mock($lock).acquire_lock.never
244
+ mock($lock).release_lock.never
245
+
246
+ @cache.transaction do
247
+ @cache.get(@key)
248
+ end
249
+ end
250
+
251
+ it "locks @keys in lexically sorted order" do
252
+ keys = ['c', 'a', 'b']
253
+ keys.sort.inject(mock($lock)) do |mock, key|
254
+ mock.acquire_lock(key).then
255
+ end
256
+ keys.each { |key| mock($memcache).set(key, @value) }
257
+ keys.each { |key| mock($lock).release_lock(key) }
258
+ @cache.transaction do
259
+ @cache.set(keys[0], @value)
260
+ @cache.set(keys[1], @value)
261
+ @cache.set(keys[2], @value)
262
+ end
263
+ end
264
+
265
+ it "releases locks even if memcache blows up" do
266
+ mock($lock).acquire_lock.with(@key)
267
+ mock($lock).release_lock.with(@key)
268
+ stub($memcache).set(anything, anything) { raise }
269
+ @cache.transaction do
270
+ @cache.set(@key, @value)
271
+ end rescue nil
272
+ end
273
+
274
+ end
275
+
276
+ describe 'Buffering' do
277
+ it "reading from the cache show uncommitted writes" do
278
+ @cache.get(@key).should == nil
279
+ @cache.transaction do
280
+ @cache.set(@key, @value)
281
+ @cache.get(@key).should == @value
282
+ end
283
+ end
284
+
285
+ it "get_multi is buffered" do
286
+ @cache.transaction do
287
+ @cache.set('key1', @value)
288
+ @cache.set('key2', @value)
289
+ @cache.get_multi(['key1', 'key2']).should == { 'key1' => @value, 'key2' => @value }
290
+ $memcache.get_multi(['key1', 'key2']).should == {}
291
+ end
292
+ end
293
+
294
+ it "get is memoized" do
295
+ @cache.set(@key, @value)
296
+ @cache.transaction do
297
+ @cache.get(@key).should == @value
298
+ $memcache.set(@key, "new value")
299
+ @cache.get(@key).should == @value
300
+ end
301
+ end
302
+
303
+ it "add is buffered" do
304
+ @cache.transaction do
305
+ @cache.add(@key, @value)
306
+ $memcache.get(@key).should == nil
307
+ @cache.get(@key).should == @value
308
+ end
309
+ @cache.get(@key).should == @value
310
+ $memcache.get(@key).should == @value
311
+ end
312
+
313
+ describe '#delete' do
314
+ it "within a transaction, delete is isolated" do
315
+ @cache.add(@key, @value)
316
+ @cache.transaction do
317
+ @cache.delete(@key)
318
+ $memcache.add(@key, "another value")
319
+ end
320
+ @cache.get(@key).should == nil
321
+ $memcache.get(@key).should == nil
322
+ end
323
+
324
+ it "within a transaction, delete is buffered" do
325
+ @cache.set(@key, @value)
326
+ @cache.transaction do
327
+ @cache.delete(@key)
328
+ $memcache.get(@key).should == @value
329
+ @cache.get(@key).should == nil
330
+ end
331
+ @cache.get(@key).should == nil
332
+ $memcache.get(@key).should == nil
333
+ end
334
+ end
335
+ end
336
+
337
+ describe '#incr' do
338
+ it "increment be atomic" do
339
+ @cache.set(@key, 0)
340
+ @cache.transaction do
341
+ @cache.incr(@key)
342
+ $memcache.incr(@key)
343
+ end
344
+ @cache.get(@key, true).to_i.should == 2
345
+ $memcache.get(@key, true).to_i.should == 2
346
+ end
347
+
348
+ it "interleaved, etc. increments and sets be ordered" do
349
+ @cache.set(@key, 0)
350
+ @cache.transaction do
351
+ @cache.incr(@key)
352
+ @cache.incr(@key)
353
+ @cache.set(@key, 0)
354
+ @cache.incr(@key)
355
+ @cache.incr(@key)
356
+ end
357
+ @cache.get(@key, true).to_i.should == 2
358
+ $memcache.get(@key, true).to_i.should == 2
359
+ end
360
+ end
361
+
362
+ describe '#decr' do
363
+ it "decrement be atomic" do
364
+ @cache.set(@key, 0)
365
+ @cache.incr(@key, 3)
366
+ @cache.transaction do
367
+ @cache.decr(@key)
368
+ $memcache.decr(@key)
369
+ end
370
+ @cache.get(@key, true).to_i.should == 1
371
+ $memcache.get(@key, true).to_i.should == 1
372
+ end
373
+ end
374
+
375
+ it "retains the value in the transactional cache after committing the transaction" do
376
+ @cache.get(@key).should == nil
377
+ @cache.transaction do
378
+ @cache.set(@key, @value)
379
+ end
380
+ @cache.get(@key).should == @value
381
+ end
382
+
383
+ describe 'when reading from the memcache' do
384
+ it "does NOT show uncommitted writes" do
385
+ @cache.transaction do
386
+ $memcache.get(@key).should == nil
387
+ @cache.set(@key, @value)
388
+ $memcache.get(@key).should == nil
389
+ end
390
+ end
391
+ end
392
+ end
393
+
394
+ describe 'Exception Handling' do
395
+
396
+ it "re-raises exceptions thrown by memcache" do
397
+ stub($memcache).set(anything, anything) { raise }
398
+ lambda do
399
+ @cache.transaction do
400
+ @cache.set(@key, @value)
401
+ end
402
+ end.should raise_error
403
+ end
404
+
405
+ it "rolls back transaction cleanly if an exception is raised" do
406
+ $memcache.get(@key).should == nil
407
+ @cache.get(@key).should == nil
408
+ @cache.transaction do
409
+ @cache.set(@key, @value)
410
+ raise
411
+ end rescue nil
412
+ @cache.get(@key).should == nil
413
+ $memcache.get(@key).should == nil
414
+ end
415
+
416
+ it "does not acquire locks if transaction is rolled back" do
417
+ mock($lock).acquire_lock.never
418
+ mock($lock).release_lock.never
419
+
420
+ @cache.transaction do
421
+ @cache.set(@key, value)
422
+ raise
423
+ end rescue nil
424
+ end
425
+ end
426
+
427
+ describe 'Nested Transactions' do
428
+ it "delegate unsupported messages back to the real cache" do
429
+ @cache.transaction do
430
+ @cache.transaction do
431
+ @cache.transaction do
432
+ mock($memcache).foo(:bar)
433
+ @cache.foo(:bar)
434
+ end
435
+ end
436
+ end
437
+ end
438
+
439
+ it "makes newly set keys only be visible within the transaction in which they were set" do
440
+ @cache.transaction do
441
+ @cache.set('key1', @value)
442
+ @cache.transaction do
443
+ @cache.get('key1').should == @value
444
+ @cache.set('key2', @value)
445
+ @cache.transaction do
446
+ @cache.get('key1').should == @value
447
+ @cache.get('key2').should == @value
448
+ @cache.set('key3', @value)
449
+ end
450
+ end
451
+ @cache.get('key1').should == @value
452
+ @cache.get('key2').should == @value
453
+ @cache.get('key3').should == @value
454
+ end
455
+ @cache.get('key1').should == @value
456
+ @cache.get('key2').should == @value
457
+ @cache.get('key3').should == @value
458
+ end
459
+
460
+ it "not write any values to memcache until the outermost transaction commits" do
461
+ @cache.transaction do
462
+ @cache.set('key1', @value)
463
+ @cache.transaction do
464
+ @cache.set('key2', @value)
465
+ $memcache.get('key1').should == nil
466
+ $memcache.get('key2').should == nil
467
+ end
468
+ $memcache.get('key1').should == nil
469
+ $memcache.get('key2').should == nil
470
+ end
471
+ $memcache.get('key1').should == @value
472
+ $memcache.get('key2').should == @value
473
+ end
474
+
475
+ it "acquire locks in lexical order for all keys" do
476
+ keys = ['c', 'a', 'b']
477
+ keys.sort.inject(mock($lock)) do |mock, key|
478
+ mock.acquire_lock(key).then
479
+ end
480
+ keys.each { |key| mock($memcache).set(key, @value) }
481
+ keys.each { |key| mock($lock).release_lock(key) }
482
+ @cache.transaction do
483
+ @cache.set(keys[0], @value)
484
+ @cache.transaction do
485
+ @cache.set(keys[1], @value)
486
+ @cache.transaction do
487
+ @cache.set(keys[2], @value)
488
+ end
489
+ end
490
+ end
491
+ end
492
+
493
+ it "reads through the real memcache if key has not been written to in a transaction" do
494
+ $memcache.set(@key, @value)
495
+ @cache.transaction do
496
+ @cache.transaction do
497
+ @cache.transaction do
498
+ @cache.get(@key).should == @value
499
+ end
500
+ end
501
+ end
502
+ @cache.get(@key).should == @value
503
+ end
504
+
505
+ describe 'Error Handling' do
506
+ it "releases locks even if memcache blows up" do
507
+ mock($lock).acquire_lock(@key)
508
+ mock($lock).release_lock(@key)
509
+ stub($memcache).set(anything, anything) { raise }
510
+ @cache.transaction do
511
+ @cache.transaction do
512
+ @cache.transaction do
513
+ @cache.set(@key, @value)
514
+ end
515
+ end
516
+ end rescue nil
517
+ end
518
+
519
+ it "re-raise exceptions thrown by memcache" do
520
+ stub($memcache).set(anything, anything) { raise }
521
+ lambda do
522
+ @cache.transaction do
523
+ @cache.transaction do
524
+ @cache.transaction do
525
+ @cache.set(@key, @value)
526
+ end
527
+ end
528
+ end
529
+ end.should raise_error
530
+ end
531
+
532
+ it "rollback transaction cleanly if an exception is raised" do
533
+ $memcache.get(@key).should == nil
534
+ @cache.get(@key).should == nil
535
+ @cache.transaction do
536
+ @cache.transaction do
537
+ @cache.set(@key, @value)
538
+ raise
539
+ end
540
+ end rescue nil
541
+ @cache.get(@key).should == nil
542
+ $memcache.get(@key).should == nil
543
+ end
544
+
545
+ it "not acquire locks if transaction is rolled back" do
546
+ mock($lock).acquire_lock.never
547
+ mock($lock).release_lock.never
548
+
549
+ @cache.transaction do
550
+ @cache.transaction do
551
+ @cache.set(@key, @value)
552
+ raise
553
+ end
554
+ end rescue nil
555
+ end
556
+
557
+ it "support rollbacks" do
558
+ @cache.transaction do
559
+ @cache.set('key1', @value)
560
+ @cache.transaction do
561
+ @cache.get('key1').should == @value
562
+ @cache.set('key2', @value)
563
+ raise
564
+ end rescue nil
565
+ @cache.get('key1').should == @value
566
+ @cache.get('key2').should == nil
567
+ end
568
+ $memcache.get('key1').should == @value
569
+ $memcache.get('key2').should == nil
570
+ end
571
+ end
572
+ end
573
+ end
574
+ end