KellyMahan-memcachedb-client 1.0.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.
@@ -0,0 +1,102 @@
1
+ ##
2
+ # A utility wrapper around the MemCacheDb client to simplify cache access. All
3
+ # methods silently ignore MemCacheDb errors.
4
+
5
+ module CacheDb
6
+
7
+ ##
8
+ # Try to return a logger object that does not rely
9
+ # on ActiveRecord for logging.
10
+ def self.logger
11
+ @logger ||= if defined? Rails.logger # Rails 2.1 +
12
+ Rails.logger
13
+ elsif defined? RAILS_DEFAULT_LOGGER # Rails 1.2.2 +
14
+ RAILS_DEFAULT_LOGGER
15
+ else
16
+ ActiveRecord::Base.logger # ... very old Rails.
17
+ end
18
+ end
19
+ ##
20
+ # Returns the object at +key+ from the cache if successful, or nil if either
21
+ # the object is not in the cache or if there was an error attermpting to
22
+ # access the cache.
23
+ #
24
+ # If there is a cache miss and a block is given the result of the block will
25
+ # be stored in the cache with optional +expiry+, using the +add+ method rather
26
+ # than +set+.
27
+
28
+ def self.get(key, expiry = 0)
29
+ start_time = Time.now
30
+ value = CACHE.get key
31
+ elapsed = Time.now - start_time
32
+ logger.debug('MemCacheDb Get (%0.6f) %s' % [elapsed, key])
33
+ if value.nil? and block_given? then
34
+ value = yield
35
+ add key, value, expiry
36
+ end
37
+ value
38
+ rescue MemCacheDb::MemCacheDbError => err
39
+ logger.debug "MemCacheDb Error: #{err.message}"
40
+ if block_given? then
41
+ value = yield
42
+ put key, value, expiry
43
+ end
44
+ value
45
+ end
46
+
47
+ ##
48
+ # Sets +value+ in the cache at +key+, with an optional +expiry+ time in
49
+ # seconds.
50
+
51
+ def self.put(key, value, expiry = 0)
52
+ start_time = Time.now
53
+ CACHE.set key, value, expiry
54
+ elapsed = Time.now - start_time
55
+ logger.debug('MemCacheDb Set (%0.6f) %s' % [elapsed, key])
56
+ value
57
+ rescue MemCacheDb::MemCacheDbError => err
58
+ ActiveRecord::Base.logger.debug "MemCacheDb Error: #{err.message}"
59
+ nil
60
+ end
61
+
62
+ ##
63
+ # Sets +value+ in the cache at +key+, with an optional +expiry+ time in
64
+ # seconds. If +key+ already exists in cache, returns nil.
65
+
66
+ def self.add(key, value, expiry = 0)
67
+ start_time = Time.now
68
+ response = CACHE.add key, value, expiry
69
+ elapsed = Time.now - start_time
70
+ logger.debug('MemCacheDb Add (%0.6f) %s' % [elapsed, key])
71
+ (response == "STORED\r\n") ? value : nil
72
+ rescue MemCacheDb::MemCacheDbError => err
73
+ ActiveRecord::Base.logger.debug "MemCacheDb Error: #{err.message}"
74
+ nil
75
+ end
76
+
77
+ ##
78
+ # Deletes +key+ from the cache in +delay+ seconds.
79
+
80
+ def self.delete(key, delay = nil)
81
+ start_time = Time.now
82
+ CACHE.delete key, delay
83
+ elapsed = Time.now - start_time
84
+ logger.debug('MemCacheDb Delete (%0.6f) %s' %
85
+ [elapsed, key])
86
+ nil
87
+ rescue MemCacheDb::MemCacheDbError => err
88
+ logger.debug "MemCacheDb Error: #{err.message}"
89
+ nil
90
+ end
91
+
92
+ ##
93
+ # Resets all connections to MemCacheDb servers.
94
+
95
+ def self.reset
96
+ CACHE.reset
97
+ logger.debug 'MemCacheDb Connections Reset'
98
+ nil
99
+ end
100
+
101
+ end
102
+
@@ -0,0 +1,950 @@
1
+ # encoding: utf-8
2
+ require 'logger'
3
+ require 'stringio'
4
+ require 'test/unit'
5
+ require 'rubygems'
6
+ begin
7
+ gem 'flexmock'
8
+ require 'flexmock/test_unit'
9
+ rescue LoadError => e
10
+ puts "Some tests require flexmock, please run `gem install flexmock`"
11
+ end
12
+
13
+ $TESTING = true
14
+
15
+ require File.dirname(__FILE__) + '/../lib/memcache'
16
+
17
+ class MemCacheDb
18
+
19
+ attr_writer :namespace
20
+
21
+ end
22
+
23
+ class FakeSocketDb
24
+
25
+ attr_reader :written, :data
26
+
27
+ def initialize
28
+ @written = StringIO.new
29
+ @data = StringIO.new
30
+ end
31
+
32
+ def write(data)
33
+ @written.write data
34
+ end
35
+
36
+ def gets
37
+ @data.gets
38
+ end
39
+
40
+ def read(arg)
41
+ @data.read arg
42
+ end
43
+
44
+ end
45
+
46
+ class Test::Unit::TestCase
47
+ def requirement(bool, msg)
48
+ if bool
49
+ yield
50
+ else
51
+ puts msg
52
+ assert true
53
+ end
54
+ end
55
+
56
+ def memcached_running?
57
+ TCPSocket.new('localhost', 11211) rescue false
58
+ end
59
+
60
+ def xprofile(name, &block)
61
+ a = Time.now
62
+ block.call
63
+ Time.now - a
64
+ end
65
+
66
+ def profile(name, &block)
67
+ require 'ruby-prof'
68
+ a = Time.now
69
+ result = RubyProf.profile(&block)
70
+ time = Time.now - a
71
+ printer = RubyProf::GraphHtmlPrinter.new(result)
72
+ File.open("#{name}.html", 'w') do |f|
73
+ printer.print(f, :min_percent=>1)
74
+ end
75
+ time
76
+ end
77
+
78
+ end
79
+
80
+ class FakeServerDb
81
+
82
+ attr_reader :host, :port, :socket, :weight, :multithread, :status
83
+
84
+ def initialize(socket = nil)
85
+ @closed = false
86
+ @host = 'example.com'
87
+ @port = 11211
88
+ @socket = socket || FakeSocketDb.new
89
+ @weight = 1
90
+ @multithread = false
91
+ @status = "CONNECTED"
92
+ end
93
+
94
+ def close
95
+ # begin
96
+ # raise "Already closed"
97
+ # rescue => e
98
+ # puts e.backtrace.join("\n")
99
+ # end
100
+ @closed = true
101
+ @socket = nil
102
+ @status = "NOT CONNECTED"
103
+ end
104
+
105
+ def alive?
106
+ # puts "I'm #{@closed ? 'dead' : 'alive'}"
107
+ !@closed
108
+ end
109
+
110
+ end
111
+
112
+ class TestMemCacheDb < Test::Unit::TestCase
113
+
114
+ def setup
115
+ @cache = MemCacheDb.new 'localhost:1', :namespace => 'my_namespace'
116
+ end
117
+
118
+ def test_performance
119
+ requirement(memcached_running?, 'A real memcached server must be running for performance testing') do
120
+ host = Socket.gethostname
121
+
122
+ cache = MemCacheDb.new(['localhost:21201',"#{host}:21201"])
123
+ cache.add('a', 1, 120)
124
+ with = xprofile 'get' do
125
+ 1000.times do
126
+ cache.get('a')
127
+ end
128
+ end
129
+ puts ''
130
+ puts "1000 gets with socket timeout: #{with} sec"
131
+
132
+ cache = MemCacheDb.new(['localhost:21201',"#{host}:21201"], :timeout => nil)
133
+ cache.add('a', 1, 120)
134
+ without = xprofile 'get' do
135
+ 1000.times do
136
+ cache.get('a')
137
+ end
138
+ end
139
+ puts "1000 gets without socket timeout: #{without} sec"
140
+
141
+ assert without < with
142
+ end
143
+ end
144
+
145
+ def test_consistent_hashing
146
+ requirement(self.respond_to?(:flexmock), 'Flexmock is required to run this test') do
147
+
148
+ flexmock(MemCacheDb::Server).new_instances.should_receive(:alive?).and_return(true)
149
+
150
+ # Setup a continuum of two servers
151
+ @cache.servers = ['mike1', 'mike2', 'mike3']
152
+
153
+ keys = []
154
+ 1000.times do |idx|
155
+ keys << idx.to_s
156
+ end
157
+
158
+ before_continuum = keys.map {|key| @cache.get_server_for_key(key) }
159
+
160
+ @cache.servers = ['mike1', 'mike2', 'mike3', 'mike4']
161
+
162
+ after_continuum = keys.map {|key| @cache.get_server_for_key(key) }
163
+
164
+ same_count = before_continuum.zip(after_continuum).find_all {|a| a[0].host == a[1].host }.size
165
+
166
+ # With continuum, we should see about 75% of the keys map to the same server
167
+ # With modulo, we would see about 25%.
168
+ assert same_count > 700
169
+ end
170
+ end
171
+
172
+ def test_get_multi_with_server_failure
173
+ @cache = MemCacheDb.new 'localhost:1', :namespace => 'my_namespace', :logger => nil #Logger.new(STDOUT)
174
+ s1 = FakeServerDb.new
175
+ s2 = FakeServerDb.new
176
+
177
+ # Write two messages to the socket to test failover
178
+ s1.socket.data.write "VALUE my_namespace:a 0 14\r\n\004\b\"\0170123456789\r\nEND\r\n"
179
+ s1.socket.data.rewind
180
+ s2.socket.data.write "bogus response\r\nbogus response\r\n"
181
+ s2.socket.data.rewind
182
+
183
+ @cache.servers = [s1, s2]
184
+
185
+ assert s1.alive?
186
+ assert s2.alive?
187
+ # a maps to s1, the rest map to s2
188
+ value = @cache.get_multi(['foo', 'bar', 'a', 'b', 'c'])
189
+ assert_equal({'a'=>'0123456789'}, value)
190
+ assert s1.alive?
191
+ assert !s2.alive?
192
+ end
193
+
194
+ def test_cache_get_with_failover
195
+ @cache = MemCacheDb.new 'localhost:1', :namespace => 'my_namespace', :logger => nil#Logger.new(STDOUT)
196
+ s1 = FakeServerDb.new
197
+ s2 = FakeServerDb.new
198
+
199
+ # Write two messages to the socket to test failover
200
+ s1.socket.data.write "VALUE foo 0 14\r\n\004\b\"\0170123456789\r\n"
201
+ s1.socket.data.rewind
202
+ s2.socket.data.write "bogus response\r\nbogus response\r\n"
203
+ s2.socket.data.rewind
204
+
205
+ @cache.instance_variable_set(:@failover, true)
206
+ @cache.servers = [s1, s2]
207
+
208
+ assert s1.alive?
209
+ assert s2.alive?
210
+ @cache.get('foo')
211
+ assert s1.alive?
212
+ assert !s2.alive?
213
+ end
214
+
215
+ def test_cache_get_without_failover
216
+ s1 = FakeServerDb.new
217
+ s2 = FakeServerDb.new
218
+
219
+ s1.socket.data.write "VALUE foo 0 14\r\n\004\b\"\0170123456789\r\n"
220
+ s1.socket.data.rewind
221
+ s2.socket.data.write "bogus response\r\nbogus response\r\n"
222
+ s2.socket.data.rewind
223
+
224
+ @cache.instance_variable_set(:@failover, false)
225
+ @cache.servers = [s1, s2]
226
+
227
+ assert s1.alive?
228
+ assert s2.alive?
229
+ e = assert_raise MemCacheDb::MemCacheDbError do
230
+ @cache.get('foo')
231
+ end
232
+ assert s1.alive?
233
+ assert !s2.alive?
234
+
235
+ assert_equal "No servers available", e.message
236
+ end
237
+
238
+ def test_cache_get
239
+ server = util_setup_fake_server
240
+
241
+ assert_equal "\004\b\"\0170123456789",
242
+ @cache.cache_get(server, 'my_namespace:key')
243
+
244
+ assert_equal "get my_namespace:key\r\n",
245
+ server.socket.written.string
246
+ end
247
+
248
+ def test_cache_get_EOF
249
+ server = util_setup_fake_server
250
+ server.socket.data.string = ''
251
+
252
+ e = assert_raise IndexError do
253
+ @cache.cache_get server, 'my_namespace:key'
254
+ end
255
+
256
+ assert_equal "No connection to server (NOT CONNECTED)", e.message
257
+ end
258
+
259
+ def test_cache_get_bad_state
260
+ server = FakeServerDb.new
261
+
262
+ # Write two messages to the socket to test failover
263
+ server.socket.data.write "bogus response\r\nbogus response\r\n"
264
+ server.socket.data.rewind
265
+
266
+ @cache.servers = []
267
+ @cache.servers << server
268
+
269
+ e = assert_raise IndexError do
270
+ @cache.cache_get(server, 'my_namespace:key')
271
+ end
272
+
273
+ assert_match /#{Regexp.quote 'No connection to server (NOT CONNECTED)'}/, e.message
274
+
275
+ assert !server.alive?
276
+ end
277
+
278
+ def test_cache_get_miss
279
+ socket = FakeSocketDb.new
280
+ socket.data.write "END\r\n"
281
+ socket.data.rewind
282
+ server = FakeServerDb.new socket
283
+
284
+ assert_equal nil, @cache.cache_get(server, 'my_namespace:key')
285
+
286
+ assert_equal "get my_namespace:key\r\n",
287
+ socket.written.string
288
+ end
289
+
290
+ def test_cache_get_multi
291
+ server = util_setup_fake_server
292
+ server.socket.data.write "VALUE foo 0 7\r\n"
293
+ server.socket.data.write "\004\b\"\bfoo\r\n"
294
+ server.socket.data.write "VALUE bar 0 7\r\n"
295
+ server.socket.data.write "\004\b\"\bbar\r\n"
296
+ server.socket.data.write "END\r\n"
297
+ server.socket.data.rewind
298
+
299
+ result = @cache.cache_get_multi server, 'foo bar baz'
300
+
301
+ assert_equal 2, result.length
302
+ assert_equal "\004\b\"\bfoo", result['foo']
303
+ assert_equal "\004\b\"\bbar", result['bar']
304
+ end
305
+
306
+ def test_cache_get_multi_EOF
307
+ server = util_setup_fake_server
308
+ server.socket.data.string = ''
309
+
310
+ e = assert_raise IndexError do
311
+ @cache.cache_get_multi server, 'my_namespace:key'
312
+ end
313
+
314
+ assert_equal "No connection to server (NOT CONNECTED)", e.message
315
+ end
316
+
317
+ def test_cache_get_multi_bad_state
318
+ server = FakeServerDb.new
319
+
320
+ # Write two messages to the socket to test failover
321
+ server.socket.data.write "bogus response\r\nbogus response\r\n"
322
+ server.socket.data.rewind
323
+
324
+ @cache.servers = []
325
+ @cache.servers << server
326
+
327
+ e = assert_raise IndexError do
328
+ @cache.cache_get_multi server, 'my_namespace:key'
329
+ end
330
+
331
+ assert_match /#{Regexp.quote 'No connection to server (NOT CONNECTED)'}/, e.message
332
+
333
+ assert !server.alive?
334
+ end
335
+
336
+ def test_initialize
337
+ cache = MemCacheDb.new :namespace => 'my_namespace', :readonly => true
338
+
339
+ assert_equal 'my_namespace', cache.namespace
340
+ assert_equal true, cache.readonly?
341
+ assert_equal true, cache.servers.empty?
342
+ end
343
+
344
+ def test_initialize_compatible
345
+ cache = MemCacheDb.new ['localhost:21201', 'localhost:11212'],
346
+ :namespace => 'my_namespace', :readonly => true
347
+
348
+ assert_equal 'my_namespace', cache.namespace
349
+ assert_equal true, cache.readonly?
350
+ assert_equal false, cache.servers.empty?
351
+ end
352
+
353
+ def test_initialize_compatible_no_hash
354
+ cache = MemCacheDb.new ['localhost:21201', 'localhost:11212']
355
+
356
+ assert_equal nil, cache.namespace
357
+ assert_equal false, cache.readonly?
358
+ assert_equal false, cache.servers.empty?
359
+ end
360
+
361
+ def test_initialize_compatible_one_server
362
+ cache = MemCacheDb.new 'localhost:21201'
363
+
364
+ assert_equal nil, cache.namespace
365
+ assert_equal false, cache.readonly?
366
+ assert_equal false, cache.servers.empty?
367
+ end
368
+
369
+ def test_initialize_compatible_bad_arg
370
+ e = assert_raise ArgumentError do
371
+ cache = MemCacheDb.new Object.new
372
+ end
373
+
374
+ assert_equal 'first argument must be Array, Hash or String', e.message
375
+ end
376
+
377
+ def test_initialize_multiple_servers
378
+ cache = MemCacheDb.new %w[localhost:21201 localhost:11212],
379
+ :namespace => 'my_namespace', :readonly => true
380
+
381
+ assert_equal 'my_namespace', cache.namespace
382
+ assert_equal true, cache.readonly?
383
+ assert_equal false, cache.servers.empty?
384
+ assert !cache.instance_variable_get(:@continuum).empty?
385
+ end
386
+
387
+ def test_initialize_too_many_args
388
+ assert_raises ArgumentError do
389
+ MemCacheDb.new 1, 2, 3
390
+ end
391
+ end
392
+
393
+ def test_decr
394
+ server = FakeServerDb.new
395
+ server.socket.data.write "5\r\n"
396
+ server.socket.data.rewind
397
+
398
+ @cache.servers = []
399
+ @cache.servers << server
400
+
401
+ value = @cache.decr 'key'
402
+
403
+ assert_equal "decr my_namespace:key 1\r\n",
404
+ @cache.servers.first.socket.written.string
405
+
406
+ assert_equal 5, value
407
+ end
408
+
409
+ def test_decr_not_found
410
+ server = FakeServerDb.new
411
+ server.socket.data.write "NOT_FOUND\r\n"
412
+ server.socket.data.rewind
413
+
414
+ @cache.servers = []
415
+ @cache.servers << server
416
+
417
+ value = @cache.decr 'key'
418
+
419
+ assert_equal "decr my_namespace:key 1\r\n",
420
+ @cache.servers.first.socket.written.string
421
+
422
+ assert_equal nil, value
423
+ end
424
+
425
+ def test_decr_space_padding
426
+ server = FakeServerDb.new
427
+ server.socket.data.write "5 \r\n"
428
+ server.socket.data.rewind
429
+
430
+ @cache.servers = []
431
+ @cache.servers << server
432
+
433
+ value = @cache.decr 'key'
434
+
435
+ assert_equal "decr my_namespace:key 1\r\n",
436
+ @cache.servers.first.socket.written.string
437
+
438
+ assert_equal 5, value
439
+ end
440
+
441
+ def test_get
442
+ util_setup_fake_server
443
+
444
+ value = @cache.get 'key'
445
+
446
+ assert_equal "get my_namespace:key\r\n",
447
+ @cache.servers.first.socket.written.string
448
+
449
+ assert_equal '0123456789', value
450
+ end
451
+
452
+ def test_get_bad_key
453
+ util_setup_fake_server
454
+ assert_raise ArgumentError do @cache.get 'k y' end
455
+
456
+ util_setup_fake_server
457
+ assert_raise ArgumentError do @cache.get 'k' * 250 end
458
+ end
459
+
460
+ def test_get_cache_get_IOError
461
+ socket = Object.new
462
+ def socket.write(arg) raise IOError, 'some io error'; end
463
+ server = FakeServerDb.new socket
464
+
465
+ @cache.servers = []
466
+ @cache.servers << server
467
+
468
+ e = assert_raise MemCacheDb::MemCacheDbError do
469
+ @cache.get 'my_namespace:key'
470
+ end
471
+
472
+ assert_equal 'some io error', e.message
473
+ end
474
+
475
+ def test_get_cache_get_SystemCallError
476
+ socket = Object.new
477
+ def socket.write(arg) raise SystemCallError, 'some syscall error'; end
478
+ server = FakeServerDb.new socket
479
+
480
+ @cache.servers = []
481
+ @cache.servers << server
482
+
483
+ e = assert_raise MemCacheDb::MemCacheDbError do
484
+ @cache.get 'my_namespace:key'
485
+ end
486
+
487
+ assert_equal 'unknown error - some syscall error', e.message
488
+ end
489
+
490
+ def test_get_no_connection
491
+ @cache.servers = 'localhost:1'
492
+ e = assert_raise MemCacheDb::MemCacheDbError do
493
+ @cache.get 'key'
494
+ end
495
+
496
+ assert_match /^No connection to server/, e.message
497
+ end
498
+
499
+ def test_get_no_servers
500
+ @cache.servers = []
501
+ e = assert_raise MemCacheDb::MemCacheDbError do
502
+ @cache.get 'key'
503
+ end
504
+
505
+ assert_equal 'No active servers', e.message
506
+ end
507
+
508
+ def test_get_multi
509
+ server = FakeServerDb.new
510
+ server.socket.data.write "VALUE my_namespace:key 0 14\r\n"
511
+ server.socket.data.write "\004\b\"\0170123456789\r\n"
512
+ server.socket.data.write "VALUE my_namespace:keyb 0 14\r\n"
513
+ server.socket.data.write "\004\b\"\0179876543210\r\n"
514
+ server.socket.data.write "END\r\n"
515
+ server.socket.data.rewind
516
+
517
+ @cache.servers = []
518
+ @cache.servers << server
519
+
520
+ values = @cache.get_multi 'key', 'keyb'
521
+
522
+ assert_equal "get my_namespace:key my_namespace:keyb\r\n",
523
+ server.socket.written.string
524
+
525
+ expected = { 'key' => '0123456789', 'keyb' => '9876543210' }
526
+
527
+ assert_equal expected.sort, values.sort
528
+ end
529
+
530
+ def test_get_raw
531
+ server = FakeServerDb.new
532
+ server.socket.data.write "VALUE my_namespace:key 0 10\r\n"
533
+ server.socket.data.write "0123456789\r\n"
534
+ server.socket.data.write "END\r\n"
535
+ server.socket.data.rewind
536
+
537
+ @cache.servers = []
538
+ @cache.servers << server
539
+
540
+
541
+ value = @cache.get 'key', true
542
+
543
+ assert_equal "get my_namespace:key\r\n",
544
+ @cache.servers.first.socket.written.string
545
+
546
+ assert_equal '0123456789', value
547
+ end
548
+
549
+ def test_get_server_for_key
550
+ server = @cache.get_server_for_key 'key'
551
+ assert_equal 'localhost', server.host
552
+ assert_equal 1, server.port
553
+ end
554
+
555
+ def test_get_server_for_key_multiple
556
+ s1 = util_setup_server @cache, 'one.example.com', ''
557
+ s2 = util_setup_server @cache, 'two.example.com', ''
558
+ @cache.servers = [s1, s2]
559
+
560
+ server = @cache.get_server_for_key 'keya'
561
+ assert_equal 'two.example.com', server.host
562
+ server = @cache.get_server_for_key 'keyb'
563
+ assert_equal 'two.example.com', server.host
564
+ server = @cache.get_server_for_key 'keyc'
565
+ assert_equal 'two.example.com', server.host
566
+ server = @cache.get_server_for_key 'keyd'
567
+ assert_equal 'one.example.com', server.host
568
+ end
569
+
570
+ def test_get_server_for_key_no_servers
571
+ @cache.servers = []
572
+
573
+ e = assert_raise MemCacheDb::MemCacheDbError do
574
+ @cache.get_server_for_key 'key'
575
+ end
576
+
577
+ assert_equal 'No servers available', e.message
578
+ end
579
+
580
+ def test_get_server_for_key_spaces
581
+ e = assert_raise ArgumentError do
582
+ @cache.get_server_for_key 'space key'
583
+ end
584
+ assert_equal 'illegal character in key "space key"', e.message
585
+ end
586
+
587
+ def test_get_server_for_key_length
588
+ @cache.get_server_for_key 'x' * 250
589
+ long_key = 'x' * 251
590
+ e = assert_raise ArgumentError do
591
+ @cache.get_server_for_key long_key
592
+ end
593
+ assert_equal "key too long #{long_key.inspect}", e.message
594
+ end
595
+
596
+ def test_incr
597
+ server = FakeServerDb.new
598
+ server.socket.data.write "5\r\n"
599
+ server.socket.data.rewind
600
+
601
+ @cache.servers = []
602
+ @cache.servers << server
603
+
604
+ value = @cache.incr 'key'
605
+
606
+ assert_equal "incr my_namespace:key 1\r\n",
607
+ @cache.servers.first.socket.written.string
608
+
609
+ assert_equal 5, value
610
+ end
611
+
612
+ def test_incr_not_found
613
+ server = FakeServerDb.new
614
+ server.socket.data.write "NOT_FOUND\r\n"
615
+ server.socket.data.rewind
616
+
617
+ @cache.servers = []
618
+ @cache.servers << server
619
+
620
+ value = @cache.incr 'key'
621
+
622
+ assert_equal "incr my_namespace:key 1\r\n",
623
+ @cache.servers.first.socket.written.string
624
+
625
+ assert_equal nil, value
626
+ end
627
+
628
+ def test_incr_space_padding
629
+ server = FakeServerDb.new
630
+ server.socket.data.write "5 \r\n"
631
+ server.socket.data.rewind
632
+
633
+ @cache.servers = []
634
+ @cache.servers << server
635
+
636
+ value = @cache.incr 'key'
637
+
638
+ assert_equal "incr my_namespace:key 1\r\n",
639
+ @cache.servers.first.socket.written.string
640
+
641
+ assert_equal 5, value
642
+ end
643
+
644
+ def test_make_cache_key
645
+ assert_equal 'my_namespace:key', @cache.make_cache_key('key')
646
+ @cache.namespace = nil
647
+ assert_equal 'key', @cache.make_cache_key('key')
648
+ end
649
+
650
+ def test_servers
651
+ server = FakeServerDb.new
652
+ @cache.servers = []
653
+ @cache.servers << server
654
+ assert_equal [server], @cache.servers
655
+ end
656
+
657
+ def test_set
658
+ server = FakeServerDb.new
659
+ server.socket.data.write "STORED\r\n"
660
+ server.socket.data.rewind
661
+ @cache.servers = []
662
+ @cache.servers << server
663
+
664
+ @cache.set 'key', 'value'
665
+
666
+ dumped = Marshal.dump('value')
667
+ expected = "set my_namespace:key 0 0 #{dumped.length}\r\n#{dumped}\r\n"
668
+ # expected = "set my_namespace:key 0 0 9\r\n\004\b\"\nvalue\r\n"
669
+ assert_equal expected, server.socket.written.string
670
+ end
671
+
672
+ def test_set_expiry
673
+ server = FakeServerDb.new
674
+ server.socket.data.write "STORED\r\n"
675
+ server.socket.data.rewind
676
+ @cache.servers = []
677
+ @cache.servers << server
678
+
679
+ @cache.set 'key', 'value', 5
680
+
681
+ dumped = Marshal.dump('value')
682
+ expected = "set my_namespace:key 0 5 #{dumped.length}\r\n#{dumped}\r\n"
683
+ assert_equal expected, server.socket.written.string
684
+ end
685
+
686
+ def test_set_raw
687
+ server = FakeServerDb.new
688
+ server.socket.data.write "STORED\r\n"
689
+ server.socket.data.rewind
690
+ @cache.servers = []
691
+ @cache.servers << server
692
+
693
+ @cache.set 'key', 'value', 0, true
694
+
695
+ expected = "set my_namespace:key 0 0 5\r\nvalue\r\n"
696
+ assert_equal expected, server.socket.written.string
697
+ end
698
+
699
+ def test_set_readonly
700
+ cache = MemCacheDb.new :readonly => true
701
+
702
+ e = assert_raise MemCacheDb::MemCacheDbError do
703
+ cache.set 'key', 'value'
704
+ end
705
+
706
+ assert_equal 'Update of readonly cache', e.message
707
+ end
708
+
709
+ def test_set_too_big
710
+ server = FakeServerDb.new
711
+
712
+ # Write two messages to the socket to test failover
713
+ server.socket.data.write "SERVER_ERROR\r\nSERVER_ERROR object too large for cache\r\n"
714
+ server.socket.data.rewind
715
+
716
+ @cache.servers = []
717
+ @cache.servers << server
718
+
719
+ e = assert_raise MemCacheDb::MemCacheDbError do
720
+ @cache.set 'key', 'v'
721
+ end
722
+
723
+ assert_match /object too large for cache/, e.message
724
+ end
725
+
726
+ def test_add
727
+ server = FakeServerDb.new
728
+ server.socket.data.write "STORED\r\n"
729
+ server.socket.data.rewind
730
+ @cache.servers = []
731
+ @cache.servers << server
732
+
733
+ @cache.add 'key', 'value'
734
+
735
+ dumped = Marshal.dump('value')
736
+
737
+ expected = "add my_namespace:key 0 0 #{dumped.length}\r\n#{dumped}\r\n"
738
+ assert_equal expected, server.socket.written.string
739
+ end
740
+
741
+ def test_add_exists
742
+ server = FakeServerDb.new
743
+ server.socket.data.write "NOT_STORED\r\n"
744
+ server.socket.data.rewind
745
+ @cache.servers = []
746
+ @cache.servers << server
747
+
748
+ @cache.add 'key', 'value'
749
+
750
+ dumped = Marshal.dump('value')
751
+ expected = "add my_namespace:key 0 0 #{dumped.length}\r\n#{dumped}\r\n"
752
+ assert_equal expected, server.socket.written.string
753
+ end
754
+
755
+ def test_add_expiry
756
+ server = FakeServerDb.new
757
+ server.socket.data.write "STORED\r\n"
758
+ server.socket.data.rewind
759
+ @cache.servers = []
760
+ @cache.servers << server
761
+
762
+ @cache.add 'key', 'value', 5
763
+
764
+ dumped = Marshal.dump('value')
765
+ expected = "add my_namespace:key 0 5 #{dumped.length}\r\n#{dumped}\r\n"
766
+ assert_equal expected, server.socket.written.string
767
+ end
768
+
769
+ def test_add_raw
770
+ server = FakeServerDb.new
771
+ server.socket.data.write "STORED\r\n"
772
+ server.socket.data.rewind
773
+ @cache.servers = []
774
+ @cache.servers << server
775
+
776
+ @cache.add 'key', 'value', 0, true
777
+
778
+ expected = "add my_namespace:key 0 0 5\r\nvalue\r\n"
779
+ assert_equal expected, server.socket.written.string
780
+ end
781
+
782
+ def test_add_raw_int
783
+ server = FakeServerDb.new
784
+ server.socket.data.write "STORED\r\n"
785
+ server.socket.data.rewind
786
+ @cache.servers = []
787
+ @cache.servers << server
788
+
789
+ @cache.add 'key', 12, 0, true
790
+
791
+ expected = "add my_namespace:key 0 0 2\r\n12\r\n"
792
+ assert_equal expected, server.socket.written.string
793
+ end
794
+
795
+ def test_add_readonly
796
+ cache = MemCacheDb.new :readonly => true
797
+
798
+ e = assert_raise MemCacheDb::MemCacheDbError do
799
+ cache.add 'key', 'value'
800
+ end
801
+
802
+ assert_equal 'Update of readonly cache', e.message
803
+ end
804
+
805
+ def test_delete
806
+ server = FakeServerDb.new
807
+ @cache.servers = []
808
+ @cache.servers << server
809
+
810
+ @cache.delete 'key'
811
+
812
+ expected = "delete my_namespace:key 0\r\n"
813
+ assert_equal expected, server.socket.written.string
814
+ end
815
+
816
+ def test_delete_with_expiry
817
+ server = FakeServerDb.new
818
+ @cache.servers = []
819
+ @cache.servers << server
820
+
821
+ @cache.delete 'key', 300
822
+
823
+ expected = "delete my_namespace:key 300\r\n"
824
+ assert_equal expected, server.socket.written.string
825
+ end
826
+
827
+ def test_flush_all
828
+ @cache.servers = []
829
+ 3.times { @cache.servers << FakeServerDb.new }
830
+
831
+ @cache.flush_all
832
+
833
+ expected = "flush_all\r\n"
834
+ @cache.servers.each do |server|
835
+ assert_equal expected, server.socket.written.string
836
+ end
837
+ end
838
+
839
+ def test_flush_all_failure
840
+ socket = FakeSocketDb.new
841
+
842
+ # Write two messages to the socket to test failover
843
+ socket.data.write "ERROR\r\nERROR\r\n"
844
+ socket.data.rewind
845
+
846
+ server = FakeServerDb.new socket
847
+
848
+ @cache.servers = []
849
+ @cache.servers << server
850
+
851
+ assert_raise MemCacheDb::MemCacheDbError do
852
+ @cache.flush_all
853
+ end
854
+
855
+ assert_match /flush_all\r\n/, socket.written.string
856
+ end
857
+
858
+ def test_stats
859
+ socket = FakeSocketDb.new
860
+ socket.data.write "STAT pid 20188\r\nSTAT total_items 32\r\nSTAT version 1.2.3\r\nSTAT rusage_user 1:300\r\nSTAT dummy ok\r\nEND\r\n"
861
+ socket.data.rewind
862
+ server = FakeServerDb.new socket
863
+ def server.host() 'localhost'; end
864
+ def server.port() 11211; end
865
+
866
+ @cache.servers = []
867
+ @cache.servers << server
868
+
869
+ expected = {
870
+ 'localhost:21201' => {
871
+ 'pid' => 20188, 'total_items' => 32, 'version' => '1.2.3',
872
+ 'rusage_user' => 1.0003, 'dummy' => 'ok'
873
+ }
874
+ }
875
+ assert_equal expected, @cache.stats
876
+
877
+ assert_equal "stats\r\n", socket.written.string
878
+ end
879
+
880
+ def test_basic_threaded_operations_should_work
881
+ cache = MemCacheDb.new :multithread => true,
882
+ :namespace => 'my_namespace',
883
+ :readonly => false
884
+
885
+ server = FakeServerDb.new
886
+ server.socket.data.write "STORED\r\n"
887
+ server.socket.data.rewind
888
+
889
+ cache.servers = []
890
+ cache.servers << server
891
+
892
+ assert cache.multithread
893
+
894
+ assert_nothing_raised do
895
+ cache.set "test", "test value"
896
+ end
897
+
898
+ output = server.socket.written.string
899
+ assert_match /set my_namespace:test/, output
900
+ assert_match /test value/, output
901
+ end
902
+
903
+ def test_basic_unthreaded_operations_should_work
904
+ cache = MemCacheDb.new :multithread => false,
905
+ :namespace => 'my_namespace',
906
+ :readonly => false
907
+
908
+ server = FakeServerDb.new
909
+ server.socket.data.write "STORED\r\n"
910
+ server.socket.data.rewind
911
+
912
+ cache.servers = []
913
+ cache.servers << server
914
+
915
+ assert !cache.multithread
916
+
917
+ assert_nothing_raised do
918
+ cache.set "test", "test value"
919
+ end
920
+
921
+ output = server.socket.written.string
922
+ assert_match /set my_namespace:test/, output
923
+ assert_match /test value/, output
924
+ end
925
+
926
+ def util_setup_fake_server
927
+ server = FakeServerDb.new
928
+ server.socket.data.write "VALUE my_namespace:key 0 14\r\n"
929
+ server.socket.data.write "\004\b\"\0170123456789\r\n"
930
+ server.socket.data.write "END\r\n"
931
+ server.socket.data.rewind
932
+
933
+ @cache.servers = []
934
+ @cache.servers << server
935
+
936
+ return server
937
+ end
938
+
939
+ def util_setup_server(memcache, host, responses)
940
+ server = MemCacheDb::Server.new memcache, host
941
+ server.instance_variable_set :@sock, StringIO.new(responses)
942
+
943
+ @cache.servers = []
944
+ @cache.servers << server
945
+
946
+ return server
947
+ end
948
+
949
+ end
950
+