sreeix-cache-money 0.2.24.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.
Files changed (49) hide show
  1. data/LICENSE +201 -0
  2. data/README +210 -0
  3. data/README.markdown +210 -0
  4. data/TODO +17 -0
  5. data/UNSUPPORTED_FEATURES +13 -0
  6. data/config/environment.rb +16 -0
  7. data/config/memcached.yml +6 -0
  8. data/db/schema.rb +18 -0
  9. data/init.rb +1 -0
  10. data/lib/cache_money.rb +91 -0
  11. data/lib/cash/accessor.rb +83 -0
  12. data/lib/cash/buffered.rb +129 -0
  13. data/lib/cash/config.rb +75 -0
  14. data/lib/cash/fake.rb +83 -0
  15. data/lib/cash/finders.rb +38 -0
  16. data/lib/cash/index.rb +214 -0
  17. data/lib/cash/local.rb +76 -0
  18. data/lib/cash/lock.rb +63 -0
  19. data/lib/cash/mock.rb +154 -0
  20. data/lib/cash/query/abstract.rb +210 -0
  21. data/lib/cash/query/calculation.rb +45 -0
  22. data/lib/cash/query/primary_key.rb +50 -0
  23. data/lib/cash/query/select.rb +16 -0
  24. data/lib/cash/request.rb +3 -0
  25. data/lib/cash/transactional.rb +43 -0
  26. data/lib/cash/util/array.rb +9 -0
  27. data/lib/cash/util/marshal.rb +19 -0
  28. data/lib/cash/write_through.rb +69 -0
  29. data/lib/mem_cached_session_store.rb +49 -0
  30. data/lib/mem_cached_support_store.rb +135 -0
  31. data/lib/memcached_wrapper.rb +263 -0
  32. data/rails/init.rb +38 -0
  33. data/spec/cash/accessor_spec.rb +159 -0
  34. data/spec/cash/active_record_spec.rb +224 -0
  35. data/spec/cash/buffered_spec.rb +9 -0
  36. data/spec/cash/calculations_spec.rb +78 -0
  37. data/spec/cash/finders_spec.rb +430 -0
  38. data/spec/cash/local_buffer_spec.rb +9 -0
  39. data/spec/cash/local_spec.rb +9 -0
  40. data/spec/cash/lock_spec.rb +108 -0
  41. data/spec/cash/marshal_spec.rb +60 -0
  42. data/spec/cash/order_spec.rb +138 -0
  43. data/spec/cash/shared.rb +62 -0
  44. data/spec/cash/transactional_spec.rb +578 -0
  45. data/spec/cash/window_spec.rb +195 -0
  46. data/spec/cash/write_through_spec.rb +245 -0
  47. data/spec/memcached_wrapper_test.rb +209 -0
  48. data/spec/spec_helper.rb +60 -0
  49. metadata +151 -0
