netshade-cache-money 0.2.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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