memcache-client-activerecord 0.1.0

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.
@@ -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