@@ -0,0 +1,78 @@
1
+ require "spec_helper"
2
+
3
+ module Cash
4
+ describe Finders do
5
+ describe 'Calculations' do
6
+ describe 'when the cache is populated' do
7
+ before do
8
+ @stories = [Story.create!(:title => @title = 'asdf'), Story.create!(:title => @title)]
9
+ end
10
+
11
+ describe '#count(:all, :conditions => ...)' do
12
+ it "does not use the database" do
13
+ Story.count(:all, :conditions => { :title => @title }).should == @stories.size
14
+ end
15
+ end
16
+
17
+ describe '#count(:column, :conditions => ...)' do
18
+ it "uses the database, not the cache" do
19
+ mock(Story).get.never
20
+ Story.count(:title, :conditions => { :title => @title }).should == @stories.size
21
+ end
22
+ end
23
+
24
+ describe '#count(:all, :distinct => ..., :select => ...)' do
25
+ it 'uses the database, not the cache' do
26
+ mock(Story).get.never
27
+ Story.count(:all, :distinct => true, :select => :title, :conditions => { :title => @title }).should == @stories.collect(&:title).uniq.size
28
+ end
29
+ end
30
+
31
+ describe 'association proxies' do
32
+ describe '#count(:all, :conditions => ...)' do
33
+ it 'does not use the database' do
34
+ story = Story.create!
35
+ characters = [story.characters.create!(:name => name = 'name'), story.characters.create!(:name => name)]
36
+ mock(Story.connection).execute.never
37
+ story.characters.count(:all, :conditions => { :name => name }).should == characters.size
38
+ end
39
+
40
+ it 'has correct counter cache' do
41
+ story = Story.create!
42
+ characters = [story.characters.create!(:name => name = 'name'), story.characters.create!(:name => name)]
43
+ $memcache.flush_all
44
+ story.characters.find(:all, :conditions => { :name => name }) == characters
45
+ story.characters.count(:all, :conditions => { :name => name }).should == characters.size
46
+ story.characters.find(:all, :conditions => { :name => name }) == characters
47
+ mock(Story.connection).execute.never
48
+ story.characters.count(:all, :conditions => { :name => name }).should == characters.size
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ describe 'when the cache is not populated' do
55
+ describe '#count(:all, ...)' do
56
+ describe '#count(:all)' do
57
+ it 'uses the database, not the cache' do
58
+ mock(Story).get.never
59
+ Story.count
60
+ end
61
+ end
62
+
63
+ describe '#count(:all, :conditions => ...)' do
64
+ before do
65
+ Story.create!(:title => @title = 'title')
66
+ $memcache.flush_all
67
+ end
68
+
69
+ it "populates the count correctly" do
70
+ Story.count(:all, :conditions => { :title => @title }).should == 1
71
+ Story.fetch("title/#{@title}/count", :raw => true).should =~ /\s*1\s*/
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,430 @@
1
+ require "spec_helper"
2
+
3
+ module Cash
4
+ describe Finders do
5
+ describe 'Cache Usage' do
6
+ describe 'when the cache is populated' do
7
+ describe '#find' do
8
+ describe '#find(1)' do
9
+ it 'does not use the database' do
10
+ story = Story.create!
11
+ mock(Story.connection).execute.never
12
+ Story.find(story.id).should == story
13
+ end
14
+ end
15
+
16
+ describe '#find(object)' do
17
+ it 'uses the objects quoted id' do
18
+ story = Story.create!
19
+ mock(Story.connection).execute.never
20
+ Story.find(story).should == story
21
+ end
22
+ end
23
+
24
+ describe '#find(:first, ...)' do
25
+ describe '#find(:first, :conditions => { :id => ? })' do
26
+ it "does not use the database" do
27
+ story = Story.create!
28
+ mock(Story.connection).execute.never
29
+ Story.find(:first, :conditions => { :id => story.id }).should == story
30
+ end
31
+ end
32
+
33
+ describe '#find(:first, :conditions => [ "id = :id", { :id => story.id } ])' do
34
+ it "does not use the database" do
35
+ story = Story.create!
36
+ mock(Story.connection).execute.never
37
+ Story.find(:first, :conditions => [ "id = :id", { :id => story.id } ]).should == story
38
+ end
39
+ end
40
+
41
+ describe "#find(:first, :conditions => 'id = ?')" do
42
+ it "does not use the database" do
43
+ story = Story.create!
44
+ mock(Story.connection).execute.never
45
+ Story.find(:first, :conditions => "id = #{story.id}").should == story
46
+ Story.find(:first, :conditions => "`stories`.id = #{story.id}").should == story
47
+ Story.find(:first, :conditions => "`stories`.`id` = #{story.id}").should == story
48
+ end
49
+ end
50
+
51
+ describe '#find(:first, :readonly => false) and any other options other than conditions are nil' do
52
+ it "does not use the database" do
53
+ story = Story.create!
54
+ mock(Story.connection).execute.never
55
+ Story.find(:first, :conditions => { :id => story.id }, :readonly => false, :limit => nil, :offset => nil, :joins => nil, :include => nil).should == story
56
+ end
57
+ end
58
+
59
+ describe '#find(:first, :readonly => true)' do
60
+ it "uses the database, not the cache" do
61
+ story = Story.create!
62
+ mock(Story).get.never
63
+ Story.find(:first, :conditions => { :id => story.id }, :readonly => true).should == story
64
+ end
65
+ end
66
+
67
+ describe '#find(:first, :join => ...) or find(..., :include => ...)' do
68
+ it "uses the database, not the cache" do
69
+ story = Story.create!
70
+ mock(Story).get.never
71
+ Story.find(:first, :conditions => { :id => story.id }, :joins => 'AS stories').should == story
72
+ Story.find(:first, :conditions => { :id => story.id }, :include => :characters).should == story
73
+ end
74
+ end
75
+
76
+ describe '#find(:first)' do
77
+ it 'uses the database, not the cache' do
78
+ mock(Story).get.never
79
+ Story.find(:first)
80
+ end
81
+ end
82
+
83
+ describe '#find(:first, :conditions => "...")' do
84
+ describe 'on unindexed attributes' do
85
+ it 'uses the database, not the cache' do
86
+ story = Story.create!
87
+ mock(Story).get.never
88
+ Story.find(:first, :conditions => "type IS NULL")
89
+ end
90
+ end
91
+
92
+ describe 'on indexed attributes' do
93
+ describe 'when the attributes are integers' do
94
+ it 'does not use the database' do
95
+ story = Story.create!
96
+ mock(Story.connection).execute.never
97
+ Story.find(:first, :conditions => "`stories`.id = #{story.id}") \
98
+ .should == story
99
+ end
100
+ end
101
+
102
+ describe 'when the attributes are non-integers' do
103
+ it 'uses the database, not the cache' do
104
+ story = Story.create!(:title => "title")
105
+ mock(Story.connection).execute.never
106
+ Story.find(:first, :conditions => "`stories`.title = '#{story.title }'") \
107
+ .should == story
108
+ end
109
+ end
110
+
111
+ describe 'when the attributes must be coerced to sql values' do
112
+ it 'does not use the database' do
113
+ story1 = Story.create!(:published => true)
114
+ story2 = Story.create!(:published => false)
115
+ mock(Story.connection).execute.never
116
+ Story.find(:first, :conditions => 'published = 0').should == story2
117
+ end
118
+ end
119
+ end
120
+
121
+ describe '#find(:first, :conditions => [...])' do
122
+ describe 'with one indexed attribute' do
123
+ it 'does not use the database' do
124
+ story = Story.create!
125
+ mock(Story.connection).execute.never
126
+ Story.find(:first, :conditions => ['id = ?', story.id]).should == story
127
+ end
128
+ end
129
+
130
+ describe 'with two attributes that match a combo-index' do
131
+ it 'does not use the database' do
132
+ story = Story.create!(:title => 'title')
133
+ mock(Story.connection).execute.never
134
+ Story.find(:first, :conditions => ['id = ? AND title = ?', story.id, story.title]).should == story
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ describe '#find(:first, :conditions => {...})' do
141
+ it "does not use the database" do
142
+ story = Story.create!(:title => "Sam")
143
+ mock(Story.connection).execute.never
144
+ Story.find(:first, :conditions => { :id => story.id, :title => story.title }).should == story
145
+ end
146
+
147
+ describe 'regardless of hash order' do
148
+ it 'does not use the database' do
149
+ story = Story.create!(:title => "Sam")
150
+ mock(Story.connection).execute.never
151
+ Story.find(:first, :conditions => { :id => story.id, :title => story.title }).should == story
152
+ Story.find(:first, :conditions => { :title => story.title, :id => story.id }).should == story
153
+ end
154
+ end
155
+
156
+ describe 'on unindexed attribtes' do
157
+ it 'uses the database, not the cache' do
158
+ story = Story.create!
159
+ mock(Story).get.never
160
+ Story.find(:first, :conditions => { :id => story.id, :type => story.type }).should == story
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ describe 'when there is a with_scope' do
167
+ describe 'when the with_scope has conditions' do
168
+ describe 'when the scope conditions is a string' do
169
+ it "uses the database, not the cache" do
170
+ story = Story.create!(:title => title = 'title')
171
+ mock(Story.connection).execute.never
172
+ Story.send :with_scope, :find => { :conditions => "title = '#{title}'"} do
173
+ Story.find(:first, :conditions => { :id => story.id }).should == story
174
+ end
175
+ end
176
+ end
177
+
178
+ describe 'when the find conditions is a string' do
179
+ it "does not use the database" do
180
+ story = Story.create!(:title => title = 'title')
181
+ mock(Story.connection).execute.never
182
+ Story.send :with_scope, :find => { :conditions => { :id => story.id }} do
183
+ Story.find(:first, :conditions => "title = '#{title}'").should == story
184
+ end
185
+ end
186
+ end
187
+
188
+ describe '#find(1, :conditions => ...)' do
189
+ it "does not use the database" do
190
+ story = Story.create!
191
+ character = Character.create!(:name => name = 'barbara', :story_id => story)
192
+ mock(Character.connection).execute.never
193
+ Character.send :with_scope, :find => { :conditions => { :story_id => story.id } } do
194
+ Character.find(character.id, :conditions => { :name => name }).should == character
195
+ end
196
+ end
197
+ end
198
+ end
199
+
200
+ describe 'has_many associations' do
201
+ describe '#find(1)' do
202
+ it "does not use the database" do
203
+ story = Story.create!
204
+ character = story.characters.create!
205
+ mock(Character.connection).execute.never
206
+ story.characters.find(character.id).should == character
207
+ end
208
+ end
209
+
210
+ describe '#find(1, 2, ...)' do
211
+ it "does not use the database" do
212
+ story = Story.create!
213
+ character1 = story.characters.create!
214
+ character2 = story.characters.create!
215
+ mock(Character.connection).execute.never
216
+ story.characters.find(character1.id, character2.id).should == [character1, character2]
217
+ end
218
+ end
219
+
220
+ describe '#find_by_attr' do
221
+ it "does not use the database" do
222
+ story = Story.create!
223
+ character = story.characters.create!
224
+ mock(Character.connection).execute.never
225
+ story.characters.find_by_id(character.id).should == character
226
+ end
227
+ end
228
+ end
229
+ end
230
+
231
+ describe '#find(:all)' do
232
+ it "uses the database, not the cache" do
233
+ character = Character.create!
234
+ mock(Character).get.never
235
+ Character.find(:all).should == [character]
236
+ end
237
+
238
+ describe '#find(:all, :conditions => {...})' do
239
+ describe 'when the index is not empty' do
240
+ it 'does not use the database' do
241
+ story1 = Story.create!(:title => title = "title")
242
+ story2 = Story.create!(:title => title)
243
+ mock(Story.connection).execute.never
244
+ Story.find(:all, :conditions => { :title => story1.title }).should == [story1, story2]
245
+ end
246
+ end
247
+ end
248
+
249
+ describe '#find(:all, :limit => ..., :offset => ...)' do
250
+ it "cached attributes should support limits and offsets" do
251
+ character1 = Character.create!(:name => "Sam", :story_id => 1)
252
+ character2 = Character.create!(:name => "Sam", :story_id => 1)
253
+ character3 = Character.create!(:name => "Sam", :story_id => 1)
254
+ mock(Character.connection).execute.never
255
+
256
+ Character.find(:all, :conditions => { :name => character1.name, :story_id => character1.story_id }, :limit => 1).should == [character1]
257
+ Character.find(:all, :conditions => { :name => character1.name, :story_id => character1.story_id }, :offset => 1).should == [character2, character3]
258
+ Character.find(:all, :conditions => { :name => character1.name, :story_id => character1.story_id }, :limit => 1, :offset => 1).should == [character2]
259
+ end
260
+ end
261
+ end
262
+
263
+ describe '#find([...])' do
264
+ describe '#find([1, 2, ...], :conditions => ...)' do
265
+ it "uses the database, not the cache" do
266
+ story1, story2 = Story.create!, Story.create!
267
+ mock(Story).get.never
268
+ Story.find([story1.id, story2.id], :conditions => "type IS NULL").should == [story1, story2]
269
+ end
270
+ end
271
+
272
+ describe '#find([1], :conditions => ...)' do
273
+ it "uses the database, not the cache" do
274
+ story1, story2 = Story.create!, Story.create!
275
+ mock(Story).get.never
276
+ Story.find([story1.id], :conditions => "type IS NULL").should == [story1]
277
+ end
278
+ end
279
+ end
280
+
281
+ describe '#find_by_attr' do
282
+ describe 'on indexed attributes' do
283
+ describe '#find_by_id(id)' do
284
+ it "does not use the database" do
285
+ story = Story.create!
286
+ mock(Story.connection).execute.never
287
+ Story.find_by_id(story.id).should == story
288
+ end
289
+ end
290
+
291
+ describe '#find_by_title(title)' do
292
+ it "does not use the database" do
293
+ story1 = Story.create!(:title => 'title1')
294
+ story2 = Story.create!(:title => 'title2')
295
+ mock(Story.connection).execute.never
296
+ Story.find_by_title('title1').should == story1
297
+ end
298
+ end
299
+ end
300
+ end
301
+
302
+ describe "Single Table Inheritence" do
303
+ describe '#find(:all, ...)' do
304
+ it "does not use the database" do
305
+ story, epic, oral = Story.create!(:title => title = 'foo'), Epic.create!(:title => title), Oral.create!(:title => title)
306
+ mock(Story.connection).execute.never
307
+ Story.find(:all, :conditions => { :title => title }).should == [story, epic, oral]
308
+ Epic.find(:all, :conditions => { :title => title }).should == [epic, oral]
309
+ Oral.find(:all, :conditions => { :title => title }).should == [oral]
310
+ end
311
+ end
312
+ end
313
+ end
314
+
315
+ describe '#without_cache' do
316
+ describe 'when finders are called within the provided block' do
317
+ it 'uses the database not the cache' do
318
+ story = Story.create!
319
+ mock(Story).get.never
320
+ Story.without_cache do
321
+ Story.find(story.id).should == story
322
+ end
323
+ end
324
+ end
325
+ end
326
+ end
327
+
328
+ describe 'when the cache is not populated' do
329
+ before do
330
+ @story = Story.create!(:title => 'title')
331
+ $memcache.flush_all
332
+ end
333
+
334
+ describe '#find(:first, ...)' do
335
+ it 'populates the cache' do
336
+ Story.find(:first, :conditions => { :title => @story.title })
337
+ Story.fetch("title/#{@story.title}").should == [@story.id]
338
+ end
339
+ end
340
+
341
+ describe '#find_by_attr' do
342
+ before(:each) do
343
+ Story.find_by_title(@story.title) # populates cache for title with [@story.id]
344
+ end
345
+
346
+ it 'populates the cache' do
347
+ Story.fetch("title/#{@story.title}").should == [@story.id]
348
+ end
349
+
350
+ it 'populates the cache when finding by non-primary-key attribute' do
351
+ Story.find_by_title(@story.title) # populates cache for id with record
352
+
353
+ mock(Story.connection).execute.never # should hit cache only
354
+ Story.find_by_title(@story.title).id.should == @story.id
355
+ end
356
+ end
357
+
358
+ describe '#find(:all, :conditions => ...)' do
359
+ it 'populates the cache' do
360
+ Story.find(:all, :conditions => { :title => @story.title })
361
+ Story.fetch("title/#{@story.title}").should == [@story.id]
362
+ end
363
+ end
364
+
365
+ describe '#find(:all, :conditions => ..., :order => ...)' do
366
+ before(:each) do
367
+ @short1 = Short.create(:title => 'title',
368
+ :subtitle => 'subtitle')
369
+ @short2 = Short.create(:title => 'another title',
370
+ :subtitle => 'subtitle')
371
+ $memcache.flush_all
372
+ # debugger
373
+ Short.find(:all, :conditions => { :subtitle => @short1.subtitle },
374
+ :order => 'title')
375
+ end
376
+
377
+ it 'populates the cache' do
378
+ Short.fetch("subtitle/subtitle").should_not be_blank
379
+ end
380
+
381
+ it 'returns objects in the correct order' do
382
+ Short.fetch("subtitle/subtitle").should ==
383
+ [@short2.id, @short1.id]
384
+ end
385
+
386
+ it 'finds objects in the correct order' do
387
+ Short.find(:all, :conditions => { :subtitle => @short1.subtitle },
388
+ :order => 'title').map(&:id).should == [@short2.id, @short1.id]
389
+ end
390
+
391
+ it 'populates cache for each object' do
392
+ Short.fetch("id/#{@short1.id}").should == [@short1]
393
+ Short.fetch("id/#{@short2.id}").should == [@short2]
394
+ end
395
+ end
396
+
397
+ describe '#find(1)' do
398
+ it 'populates the cache' do
399
+ Story.find(@story.id)
400
+ Story.fetch("id/#{@story.id}").should == [@story]
401
+ end
402
+ end
403
+
404
+ describe '#find([1, 2, ...])' do
405
+ before do
406
+ @short1 = Short.create(:title => 'title1', :subtitle => 'subtitle')
407
+ @short2 = Short.create(:title => 'title2', :subtitle => 'subtitle')
408
+ @short3 = Short.create(:title => 'title3', :subtitle => 'subtitle')
409
+ $memcache.flush_all
410
+ end
411
+
412
+ it 'populates the cache' do
413
+ Short.find(@short1.id, @short2.id, @short3.id) == [@short1, @short2, @short3]
414
+ Short.fetch("id/#{@short1.id}").should == [@short1]
415
+ Short.fetch("id/#{@short2.id}").should == [@short2]
416
+ Short.fetch("id/#{@short3.id}").should == [@short3]
417
+ end
418
+ end
419
+
420
+ describe 'when there is a with_scope' do
421
+ it "uses the database, not the cache" do
422
+ Story.send :with_scope, :find => { :conditions => { :title => @story.title }} do
423
+ Story.find(:first, :conditions => { :id => @story.id }).should == @story
424
+ end
425
+ end
426
+ end
427
+ end
428
+ end
429
+ end
430
+ end
@@ -0,0 +1,9 @@
1
+ require "spec_helper"
2
+
3
+ module Cash
4
+ describe LocalBuffer do
5
+ it "should have method missing as a private method" do
6
+ LocalBuffer.private_instance_methods.should include(:method_missing)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require "spec_helper"
2
+
3
+ module Cash
4
+ describe Local do
5
+ it "should have method missing as a private method" do
6
+ Local.private_instance_methods.should include(:method_missing)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,108 @@
1
+ require "spec_helper"
2
+
3
+ module Cash
4
+ describe Lock do
5
+ describe '#synchronize' do
6
+ it "yields the block" do
7
+ block_was_called = false
8
+ $lock.synchronize('lock_key') do
9
+ block_was_called = true
10
+ end
11
+ block_was_called.should == true
12
+ end
13
+
14
+ it "acquires the specified lock before the block is run" do
15
+ $memcache.get("lock/lock_key").should == nil
16
+ $lock.synchronize('lock_key') do
17
+ $memcache.get("lock/lock_key").should_not == nil
18
+ end
19
+ end
20
+
21
+ it "releases the lock after the block is run" do
22
+ $memcache.get("lock/lock_key").should == nil
23
+ $lock.synchronize('lock_key') {}
24
+ $memcache.get("lock/lock_key").should == nil
25
+ end
26
+
27
+ it "releases the lock even if the block raises" do
28
+ $memcache.get("lock/lock_key").should == nil
29
+ $lock.synchronize('lock_key') { raise } rescue nil
30
+ $memcache.get("lock/lock_key").should == nil
31
+ end
32
+
33
+ it "does not block on recursive lock acquisition" do
34
+ $lock.synchronize('lock_key') do
35
+ lambda { $lock.synchronize('lock_key') {} }.should_not raise_error
36
+ end
37
+ end
38
+ end
39
+
40
+ describe '#acquire_lock' do
41
+ it "creates a lock at a given cache key" do
42
+ $memcache.get("lock/lock_key").should == nil
43
+ $lock.acquire_lock("lock_key")
44
+ $memcache.get("lock/lock_key").should_not == nil
45
+ end
46
+
47
+ describe 'when given a timeout for the lock' do
48
+ it "correctly sets timeout on memcache entries" do
49
+ mock($memcache).add('lock/lock_key', "#{Socket.gethostname} #{Process.pid}", timeout = 10) { true }
50
+ # $lock.acquire_lock('lock_key', timeout)
51
+ lambda { $lock.acquire_lock('lock_key', timeout, 1) }.should raise_error
52
+ end
53
+ end
54
+
55
+ describe 'when to processes contend for a lock' do
56
+ it "prevents two processes from acquiring the same lock at the same time" do
57
+ $lock.acquire_lock('lock_key')
58
+ as_another_process do
59
+ stub($lock).exponential_sleep
60
+ lambda { $lock.acquire_lock('lock_key') }.should raise_error
61
+ end
62
+ end
63
+
64
+ describe 'when given a number of times to retry' do
65
+ it "retries specified number of times" do
66
+ $lock.acquire_lock('lock_key')
67
+ as_another_process do
68
+ mock($memcache).add("lock/lock_key", "#{Socket.gethostname} #{Process.pid}", timeout = 10) { false }.times(retries = 3)
69
+ stub($lock).exponential_sleep
70
+ lambda { $lock.acquire_lock('lock_key', timeout, retries) }.should raise_error
71
+ end
72
+ end
73
+ end
74
+
75
+ describe 'when given an initial wait' do
76
+ it 'sleeps exponentially starting with the initial wait' do
77
+ stub($lock).sleep(initial_wait = 0.123)
78
+ stub($lock).sleep(2 * initial_wait)
79
+ stub($lock).sleep(4 * initial_wait)
80
+ stub($lock).sleep(8 * initial_wait)
81
+ $lock.acquire_lock('lock_key')
82
+ as_another_process do
83
+ lambda { $lock.acquire_lock('lock_key', Lock::DEFAULT_EXPIRY, Lock::DEFAULT_RETRY, initial_wait) }.should raise_error
84
+ end
85
+ end
86
+ end
87
+
88
+ def as_another_process
89
+ current_pid = Process.pid
90
+ stub(Process).pid { current_pid + 1 }
91
+ yield
92
+ end
93
+
94
+ end
95
+
96
+ end
97
+
98
+ describe '#release_lock' do
99
+ it "deletes the lock for a given cache key" do
100
+ $memcache.get("lock/lock_key").should == nil
101
+ $lock.acquire_lock("lock_key")
102
+ $memcache.get("lock/lock_key").should_not == nil
103
+ $lock.release_lock("lock_key")
104
+ $memcache.get("lock/lock_key").should == nil
105
+ end
106
+ end
107
+ end
108
+ end