memcache-client-activerecord 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ sqlite3:
2
+ adapter: sqlite3
3
+ database: spec/database.sqlite3
4
+
5
+ mysql:
6
+ adapter: mysql
7
+ database: memcache_client_activerecord_test
8
+ username: root
9
+ password:
10
+ socket: /var/lib/mysql/mysql.sock
11
+ encoding: utf8
12
+
13
+ postgresql:
14
+ adapter: postgresql
15
+ database: memcache_client_activerecord_test
16
+ username: memcache_client_activerecord_test
17
+ password:
18
+ encoding: unicode
@@ -0,0 +1,550 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ require 'erb'
4
+ require 'yaml'
5
+ require 'rubygems'
6
+ gem 'memcache-client'; require 'memcache'
7
+ gem 'activerecord'; require 'active_record'
8
+
9
+
10
+ ### start memcached
11
+
12
+ MEMCACHED_HOST = 'localhost'
13
+ MEMCACHED_PORT = '31121'
14
+ MEMCACHED_SERVER = "#{MEMCACHED_HOST}:#{MEMCACHED_PORT}"
15
+
16
+ if MEMCACHED_HOST == 'localhost'
17
+ memcached_pid = fork do
18
+ exec 'memcached', '-p', MEMCACHED_PORT
19
+ end
20
+
21
+ END {
22
+ Process.kill(:SIGINT, memcached_pid)
23
+ }
24
+
25
+ sleep 1
26
+ end
27
+
28
+
29
+ ### initializing
30
+
31
+ ROOT_DIR = File.dirname(__FILE__) + '/../..'
32
+ DATABASE_YAML = ROOT_DIR + '/spec/database.yml'
33
+ MIGRATION = ROOT_DIR + '/generators/cache_model/templates/migration.rb'
34
+ TABLE_NAME = 'caches'
35
+
36
+ ActiveRecord::Base.configurations = YAML.load_file(DATABASE_YAML)
37
+ ActiveRecord::Base.logger = Logger.new($stdout) if ENV['LOG']
38
+
39
+ migration_name = 'CreateCaches'
40
+ table_name = TABLE_NAME
41
+ eval(ERB.new(File.read(MIGRATION)).result(binding))
42
+
43
+ def new_cache_class(adapter)
44
+ cache_class = Class.new(ActiveRecord::Base)
45
+ Object.const_set('Cache' + rand.to_s[2..-1], cache_class)
46
+ cache_class.set_table_name(TABLE_NAME)
47
+ cache_class.establish_connection(adapter)
48
+ cache_class
49
+ end
50
+
51
+ def new_dbcache(adapter, options)
52
+ MemCache::ActiveRecord.new(new_cache_class(adapter), options)
53
+ end
54
+
55
+ class Caches
56
+ def initialize(memcache, dbcache)
57
+ @memcache = memcache
58
+ @dbcache = dbcache
59
+ end
60
+
61
+ def same(*args, &block)
62
+ convert = args.first.is_a?(Symbol) ? lambda {|m| m } : args.shift
63
+
64
+ begin
65
+ memcache_result = @memcache.send(*args, &block)
66
+ rescue Exception => memcache_error
67
+ end
68
+
69
+ if memcache_error
70
+ begin
71
+ dbcache_result = @dbcache.send(*args, &block)
72
+ rescue Exception => dbcache_error
73
+ end
74
+
75
+ dbcache_error.class.should == memcache_error.class
76
+ dbcache_error.message.should == memcache_error.message
77
+
78
+ else
79
+ dbcache_result = @dbcache.send(*args, &block)
80
+ dbcache_result.should == convert.call(memcache_result)
81
+ end
82
+
83
+ dbcache_result
84
+ end
85
+
86
+ private
87
+ def method_missing(name, *args)
88
+ if @dbcache.respond_to?(name)
89
+ @dbcache.send(name, *args)
90
+ @memcache.send(name, *args)
91
+ else
92
+ super
93
+ end
94
+ end
95
+ end
96
+
97
+
98
+ ### specs
99
+
100
+ mem_flusher = MemCache.new(MEMCACHED_SERVER)
101
+ mem_flusher.flush_all
102
+
103
+ ActiveRecord::Base.configurations.each_key do |adapter|
104
+ ActiveRecord::Base.establish_connection(adapter)
105
+ CreateCaches.down rescue nil
106
+ CreateCaches.up
107
+ ActiveRecord::Base.clear_all_connections!
108
+ ActiveRecord::Base.remove_connection
109
+
110
+ cache_class = new_cache_class(adapter)
111
+
112
+ db_flusher = MemCache::ActiveRecord.new(cache_class)
113
+ db_flusher.flush_all
114
+
115
+ flushers = Caches.new(mem_flusher, db_flusher)
116
+
117
+ describe "#{adapter}:" do
118
+ after do
119
+ flushers.flush_all
120
+ end
121
+
122
+ [
123
+ {},
124
+ { :namespace => 'ns' },
125
+ { :no_reply => true },
126
+ ].each do |options|
127
+ memcache = MemCache.new(MEMCACHED_SERVER, options)
128
+ dbcache = MemCache::ActiveRecord.new(cache_class, options)
129
+ caches = Caches.new(memcache, dbcache)
130
+
131
+ describe MemCache::ActiveRecord, " when options are #{options.inspect}" do
132
+ if options.empty?
133
+ it 'should be case-sensitive' do
134
+ caches.same(:set, 'foo', 1)
135
+ caches.same(:set, 'FOO', 2)
136
+
137
+ caches.same(:get, 'foo').should == 1
138
+ caches.same(:get, 'FOO').should == 2
139
+ end
140
+
141
+ unless ENV['LOG']
142
+ it 'should behave like MemCache with over 64KB value' do
143
+ value = 'a' * (2 ** 16 + 1)
144
+ caches.same(:set, 'foo', value, 0, true)
145
+ caches.same(:get, 'foo', true).should == value
146
+ end
147
+
148
+ it 'should behave like MemCache with over 1MB value' do
149
+ value = 'a' * (2 ** 20 + 1)
150
+ caches.same(:set, 'foo', value, 0, true)
151
+ caches.same(:get, 'foo', true).should be_nil
152
+ end
153
+ end
154
+
155
+ unless ENV['WITHOUT_SLEEP']
156
+ it 'should be able to collect garbage' do
157
+ dbcache.set('foo', 1)
158
+ dbcache.set('bar', 1, 1)
159
+ dbcache.set('baz', 1, 2)
160
+
161
+ cache_class.count.should == 3
162
+
163
+ sleep 1
164
+ dbcache.garbage_collection!
165
+
166
+ cache_class.count.should == 2
167
+ end
168
+ end
169
+ end
170
+
171
+ describe '#get' do
172
+ it 'should behave like MemCache#get' do
173
+ caches.same(:get, 'foo')
174
+ end
175
+
176
+ if options.empty?
177
+ unless ENV['WITHOUT_SLEEP']
178
+ it 'should behave like MemCache#get with expiry' do
179
+ 3.times do |expiry|
180
+ if expiry > 0
181
+ caches.same(:delete, 'foo')
182
+ end
183
+
184
+ caches.same(:set, 'foo', 1, expiry)
185
+
186
+ 3.times do |i|
187
+ sleep 1 if i > 0
188
+ caches.same(:get, 'foo')
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+
196
+ describe '#fetch' do
197
+ it 'should behave like MemCache#fetch' do
198
+ caches.same(:fetch, 'foo') { 1 }.should == 1
199
+ caches.same(:fetch, 'foo') { 2 }.should == 1
200
+ caches.same(:fetch, 'foo') .should == 1
201
+ end
202
+ end
203
+
204
+ describe '#get_multi' do
205
+ it 'should behave like MemCache#get_multi' do
206
+ caches.same(:get_multi, 'foo', 'bar', 'baz')
207
+
208
+ caches.same(:set, 'foo', 1)
209
+ caches.same(:set, 'bar', nil)
210
+
211
+ caches.same(:get_multi, 'foo', 'bar', 'baz')
212
+ end
213
+ end
214
+
215
+ describe '#set' do
216
+ it 'should behave like MemCache#set' do
217
+ caches.same(:set, 'foo', 1)
218
+ caches.same(:get, 'foo').should == 1
219
+
220
+ caches.same(:set, 'foo', 2)
221
+ caches.same(:get, 'foo').should == 2
222
+ end
223
+
224
+ it 'should work in multithread' do
225
+ thread = Thread.new do
226
+ dbcache2 = new_dbcache(adapter, options)
227
+ def dbcache2.insert(*args)
228
+ sleep 0.2
229
+ super
230
+ end
231
+ dbcache2.set('foo', 2)
232
+ end
233
+
234
+ sleep 0.1
235
+ dbcache.set('foo', 1)
236
+
237
+ thread.join
238
+
239
+ dbcache.get('foo').should == 1
240
+ end
241
+
242
+ if options.empty?
243
+ it 'should behave like MemCache#set with negative expiry' do
244
+ caches.same(:set, 'foo', 1, -1)
245
+ caches.same(:get, 'foo').should be_nil
246
+ end
247
+ end
248
+ end
249
+
250
+ describe '#cas' do
251
+ it 'should behave like MemCache#cas' do
252
+ caches.same(:cas, 'foo') {|v| v + 1 }
253
+ caches.same(:get, 'foo').should be_nil
254
+
255
+ caches.same(:set, 'foo', 1)
256
+ caches.same(:cas, 'foo') {|v| v + 1 }
257
+ caches.same(:get, 'foo').should == 2
258
+ end
259
+
260
+ it 'should behave like MemCache#cas without block' do
261
+ caches.same(:cas, 'foo')
262
+ end
263
+
264
+ it 'should work in multithread' do
265
+ dbcache.set('foo', 0)
266
+
267
+ thread = Thread.new do
268
+ dbcache2 = new_dbcache(adapter, options)
269
+ dbcache2.cas('foo') {|v| sleep 0.2; v + 1 }.should(
270
+ options[:no_reply] ? be_nil : eql(MemCache::ActiveRecord::EXISTS))
271
+ end
272
+
273
+ sleep 0.1
274
+ dbcache.set('foo', 2)
275
+
276
+ thread.join
277
+
278
+ dbcache.get('foo').should == 2
279
+ end
280
+ end
281
+
282
+ describe '#add' do
283
+ it 'should behave like MemCache#add' do
284
+ caches.same(:add, 'foo', 1)
285
+ caches.same(:get, 'foo').should == 1
286
+
287
+ caches.same(:add, 'foo', 2)
288
+ caches.same(:get, 'foo').should == 1
289
+ end
290
+ end
291
+
292
+ describe '#replace' do
293
+ it 'should behave like MemCache#replace' do
294
+ caches.same(:replace, 'foo', 1)
295
+ caches.same(:get, 'foo').should be_nil
296
+
297
+ caches.same(:set, 'foo', 1)
298
+ caches.same(:replace, 'foo', 2)
299
+ caches.same(:get, 'foo').should == 2
300
+ end
301
+
302
+ it 'should work in multithread' do
303
+ dbcache.set('foo', 0)
304
+
305
+ thread = Thread.new do
306
+ dbcache2 = new_dbcache(adapter, options)
307
+ def dbcache2.update(*args)
308
+ sleep 0.2
309
+ super
310
+ end
311
+ dbcache2.replace('foo', 2)
312
+ end
313
+
314
+ sleep 0.1
315
+ dbcache.delete('foo')
316
+
317
+ thread.join
318
+
319
+ dbcache.get('foo').should be_nil
320
+ end
321
+
322
+ if options.empty?
323
+ unless ENV['WITHOUT_SLEEP']
324
+ it 'should behave like MemCache#replace with expiry' do
325
+ caches.same(:set, 'foo', 1, 1)
326
+ sleep 2
327
+ caches.same(:replace, 'foo', 2)
328
+ caches.same(:get, 'foo').should be_nil
329
+ end
330
+ end
331
+ end
332
+ end
333
+
334
+ describe '#append' do
335
+ it 'should behave like MemCache#append' do
336
+ caches.same(:append, 'foo', 1)
337
+ caches.same(:get, 'foo', true).should be_nil
338
+
339
+ caches.same(:set, 'foo', 0, 0, true)
340
+ caches.same(:append, 'foo', 1)
341
+ caches.same(:get, 'foo', true).should == '01'
342
+ end
343
+ end
344
+
345
+ describe '#prepend' do
346
+ it 'should behave like MemCache#prepend' do
347
+ caches.same(:prepend, 'foo', 1)
348
+ caches.same(:get, 'foo', true).should be_nil
349
+
350
+ caches.same(:set, 'foo', 0, 0, true)
351
+ caches.same(:prepend, 'foo', 1)
352
+ caches.same(:get, 'foo', true).should == '10'
353
+ end
354
+ end
355
+
356
+ describe '#incr' do
357
+ it 'should behave like MemCache#incr' do
358
+ caches.same(:incr, 'foo').should be_nil
359
+ caches.same(:get, 'foo', true).should be_nil
360
+
361
+ caches.same(:set, 'foo', 1, 0, true)
362
+ caches.same(:incr, 'foo').should(options[:no_reply] ? be_nil : eql(2))
363
+ caches.same(:get, 'foo', true).should == '2'
364
+
365
+ caches.same(:incr, 'foo', 0).should(options[:no_reply] ? be_nil : eql(2))
366
+ caches.same(:get, 'foo', true).should == '2'
367
+
368
+ caches.same(:incr, 'foo', 2).should(options[:no_reply] ? be_nil : eql(4))
369
+ caches.same(:get, 'foo', true).should == '4'
370
+ end
371
+
372
+ it 'should work in multithread' do
373
+ dbcache.set('foo', 1, 0, true)
374
+
375
+ thread = Thread.new do
376
+ dbcache2 = new_dbcache(adapter, options)
377
+ 5.times { dbcache2.incr('foo') }
378
+ end
379
+
380
+ 5.times { dbcache.incr('foo') }
381
+
382
+ thread.join
383
+
384
+ dbcache.get('foo', true).should == '11'
385
+ end
386
+
387
+ if options.empty?
388
+ it 'should behave like MemCache#incr with string amount' do
389
+ caches.same(:set, 'foo', ' 1 ', 0, true)
390
+ caches.same(:incr, 'foo', '1')
391
+ caches.same(lambda {|m| m.sub(/ \z/, '') }, :get, 'foo', true)
392
+
393
+ caches.same(:incr, 'foo', '1 ')
394
+ caches.same(lambda {|m| m.sub(/ \z/, '') }, :get, 'foo', true)
395
+
396
+ caches.same(:incr, 'foo', ' 1')
397
+ caches.same(lambda {|m| m.sub(/ \z/, '') }, :get, 'foo', true)
398
+
399
+ caches.same(:incr, 'foo', ' 1 ')
400
+ caches.same(lambda {|m| m.sub(/ \z/, '') }, :get, 'foo', true)
401
+ end
402
+
403
+ it 'should behave like MemCache#incr with non-raw value' do
404
+ caches.same(:set, 'foo', 1)
405
+ caches.same(:incr, 'foo')
406
+ end
407
+
408
+ it 'should behave like MemCache#incr with non-numeric value' do
409
+ [1.5, -1, 'qux'].each do |value|
410
+ caches.same(:set, 'foo', value, 0, true)
411
+ caches.same(:incr, 'foo')
412
+ end
413
+ end
414
+
415
+ it 'should behave like MemCache#incr with invalid numeric delta argument' do
416
+ caches.same(:set, 'foo', 1, 0, true)
417
+ [1.5, -1, 'qux'].each do |amount|
418
+ caches.same(:incr, 'foo', amount)
419
+ end
420
+ end
421
+
422
+ unless ENV['WITHOUT_SLEEP']
423
+ it 'should behave like MemCache#incr with expiry' do
424
+ caches.same(:set, 'foo', 1, 1, true)
425
+ caches.same(:incr, 'foo')
426
+ sleep 2
427
+ caches.same(:get, 'foo', true)
428
+
429
+ caches.same(:set, 'foo', 1, 1, true)
430
+ sleep 2
431
+ caches.same(:incr, 'foo')
432
+ caches.same(:get, 'foo', true)
433
+ end
434
+ end
435
+ end
436
+ end
437
+
438
+ describe '#decr' do
439
+ it 'should behave like MemCache#decr' do
440
+ caches.same(:decr, 'foo')
441
+ caches.same(:get, 'foo', true)
442
+
443
+ caches.same(:set, 'foo', 9, 0, true)
444
+ caches.same(:decr, 'foo')
445
+ caches.same(:get, 'foo', true)
446
+
447
+ caches.same(:decr, 'foo', 0)
448
+ caches.same(:get, 'foo', true)
449
+
450
+ caches.same(:decr, 'foo', 2)
451
+ caches.same(:get, 'foo', true)
452
+ end
453
+
454
+ if options.empty?
455
+ it 'should behave like MemCache#decr (1 - 2 => 0)' do
456
+ caches.same(:set, 'foo', 1, 0, true)
457
+ caches.same(:decr, 'foo', 2)
458
+ caches.same(:get, 'foo', true)
459
+ end
460
+
461
+ it 'should behave like MemCache#decr with decrement digit' do
462
+ caches.same(:set, 'foo', 10, 0, true)
463
+ caches.same(:decr, 'foo')
464
+ caches.same(lambda {|m| m.sub(/ \z/, '') }, :get, 'foo', true)
465
+ end
466
+
467
+ it 'should behave like MemCache#decr with non-raw value' do
468
+ caches.same(:set, 'foo', 1)
469
+ caches.same(:decr, 'foo')
470
+ end
471
+
472
+ it 'should behave like MemCache#decr with non-numeric value' do
473
+ [1.5, -1, 'qux'].each do |value|
474
+ caches.same(:set, 'foo', value, 0, true)
475
+ caches.same(:decr, 'foo')
476
+ end
477
+ end
478
+
479
+ it 'should behave like MemCache#decr with invalid numeric delta argument' do
480
+ caches.same(:set, 'foo', 1, 0, true)
481
+
482
+ [1.5, -1, 'qux'].each do |amount|
483
+ caches.same(:decr, 'foo', amount)
484
+ end
485
+ end
486
+ end
487
+ end
488
+
489
+ describe '#delete' do
490
+ it 'should behave like MemCache#delete' do
491
+ caches.same(:set, 'foo', 1)
492
+ caches.same(:get, 'foo')
493
+
494
+ caches.same(:delete, 'foo')
495
+ caches.same(:get, 'foo')
496
+ end
497
+ end
498
+
499
+ if options.empty?
500
+ describe '#flush_all' do
501
+ it 'should behave like MemCache#delete' do
502
+ caches.same(:set, 'foo', 1)
503
+ caches.same(:get, 'foo')
504
+
505
+ # don't use same, because MemCache#flush_all returns @servers.
506
+ caches.flush_all
507
+
508
+ caches.same(:get, 'foo')
509
+ end
510
+ end
511
+ end
512
+ end
513
+ end
514
+
515
+ [
516
+ {},
517
+ { :namespace => 'n' },
518
+ ].each do |options|
519
+ options.update(:autofix_keys => true)
520
+
521
+ memcache = MemCache.new(MEMCACHED_SERVER, options)
522
+ dbcache = MemCache::ActiveRecord.new(cache_class, options)
523
+ caches = Caches.new(memcache, dbcache)
524
+
525
+ describe MemCache::ActiveRecord, " when options are #{options.inspect}" do
526
+ it 'should behave like MemCache' do
527
+ (248..251).each do |length|
528
+ caches.same(:set, 'a' * length, length)
529
+ caches.same(:get, 'a' * length)
530
+ end
531
+ end
532
+ end
533
+ end
534
+
535
+ [
536
+ { :readonly => true },
537
+ ].each do |options|
538
+ memcache = MemCache.new(MEMCACHED_SERVER, options)
539
+ dbcache = MemCache::ActiveRecord.new(cache_class, options)
540
+ caches = Caches.new(memcache, dbcache)
541
+
542
+ describe MemCache::ActiveRecord, " when options are #{options.inspect}" do
543
+ it 'should behave like MemCache' do
544
+ caches.same(:set, 'foo', 1)
545
+ end
546
+ end
547
+ end
548
+
549
+ end
550
+ end