ngmoco-cache-money 0.2.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,67 @@
1
+ require File.join(File.dirname(__FILE__), '..', '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
+ end
40
+ end
41
+ end
42
+
43
+ describe 'when the cache is not populated' do
44
+ describe '#count(:all, ...)' do
45
+ describe '#count(:all)' do
46
+ it 'uses the database, not the cache' do
47
+ mock(Story).get.never
48
+ Story.count
49
+ end
50
+ end
51
+
52
+ describe '#count(:all, :conditions => ...)' do
53
+ before do
54
+ Story.create!(:title => @title = 'title')
55
+ $memcache.flush_all
56
+ end
57
+
58
+ it "populates the count correctly" do
59
+ Story.count(:all, :conditions => { :title => @title }).should == 1
60
+ Story.fetch("title/#{@title}/count", :raw => true).should =~ /\s*1\s*/
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,364 @@
1
+ require File.join(File.dirname(__FILE__), '..', '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 = ?')" 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 = #{story.id}").should == story
38
+ Story.find(:first, :conditions => "`stories`.id = #{story.id}").should == story
39
+ Story.find(:first, :conditions => "`stories`.`id` = #{story.id}").should == story
40
+ end
41
+ end
42
+
43
+ describe '#find(:first, :readonly => false) and any other options other than conditions are nil' do
44
+ it "does not use the database" do
45
+ story = Story.create!
46
+ mock(Story.connection).execute.never
47
+ Story.find(:first, :conditions => { :id => story.id }, :readonly => false, :limit => nil, :offset => nil, :joins => nil, :include => nil).should == story
48
+ end
49
+ end
50
+
51
+ describe '#find(:first, :readonly => true)' do
52
+ it "uses the database, not the cache" do
53
+ story = Story.create!
54
+ mock(Story).get.never
55
+ Story.find(:first, :conditions => { :id => story.id }, :readonly => true).should == story
56
+ end
57
+ end
58
+
59
+ describe '#find(:first, :join => ...) or find(..., :include => ...)' 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 }, :joins => 'AS stories').should == story
64
+ Story.find(:first, :conditions => { :id => story.id }, :include => :characters).should == story
65
+ end
66
+ end
67
+
68
+ describe '#find(:first)' do
69
+ it 'uses the database, not the cache' do
70
+ mock(Story).get.never
71
+ Story.find(:first)
72
+ end
73
+ end
74
+
75
+ describe '#find(:first, :conditions => "...")' do
76
+ describe 'on unindexed attributes' do
77
+ it 'uses the database, not the cache' do
78
+ story = Story.create!
79
+ mock(Story).get.never
80
+ Story.find(:first, :conditions => "type IS NULL")
81
+ end
82
+ end
83
+
84
+ describe 'on indexed attributes' do
85
+ describe 'when the attributes are integers' do
86
+ it 'does not use the database' do
87
+ story = Story.create!
88
+ mock(Story.connection).execute.never
89
+ Story.find(:first, :conditions => "`stories`.id = #{story.id}") \
90
+ .should == story
91
+ end
92
+ end
93
+
94
+ describe 'when the attributes are non-integers' do
95
+ it 'uses the database, not the cache' do
96
+ story = Story.create!(:title => "title")
97
+ mock(Story.connection).execute.never
98
+ Story.find(:first, :conditions => "`stories`.title = '#{story.title }'") \
99
+ .should == story
100
+ end
101
+ end
102
+
103
+ describe 'when the attributes must be coerced to sql values' do
104
+ it 'does not use the database' do
105
+ story1 = Story.create!(:published => true)
106
+ story2 = Story.create!(:published => false)
107
+ mock(Story.connection).execute.never
108
+ Story.find(:first, :conditions => 'published = 0').should == story2
109
+ end
110
+ end
111
+ end
112
+
113
+ describe '#find(:first, :conditions => [...])' do
114
+ describe 'with one indexed attribute' do
115
+ it 'does not use the database' do
116
+ story = Story.create!
117
+ mock(Story.connection).execute.never
118
+ Story.find(:first, :conditions => ['id = ?', story.id]).should == story
119
+ end
120
+ end
121
+
122
+ describe 'with two attributes that match a combo-index' do
123
+ it 'does not use the database' do
124
+ story = Story.create!(:title => 'title')
125
+ mock(Story.connection).execute.never
126
+ Story.find(:first, :conditions => ['id = ? AND title = ?', story.id, story.title]).should == story
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ describe '#find(:first, :conditions => {...})' do
133
+ it "does not use the database" do
134
+ story = Story.create!(:title => "Sam")
135
+ mock(Story.connection).execute.never
136
+ Story.find(:first, :conditions => { :id => story.id, :title => story.title }).should == story
137
+ end
138
+
139
+ describe 'regardless of hash order' do
140
+ it 'does not use the database' do
141
+ story = Story.create!(:title => "Sam")
142
+ mock(Story.connection).execute.never
143
+ Story.find(:first, :conditions => { :id => story.id, :title => story.title }).should == story
144
+ Story.find(:first, :conditions => { :title => story.title, :id => story.id }).should == story
145
+ end
146
+ end
147
+
148
+ describe 'on unindexed attribtes' do
149
+ it 'uses the database, not the cache' do
150
+ story = Story.create!
151
+ mock(Story).get.never
152
+ Story.find(:first, :conditions => { :id => story.id, :type => story.type }).should == story
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ describe 'when there is a with_scope' do
159
+ describe 'when the with_scope has conditions' do
160
+ describe 'when the scope conditions is a string' do
161
+ it "uses the database, not the cache" do
162
+ story = Story.create!(:title => title = 'title')
163
+ mock(Story.connection).execute.never
164
+ Story.send :with_scope, :find => { :conditions => "title = '#{title}'"} do
165
+ Story.find(:first, :conditions => { :id => story.id }).should == story
166
+ end
167
+ end
168
+ end
169
+
170
+ describe 'when the find conditions is a string' do
171
+ it "does not use the database" do
172
+ story = Story.create!(:title => title = 'title')
173
+ mock(Story.connection).execute.never
174
+ Story.send :with_scope, :find => { :conditions => { :id => story.id }} do
175
+ Story.find(:first, :conditions => "title = '#{title}'").should == story
176
+ end
177
+ end
178
+ end
179
+
180
+ describe '#find(1, :conditions => ...)' do
181
+ it "does not use the database" do
182
+ story = Story.create!
183
+ character = Character.create!(:name => name = 'barbara', :story_id => story)
184
+ mock(Character.connection).execute.never
185
+ Character.send :with_scope, :find => { :conditions => { :story_id => story.id } } do
186
+ Character.find(character.id, :conditions => { :name => name }).should == character
187
+ end
188
+ end
189
+ end
190
+ end
191
+
192
+ describe 'has_many associations' do
193
+ describe '#find(1)' do
194
+ it "does not use the database" do
195
+ story = Story.create!
196
+ character = story.characters.create!
197
+ mock(Character.connection).execute.never
198
+ story.characters.find(character.id).should == character
199
+ end
200
+ end
201
+
202
+ describe '#find(1, 2, ...)' do
203
+ it "does not use the database" do
204
+ story = Story.create!
205
+ character1 = story.characters.create!
206
+ character2 = story.characters.create!
207
+ mock(Character.connection).execute.never
208
+ story.characters.find(character1.id, character2.id).should == [character1, character2]
209
+ end
210
+ end
211
+
212
+ describe '#find_by_attr' do
213
+ it "does not use the database" do
214
+ story = Story.create!
215
+ character = story.characters.create!
216
+ mock(Character.connection).execute.never
217
+ story.characters.find_by_id(character.id).should == character
218
+ end
219
+ end
220
+ end
221
+ end
222
+
223
+ describe '#find(:all)' do
224
+ it "uses the database, not the cache" do
225
+ character = Character.create!
226
+ mock(Character).get.never
227
+ Character.find(:all).should == [character]
228
+ end
229
+
230
+ describe '#find(:all, :conditions => {...})' do
231
+ describe 'when the index is not empty' do
232
+ it 'does not use the database' do
233
+ story1 = Story.create!(:title => title = "title")
234
+ story2 = Story.create!(:title => title)
235
+ mock(Story.connection).execute.never
236
+ Story.find(:all, :conditions => { :title => story1.title }).should == [story1, story2]
237
+ end
238
+ end
239
+ end
240
+
241
+ describe '#find(:all, :limit => ..., :offset => ...)' do
242
+ it "cached attributes should support limits and offsets" do
243
+ character1 = Character.create!(:name => "Sam", :story_id => 1)
244
+ character2 = Character.create!(:name => "Sam", :story_id => 1)
245
+ character3 = Character.create!(:name => "Sam", :story_id => 1)
246
+ mock(Character.connection).execute.never
247
+
248
+ Character.find(:all, :conditions => { :name => character1.name, :story_id => character1.story_id }, :limit => 1).should == [character1]
249
+ Character.find(:all, :conditions => { :name => character1.name, :story_id => character1.story_id }, :offset => 1).should == [character2, character3]
250
+ Character.find(:all, :conditions => { :name => character1.name, :story_id => character1.story_id }, :limit => 1, :offset => 1).should == [character2]
251
+ end
252
+ end
253
+ end
254
+
255
+ describe '#find([...])' do
256
+ describe '#find([1, 2, ...], :conditions => ...)' do
257
+ it "uses the database, not the cache" do
258
+ story1, story2 = Story.create!, Story.create!
259
+ mock(Story).get.never
260
+ Story.find([story1.id, story2.id], :conditions => "type IS NULL").should == [story1, story2]
261
+ end
262
+ end
263
+
264
+ describe '#find([1], :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], :conditions => "type IS NULL").should == [story1]
269
+ end
270
+ end
271
+ end
272
+
273
+ describe '#find_by_attr' do
274
+ describe 'on indexed attributes' do
275
+ describe '#find_by_id(id)' do
276
+ it "does not use the database" do
277
+ story = Story.create!
278
+ mock(Story.connection).execute.never
279
+ Story.find_by_id(story.id).should == story
280
+ end
281
+ end
282
+
283
+ describe '#find_by_title(title)' do
284
+ it "does not use the database" do
285
+ story1 = Story.create!(:title => 'title1')
286
+ story2 = Story.create!(:title => 'title2')
287
+ mock(Story.connection).execute.never
288
+ Story.find_by_title('title1').should == story1
289
+ end
290
+ end
291
+ end
292
+ end
293
+
294
+ describe "Single Table Inheritence" do
295
+ describe '#find(:all, ...)' do
296
+ it "does not use the database" do
297
+ story, epic, oral = Story.create!(:title => title = 'foo'), Epic.create!(:title => title), Oral.create!(:title => title)
298
+ mock(Story.connection).execute.never
299
+ Story.find(:all, :conditions => { :title => title }).should == [story, epic, oral]
300
+ Epic.find(:all, :conditions => { :title => title }).should == [epic, oral]
301
+ Oral.find(:all, :conditions => { :title => title }).should == [oral]
302
+ end
303
+ end
304
+ end
305
+ end
306
+
307
+ describe '#without_cache' do
308
+ describe 'when finders are called within the provided block' do
309
+ it 'uses the database not the cache' do
310
+ story = Story.create!
311
+ mock(Story).get.never
312
+ Story.without_cache do
313
+ Story.find(story.id).should == story
314
+ end
315
+ end
316
+ end
317
+ end
318
+ end
319
+
320
+ describe 'when the cache is not populated' do
321
+ before do
322
+ @story = Story.create!(:title => 'title')
323
+ $memcache.flush_all
324
+ end
325
+
326
+ describe '#find(:first, ...)' do
327
+ it 'populates the cache' do
328
+ Story.find(:first, :conditions => { :title => @story.title })
329
+ Story.fetch("title/#{@story.title}").should == [@story.id]
330
+ end
331
+ end
332
+
333
+ describe '#find_by_attr' do
334
+ it 'populates the cache' do
335
+ Story.find_by_title(@story.title)
336
+ Story.fetch("title/#{@story.title}").should == [@story.id]
337
+ end
338
+ end
339
+
340
+ describe '#find(:all, :conditions => ...)' do
341
+ it 'populates the cache' do
342
+ Story.find(:all, :conditions => { :title => @story.title })
343
+ Story.fetch("title/#{@story.title}").should == [@story.id]
344
+ end
345
+ end
346
+
347
+ describe '#find(1)' do
348
+ it 'populates the cache' do
349
+ Story.find(@story.id)
350
+ Story.fetch("id/#{@story.id}").should == [@story]
351
+ end
352
+ end
353
+
354
+ describe 'when there is a with_scope' do
355
+ it "uses the database, not the cache" do
356
+ Story.send :with_scope, :find => { :conditions => { :title => @story.title }} do
357
+ Story.find(:first, :conditions => { :id => @story.id }).should == @story
358
+ end
359
+ end
360
+ end
361
+ end
362
+ end
363
+ end
364
+ end
@@ -0,0 +1,109 @@
1
+ require File.join(File.dirname(__FILE__), '..', '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
+
26
+ end
27
+
28
+ it "releases the lock even if the block raises" do
29
+ $memcache.get("lock/lock_key").should == nil
30
+ $lock.synchronize('lock_key') { raise } rescue nil
31
+ $memcache.get("lock/lock_key").should == nil
32
+ end
33
+
34
+ it "does not block on recursive lock acquisition" do
35
+ $lock.synchronize('lock_key') do
36
+ lambda { $lock.synchronize('lock_key') {} }.should_not raise_error
37
+ end
38
+ end
39
+ end
40
+
41
+ describe '#acquire_lock' do
42
+ it "creates a lock at a given cache key" do
43
+ $memcache.get("lock/lock_key").should == nil
44
+ $lock.acquire_lock("lock_key")
45
+ $memcache.get("lock/lock_key").should_not == nil
46
+ end
47
+
48
+ describe 'when given a timeout for the lock' do
49
+ it "correctly sets timeout on memcache entries" do
50
+ mock($memcache).add('lock/lock_key', Process.pid, timeout = 10) { true }
51
+ # $lock.acquire_lock('lock_key', timeout)
52
+ lambda { $lock.acquire_lock('lock_key', timeout, 1) }.should raise_error
53
+ end
54
+ end
55
+
56
+ describe 'when to processes contend for a lock' do
57
+ it "prevents two processes from acquiring the same lock at the same time" do
58
+ $lock.acquire_lock('lock_key')
59
+ as_another_process do
60
+ stub($lock).exponential_sleep
61
+ lambda { $lock.acquire_lock('lock_key') }.should raise_error
62
+ end
63
+ end
64
+
65
+ describe 'when given a number of times to retry' do
66
+ it "retries specified number of times" do
67
+ $lock.acquire_lock('lock_key')
68
+ as_another_process do
69
+ mock($memcache).add("lock/lock_key", Process.pid, timeout = 10) { false }.times(retries = 3)
70
+ stub($lock).exponential_sleep
71
+ lambda { $lock.acquire_lock('lock_key', timeout, retries) }.should raise_error
72
+ end
73
+ end
74
+ end
75
+
76
+ describe 'when given an initial wait' do
77
+ it 'sleeps exponentially starting with the initial wait' do
78
+ stub($lock).sleep(initial_wait = 0.123)
79
+ stub($lock).sleep(2 * initial_wait)
80
+ stub($lock).sleep(4 * initial_wait)
81
+ stub($lock).sleep(8 * initial_wait)
82
+ $lock.acquire_lock('lock_key')
83
+ as_another_process do
84
+ lambda { $lock.acquire_lock('lock_key', Lock::DEFAULT_EXPIRY, Lock::DEFAULT_RETRY, initial_wait) }.should raise_error
85
+ end
86
+ end
87
+ end
88
+
89
+ def as_another_process
90
+ current_pid = Process.pid
91
+ stub(Process).pid { current_pid + 1 }
92
+ yield
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+
99
+ describe '#release_lock' do
100
+ it "deletes the lock for a given cache key" do
101
+ $memcache.get("lock/lock_key").should == nil
102
+ $lock.acquire_lock("lock_key")
103
+ $memcache.get("lock/lock_key").should_not == nil
104
+ $lock.release_lock("lock_key")
105
+ $memcache.get("lock/lock_key").should == nil
106
+ end
107
+ end
108
+ end
109
+ end