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.
- data/README +1 -0
- data/TODO +20 -0
- data/UNSUPPORTED_FEATURES +14 -0
- data/config/environment.rb +6 -0
- data/config/memcache.yml +6 -0
- data/db/schema.rb +11 -0
- data/lib/cash.rb +53 -0
- data/lib/cash/accessor.rb +78 -0
- data/lib/cash/buffered.rb +126 -0
- data/lib/cash/config.rb +64 -0
- data/lib/cash/finders.rb +40 -0
- data/lib/cash/index.rb +207 -0
- data/lib/cash/local.rb +59 -0
- data/lib/cash/lock.rb +52 -0
- data/lib/cash/mock.rb +86 -0
- data/lib/cash/query/abstract.rb +162 -0
- data/lib/cash/query/calculation.rb +45 -0
- data/lib/cash/query/primary_key.rb +51 -0
- data/lib/cash/query/select.rb +16 -0
- data/lib/cash/transactional.rb +42 -0
- data/lib/cash/util/array.rb +9 -0
- data/lib/cash/write_through.rb +72 -0
- data/spec/cash/accessor_spec.rb +133 -0
- data/spec/cash/active_record_spec.rb +190 -0
- data/spec/cash/calculations_spec.rb +67 -0
- data/spec/cash/finders_spec.rb +343 -0
- data/spec/cash/lock_spec.rb +87 -0
- data/spec/cash/order_spec.rb +166 -0
- data/spec/cash/transactional_spec.rb +574 -0
- data/spec/cash/window_spec.rb +195 -0
- data/spec/cash/write_through_spec.rb +223 -0
- data/spec/spec_helper.rb +55 -0
- metadata +100 -0
@@ -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
|