memcache-client 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,19 @@
1
+ = 1.3.0
2
+
3
+ * Apply patch #6507, add stats command. Submitted by Tyler Kovacs.
4
+ * Apply patch #6509, parallel implementation of #get_multi. Submitted
5
+ by Tyler Kovacs.
6
+ * Validate keys. Disallow spaces in keys or keys that are too long.
7
+ * Perform more validation of server responses. MemCache now reports
8
+ errors if the socket was not in an expected state. (Please file
9
+ bugs if you find some.)
10
+ * Add #incr and #decr.
11
+ * Add raw argument to #set and #get to retrieve #incr and #decr
12
+ values.
13
+ * Also put on MemCacheError when using Cache::get with block.
14
+ * memcache.rb no longer sets $TESTING to a true value if it was
15
+ previously defined. Bug #8213 by Matijs van Zuijlen.
16
+
1
17
  = 1.2.1
2
18
 
3
19
  * Fix bug #7048, MemCache#servers= referenced changed local variable.
@@ -1,5 +1,5 @@
1
- All original code copyright 2005, 2006 Bob Cottrell, Eric Hodel, The
2
- Robot Co-op. All rights reserved.
1
+ All original code copyright 2005, 2006, 2007 Bob Cottrell, Eric Hodel,
2
+ The Robot Co-op. All rights reserved.
3
3
 
4
4
  Redistribution and use in source and binary forms, with or without
5
5
  modification, are permitted provided that the following conditions
@@ -13,8 +13,6 @@ are met:
13
13
  3. Neither the names of the authors nor the names of their contributors
14
14
  may be used to endorse or promote products derived from this software
15
15
  without specific prior written permission.
16
- 4. Redistribution in Rails or any sub-projects of Rails is not allowed
17
- until Rails runs without warnings with the ``-w'' flag enabled.
18
16
 
19
17
  THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
20
18
  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
data/README.txt CHANGED
@@ -4,9 +4,13 @@ Rubyforge Project:
4
4
 
5
5
  http://rubyforge.org/projects/rctools/
6
6
 
7
+ File bugs:
8
+
9
+ http://rubyforge.org/tracker/?func=add&group_id=1513&atid=5921
10
+
7
11
  == About
8
12
 
9
- memcache-client is a fast memcached client.
13
+ memcache-client is a client for Danga Interactive's memcached.
10
14
 
11
15
  == Installing memcache-client
12
16
 
@@ -30,14 +34,14 @@ See MemCache.new for details.
30
34
  === Using memcache-client with Rails
31
35
 
32
36
  Rails will automatically load the memcache-client gem, but you may
33
- need to uninstall Ruby-memcache, I don't know which one it will pick
34
- by default.
37
+ need to uninstall Ruby-memcache, I don't know which one will get
38
+ picked by default.
35
39
 
36
40
  Add your environment-specific caches to config/environment/*. If you run both
37
- development and production on the same machine be sure to use different
38
- namespaces. Be careful when running tests using memcache, you may get strange
39
- results. It will be less of a headache to simply use a readonly memcache when
40
- testing.
41
+ development and production on the same memcached server sets, be sure
42
+ to use different namespaces. Be careful when running tests using
43
+ memcache, you may get strange results. It will be less of a headache
44
+ to simply use a readonly memcache when testing.
41
45
 
42
46
  memcache-client also comes with a wrapper called Cache in memcache_util.rb for
43
47
  use with Rails. To use it be sure to assign your memcache connection to
@@ -1,4 +1,4 @@
1
- $TESTING = defined? $TESTING
1
+ $TESTING = defined? $TESTING && $TESTING
2
2
 
3
3
  require 'socket'
4
4
  require 'thread'
@@ -34,14 +34,15 @@ end
34
34
  # A Ruby client library for memcached.
35
35
  #
36
36
  # This is intended to provide access to basic memcached functionality. It
37
- # does not attempt to be complete implementation of the entire API.
37
+ # does not attempt to be complete implementation of the entire API, but it is
38
+ # approaching a complete implementation.
38
39
 
39
40
  class MemCache
40
41
 
41
42
  ##
42
43
  # The version of MemCache you are using.
43
44
 
44
- VERSION = '1.2.1'
45
+ VERSION = '1.3.0'
45
46
 
46
47
  ##
47
48
  # Default options for the cache object.
@@ -82,12 +83,14 @@ class MemCache
82
83
  ##
83
84
  # Accepts a list of +servers+ and a list of +opts+. +servers+ may be
84
85
  # omitted. See +servers=+ for acceptable server list arguments.
85
- #
86
+ #
86
87
  # Valid options for +opts+ are:
87
88
  #
88
89
  # [:namespace] Prepends this value to all keys added or retrieved.
89
90
  # [:readonly] Raises an exeception on cache writes when true.
90
91
  # [:multithread] Wraps cache access in a Mutex for thread safety.
92
+ #
93
+ # Other options are ignored.
91
94
 
92
95
  def initialize(*args)
93
96
  servers = []
@@ -119,11 +122,11 @@ class MemCache
119
122
  end
120
123
 
121
124
  ##
122
- # Return a string representation of the cache object.
125
+ # Returns a string representation of the cache object.
123
126
 
124
127
  def inspect
125
- sprintf("<MemCache: %s servers, %s buckets, ns: %p, ro: %p>",
126
- @servers.length, @buckets.length, @namespace, @readonly)
128
+ "<MemCache: %d servers, %d buckets, ns: %p, ro: %p>" %
129
+ [@servers.length, @buckets.length, @namespace, @readonly]
127
130
  end
128
131
 
129
132
  ##
@@ -134,7 +137,7 @@ class MemCache
134
137
  end
135
138
 
136
139
  ##
137
- # Returns whether the cache was created read only.
140
+ # Returns whether or not the cache object was created read only.
138
141
 
139
142
  def readonly?
140
143
  @readonly
@@ -172,14 +175,28 @@ class MemCache
172
175
  end
173
176
 
174
177
  ##
175
- # Retrieves +key+ from memcache.
178
+ # Deceremets the value for +key+ by +amount+ and returns the new value.
179
+ # +key+ must already exist. If +key+ is not an integer, it is assumed to be
180
+ # 0. +key+ can not be decremented below 0.
176
181
 
177
- def get(key)
178
- raise MemCacheError, 'No active servers' unless active?
179
- cache_key = make_cache_key key
180
- server = get_server_for_key cache_key
182
+ def decr(key, amount = 1)
183
+ server, cache_key = request_setup key
181
184
 
182
- raise MemCacheError, 'No connection to server' if server.socket.nil?
185
+ if @multithread then
186
+ threadsafe_cache_decr server, cache_key, amount
187
+ else
188
+ cache_decr server, cache_key, amount
189
+ end
190
+ rescue TypeError, SocketError, SystemCallError, IOError => err
191
+ handle_error server, err
192
+ end
193
+
194
+ ##
195
+ # Retrieves +key+ from memcache. If +raw+ is false, the value will be
196
+ # unmarshalled.
197
+
198
+ def get(key, raw = false)
199
+ server, cache_key = request_setup key
183
200
 
184
201
  value = if @multithread then
185
202
  threadsafe_cache_get server, cache_key
@@ -189,51 +206,102 @@ class MemCache
189
206
 
190
207
  return nil if value.nil?
191
208
 
192
- # Return the unmarshaled value.
193
- return Marshal.load(value)
194
- rescue ArgumentError, TypeError, SystemCallError, IOError => err
195
- server.close
196
- new_err = MemCacheError.new err.message
197
- new_err.set_backtrace err.backtrace
198
- raise new_err
209
+ value = Marshal.load value unless raw
210
+
211
+ return value
212
+ rescue TypeError, SocketError, SystemCallError, IOError => err
213
+ handle_error server, err
199
214
  end
200
215
 
201
216
  ##
202
- # Retrieves +keys+ and returns a Hash mapping keys to values.
217
+ # Retrieves multiple values from memcached in parallel, if possible.
218
+ #
219
+ # The memcached protocol supports the ability to retrieve multiple
220
+ # keys in a single request. Pass in an array of keys to this method
221
+ # and it will:
222
+ #
223
+ # 1. map the key to the appropriate memcached server
224
+ # 2. send a single request to each server that has one or more key values
225
+ #
226
+ # Returns a hash of values.
227
+ #
228
+ # cache["a"] = 1
229
+ # cache["b"] = 2
230
+ # cache.get_multi "a", "b" # => { "a" => 1, "b" => 2 }
203
231
 
204
232
  def get_multi(*keys)
205
- values = {}
206
- keys.flatten.each { |key| values[key] = get key }
207
- values
233
+ raise MemCacheError, 'No active servers' unless active?
234
+
235
+ keys.flatten!
236
+ key_count = keys.length
237
+ cache_keys = {}
238
+ server_keys = Hash.new { |h,k| h[k] = [] }
239
+
240
+ # map keys to servers
241
+ keys.each do |key|
242
+ server, cache_key = request_setup key
243
+ cache_keys[cache_key] = key
244
+ server_keys[server] << cache_key
245
+ end
246
+
247
+ results = {}
248
+
249
+ server_keys.each do |server, keys|
250
+ keys = keys.join ' '
251
+ values = if @multithread then
252
+ threadsafe_cache_get_multi server, keys
253
+ else
254
+ cache_get_multi server, keys
255
+ end
256
+ values.each do |key, value|
257
+ results[cache_keys[key]] = Marshal.load value
258
+ end
259
+ end
260
+
261
+ return results
262
+ rescue TypeError, SocketError, SystemCallError, IOError => err
263
+ handle_error server, err
208
264
  end
209
265
 
210
266
  ##
211
- # Add +key+ to the cache with value +value+ that expires in +expiry+
212
- # seconds.
267
+ # Increments the value for +key+ by +amount+ and retruns the new value.
268
+ # +key+ must already exist. If +key+ is not an integer, it is assumed to be
269
+ # 0.
213
270
 
214
- def set(key, value, expiry = 0)
215
- @mutex.lock if @multithread
271
+ def incr(key, amount = 1)
272
+ server, cache_key = request_setup key
216
273
 
217
- raise MemCacheError, "No active servers" unless self.active?
218
- raise MemCacheError, "Update of readonly cache" if @readonly
219
- cache_key = make_cache_key(key)
220
- server = get_server_for_key(cache_key)
274
+ if @multithread then
275
+ threadsafe_cache_incr server, cache_key, amount
276
+ else
277
+ cache_incr server, cache_key, amount
278
+ end
279
+ rescue TypeError, SocketError, SystemCallError, IOError => err
280
+ handle_error server, err
281
+ end
221
282
 
222
- sock = server.socket
223
- raise MemCacheError, "No connection to server" if sock.nil?
283
+ ##
284
+ # Add +key+ to the cache with value +value+ that expires in +expiry+
285
+ # seconds. If +raw+ is true, +value+ will not be Marshalled.
224
286
 
225
- marshaled_value = Marshal.dump value
226
- command = "set #{cache_key} 0 #{expiry} #{marshaled_value.size}\r\n#{marshaled_value}\r\n"
287
+ def set(key, value, expiry = 0, raw = false)
288
+ raise MemCacheError, "Update of readonly cache" if @readonly
289
+ server, cache_key = request_setup key
290
+ socket = server.socket
291
+
292
+ value = Marshal.dump value unless raw
293
+ command = "set #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"
227
294
 
228
295
  begin
229
- sock.write command
230
- sock.gets
231
- rescue SystemCallError, IOError => err
296
+ @mutex.lock if @multithread
297
+ socket.write command
298
+ socket.gets
299
+ rescue SocketError, SystemCallError, IOError => err
232
300
  server.close
233
301
  raise MemCacheError, err.message
302
+ ensure
303
+ @mutex.unlock if @multithread
234
304
  end
235
- ensure
236
- @mutex.unlock if @multithread
237
305
  end
238
306
 
239
307
  ##
@@ -252,7 +320,7 @@ class MemCache
252
320
  begin
253
321
  sock.write "delete #{cache_key} #{expiry}\r\n"
254
322
  sock.gets
255
- rescue SystemCallError, IOError => err
323
+ rescue SocketError, SystemCallError, IOError => err
256
324
  server.close
257
325
  raise MemCacheError, err.message
258
326
  end
@@ -269,6 +337,65 @@ class MemCache
269
337
  @servers.each { |server| server.close }
270
338
  end
271
339
 
340
+ ##
341
+ # Returns statistics for each memcached server. An explanation of the
342
+ # statistics can be found in the memcached docs:
343
+ #
344
+ # http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt
345
+ #
346
+ # Example:
347
+ #
348
+ # >> pp CACHE.stats
349
+ # {"localhost:11211"=>
350
+ # {"bytes"=>"4718",
351
+ # "pid"=>"20188",
352
+ # "connection_structures"=>"4",
353
+ # "time"=>"1162278121",
354
+ # "pointer_size"=>"32",
355
+ # "limit_maxbytes"=>"67108864",
356
+ # "cmd_get"=>"14532",
357
+ # "version"=>"1.2.0",
358
+ # "bytes_written"=>"432583",
359
+ # "cmd_set"=>"32",
360
+ # "get_misses"=>"0",
361
+ # "total_connections"=>"19",
362
+ # "curr_connections"=>"3",
363
+ # "curr_items"=>"4",
364
+ # "uptime"=>"1557",
365
+ # "get_hits"=>"14532",
366
+ # "total_items"=>"32",
367
+ # "rusage_system"=>"0.313952",
368
+ # "rusage_user"=>"0.119981",
369
+ # "bytes_read"=>"190619"}}
370
+ # => nil
371
+
372
+ def stats
373
+ raise MemCacheError, "No active servers" unless active?
374
+ server_stats = {}
375
+
376
+ @servers.each do |server|
377
+ sock = server.socket
378
+ raise MemCacheError, "No connection to server" if sock.nil?
379
+
380
+ value = nil
381
+ begin
382
+ sock.write "stats\r\n"
383
+ stats = {}
384
+ while line = sock.gets
385
+ break if line == "END\r\n"
386
+ line =~ /^STAT ([\w]+) ([\d.]+)/
387
+ stats[$1] = $2
388
+ end
389
+ server_stats["#{server.host}:#{server.port}"] = stats.clone
390
+ rescue SocketError, SystemCallError, IOError => err
391
+ server.close
392
+ raise MemCacheError, err.message
393
+ end
394
+ end
395
+
396
+ server_stats
397
+ end
398
+
272
399
  ##
273
400
  # Shortcut to get a value from the cache.
274
401
 
@@ -300,6 +427,9 @@ class MemCache
300
427
  # Pick a server to handle the request based on a hash of the key.
301
428
 
302
429
  def get_server_for_key(key)
430
+ raise ArgumentError, "illegal character in key #{key.inspect}" if
431
+ key =~ /\s/
432
+ raise ArgumentError, "key too long #{key.inspect}" if key.length > 250
303
433
  raise MemCacheError, "No servers available" if @servers.empty?
304
434
  return @servers.first if @servers.length == 1
305
435
 
@@ -322,6 +452,18 @@ class MemCache
322
452
  (key.crc32_ITU_T >> 16) & 0x7fff
323
453
  end
324
454
 
455
+ ##
456
+ # Performs a raw decr for +cache_key+ from +server+. Returns nil if not
457
+ # found.
458
+
459
+ def cache_decr(server, cache_key, amount)
460
+ socket = server.socket
461
+ socket.write "decr #{cache_key} #{amount}\r\n"
462
+ text = socket.gets
463
+ return nil if text == "NOT_FOUND\r\n"
464
+ return text.to_i
465
+ end
466
+
325
467
  ##
326
468
  # Fetches the raw data for +cache_key+ from +server+. Returns nil on cache
327
469
  # miss.
@@ -329,19 +471,99 @@ class MemCache
329
471
  def cache_get(server, cache_key)
330
472
  socket = server.socket
331
473
  socket.write "get #{cache_key}\r\n"
332
- text = socket.gets # "VALUE <key> <flags> <bytes>\r\n"
333
- return nil if text == "END\r\n"
474
+ keyline = socket.gets # "VALUE <key> <flags> <bytes>\r\n"
475
+ return nil if keyline == "END\r\n"
334
476
 
335
- text =~ /(\d+)\r/
477
+ unless keyline =~ /(\d+)\r/ then
478
+ server.close
479
+ raise MemCacheError, "unexpected response #{keyline.inspect}"
480
+ end
336
481
  value = socket.read $1.to_i
337
482
  socket.read 2 # "\r\n"
338
483
  socket.gets # "END\r\n"
339
484
  return value
340
485
  end
341
486
 
342
- def threadsafe_cache_get(socket, cache_key) # :nodoc:
487
+ ##
488
+ # Fetches +cache_keys+ from +server+ using a multi-get.
489
+
490
+ def cache_get_multi(server, cache_keys)
491
+ values = {}
492
+ socket = server.socket
493
+ socket.write "get #{cache_keys}\r\n"
494
+
495
+ while keyline = socket.gets
496
+ break if keyline == "END\r\n"
497
+ unless keyline =~ /^VALUE (.+) (.+) (.+)/ then
498
+ server.close
499
+ raise MemCacheError, "unexpected response #{keyline.inspect}"
500
+ end
501
+ key, data_length = $1, $3
502
+ values[$1] = socket.read data_length.to_i
503
+ socket.read(2) # "\r\n"
504
+ end
505
+
506
+ return values
507
+ end
508
+
509
+ ##
510
+ # Performs a raw incr for +cache_key+ from +server+. Returns nil if not
511
+ # found.
512
+
513
+ def cache_incr(server, cache_key, amount)
514
+ socket = server.socket
515
+ socket.write "incr #{cache_key} #{amount}\r\n"
516
+ text = socket.gets
517
+ return nil if text == "NOT_FOUND\r\n"
518
+ return text.to_i
519
+ end
520
+
521
+ ##
522
+ # Handles +error+ from +server+.
523
+
524
+ def handle_error(server, error)
525
+ server.close if server
526
+ new_error = MemCacheError.new error.message
527
+ new_error.set_backtrace error.backtrace
528
+ raise new_error
529
+ end
530
+
531
+ ##
532
+ # Performs setup for making a request with +key+ from memcached. Returns
533
+ # the server to fetch the key from and the complete key to use.
534
+
535
+ def request_setup(key)
536
+ raise MemCacheError, 'No active servers' unless active?
537
+ cache_key = make_cache_key key
538
+ server = get_server_for_key cache_key
539
+ raise MemCacheError, 'No connection to server' if server.socket.nil?
540
+ return server, cache_key
541
+ end
542
+
543
+ def threadsafe_cache_decr(server, cache_key, amount) # :nodoc:
544
+ @mutex.lock
545
+ cache_decr server, cache_key, amount
546
+ ensure
547
+ @mutex.unlock
548
+ end
549
+
550
+ def threadsafe_cache_get(server, cache_key) # :nodoc:
551
+ @mutex.lock
552
+ cache_get server, cache_key
553
+ ensure
554
+ @mutex.unlock
555
+ end
556
+
557
+ def threadsafe_cache_get_multi(socket, cache_key) # :nodoc:
558
+ @mutex.lock
559
+ cache_get_multi socket, cache_key
560
+ ensure
561
+ @mutex.unlock
562
+ end
563
+
564
+ def threadsafe_cache_incr(server, cache_key, amount) # :nodoc:
343
565
  @mutex.lock
344
- cache_get socket, cache_key
566
+ cache_incr server, cache_key, amount
345
567
  ensure
346
568
  @mutex.unlock
347
569
  end
@@ -414,8 +636,7 @@ class MemCache
414
636
  # Return a string representation of the server object.
415
637
 
416
638
  def inspect
417
- sprintf("<MemCache::Server: %s:%d [%d] (%s)>",
418
- @host, @port, @weight, @status)
639
+ "<MemCache::Server: %s:%d [%d] (%s)>" % [@host, @port, @weight, @status]
419
640
  end
420
641
 
421
642
  ##
@@ -425,7 +646,7 @@ class MemCache
425
646
  # been exceeded.
426
647
 
427
648
  def alive?
428
- !self.socket.nil?
649
+ !!socket
429
650
  end
430
651
 
431
652
  ##
@@ -14,20 +14,25 @@ module Cache
14
14
 
15
15
  def self.get(key, expiry = 0)
16
16
  start_time = Time.now
17
- result = CACHE.get key
17
+ value = CACHE.get key
18
18
  elapsed = Time.now - start_time
19
19
  ActiveRecord::Base.logger.debug('MemCache Get (%0.6f) %s' % [elapsed, key])
20
- if result.nil? and block_given? then
20
+ if value.nil? and block_given? then
21
21
  value = yield
22
22
  put key, value, expiry
23
23
  end
24
- return result
24
+ value
25
25
  rescue MemCache::MemCacheError => err
26
26
  ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}"
27
+ if block_given? then
28
+ value = yield
29
+ put key, value, expiry
30
+ end
31
+ value
27
32
  end
28
33
 
29
34
  ##
30
- # Places +value+ in the cache at +key+, with an optional +expiry+ time in
35
+ # Sets +value+ in the cache at +key+, with an optional +expiry+ time in
31
36
  # seconds.
32
37
 
33
38
  def self.put(key, value, expiry = 0)
@@ -35,8 +40,10 @@ module Cache
35
40
  CACHE.set key, value, expiry
36
41
  elapsed = Time.now - start_time
37
42
  ActiveRecord::Base.logger.debug('MemCache Set (%0.6f) %s' % [elapsed, key])
43
+ value
38
44
  rescue MemCache::MemCacheError => err
39
45
  ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}"
46
+ nil
40
47
  end
41
48
 
42
49
  ##
@@ -48,8 +55,10 @@ module Cache
48
55
  elapsed = Time.now - start_time
49
56
  ActiveRecord::Base.logger.debug('MemCache Delete (%0.6f) %s' %
50
57
  [elapsed, key])
58
+ nil
51
59
  rescue MemCache::MemCacheError => err
52
60
  ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}"
61
+ nil
53
62
  end
54
63
 
55
64
  ##
@@ -58,6 +67,7 @@ module Cache
58
67
  def self.reset
59
68
  CACHE.reset
60
69
  ActiveRecord::Base.logger.debug 'MemCache Connections Reset'
70
+ nil
61
71
  end
62
72
 
63
73
  end
@@ -43,9 +43,15 @@ class FakeServer
43
43
 
44
44
  def initialize(socket = nil)
45
45
  @socket = socket || FakeSocket.new
46
+ @closed = false
46
47
  end
47
48
 
48
49
  def close
50
+ @closed = true
51
+ end
52
+
53
+ def alive?
54
+ !@closed
49
55
  end
50
56
 
51
57
  end
@@ -66,6 +72,26 @@ class TestMemCache < Test::Unit::TestCase
66
72
  server.socket.written.string
67
73
  end
68
74
 
75
+ def test_cache_get_bad_state
76
+ server = FakeServer.new
77
+ server.socket.data.write "bogus response\r\n"
78
+ server.socket.data.rewind
79
+
80
+ @cache.servers = []
81
+ @cache.servers << server
82
+
83
+ e = assert_raise MemCache::MemCacheError do
84
+ @cache.cache_get(server, 'my_namespace:key')
85
+ end
86
+
87
+ assert_equal "unexpected response \"bogus response\\r\\n\"", e.message
88
+
89
+ deny server.alive?
90
+
91
+ assert_equal "get my_namespace:key\r\n",
92
+ server.socket.written.string
93
+ end
94
+
69
95
  def test_cache_get_miss
70
96
  socket = FakeSocket.new
71
97
  socket.data.write "END\r\n"
@@ -78,6 +104,26 @@ class TestMemCache < Test::Unit::TestCase
78
104
  socket.written.string
79
105
  end
80
106
 
107
+ def test_cache_get_multi_bad_state
108
+ server = FakeServer.new
109
+ server.socket.data.write "bogus response\r\n"
110
+ server.socket.data.rewind
111
+
112
+ @cache.servers = []
113
+ @cache.servers << server
114
+
115
+ e = assert_raise MemCache::MemCacheError do
116
+ @cache.cache_get_multi(server, ['my_namespace:key'])
117
+ end
118
+
119
+ assert_equal "unexpected response \"bogus response\\r\\n\"", e.message
120
+
121
+ deny server.alive?
122
+
123
+ assert_equal "get my_namespace:key\r\n",
124
+ server.socket.written.string
125
+ end
126
+
81
127
  def test_crc32_ITU_T
82
128
  assert_equal 0, ''.crc32_ITU_T
83
129
  assert_equal 1260851911, 'my_namespace:key'.crc32_ITU_T
@@ -140,6 +186,54 @@ class TestMemCache < Test::Unit::TestCase
140
186
  end
141
187
  end
142
188
 
189
+ def test_decr
190
+ server = FakeServer.new
191
+ server.socket.data.write "5\r\n"
192
+ server.socket.data.rewind
193
+
194
+ @cache.servers = []
195
+ @cache.servers << server
196
+
197
+ value = @cache.decr 'key'
198
+
199
+ assert_equal "decr my_namespace:key 1\r\n",
200
+ @cache.servers.first.socket.written.string
201
+
202
+ assert_equal 5, value
203
+ end
204
+
205
+ def test_decr_not_found
206
+ server = FakeServer.new
207
+ server.socket.data.write "NOT_FOUND\r\n"
208
+ server.socket.data.rewind
209
+
210
+ @cache.servers = []
211
+ @cache.servers << server
212
+
213
+ value = @cache.decr 'key'
214
+
215
+ assert_equal "decr my_namespace:key 1\r\n",
216
+ @cache.servers.first.socket.written.string
217
+
218
+ assert_equal nil, value
219
+ end
220
+
221
+ def test_decr_space_padding
222
+ server = FakeServer.new
223
+ server.socket.data.write "5 \r\n"
224
+ server.socket.data.rewind
225
+
226
+ @cache.servers = []
227
+ @cache.servers << server
228
+
229
+ value = @cache.decr 'key'
230
+
231
+ assert_equal "decr my_namespace:key 1\r\n",
232
+ @cache.servers.first.socket.written.string
233
+
234
+ assert_equal 5, value
235
+ end
236
+
143
237
  def test_get
144
238
  util_setup_fake_server
145
239
 
@@ -151,6 +245,14 @@ class TestMemCache < Test::Unit::TestCase
151
245
  assert_equal '0123456789', value
152
246
  end
153
247
 
248
+ def test_get_bad_key
249
+ util_setup_fake_server
250
+ assert_raise ArgumentError do @cache.get 'k y' end
251
+
252
+ util_setup_fake_server
253
+ assert_raise ArgumentError do @cache.get 'k' * 250 end
254
+ end
255
+
154
256
  def test_get_cache_get_IOError
155
257
  socket = Object.new
156
258
  def socket.write(arg) raise IOError, 'some io error'; end
@@ -200,15 +302,44 @@ class TestMemCache < Test::Unit::TestCase
200
302
  end
201
303
 
202
304
  def test_get_multi
203
- util_setup_fake_server
305
+ server = FakeServer.new
306
+ server.socket.data.write "VALUE my_namespace:key 0 14\r\n"
307
+ server.socket.data.write "\004\b\"\0170123456789\r\n"
308
+ server.socket.data.write "VALUE my_namespace:keyb 0 14\r\n"
309
+ server.socket.data.write "\004\b\"\0179876543210\r\n"
310
+ server.socket.data.write "END\r\n"
311
+ server.socket.data.rewind
312
+
313
+ @cache.servers = []
314
+ @cache.servers << server
315
+
316
+ values = @cache.get_multi 'key', 'keyb'
317
+
318
+ assert_equal "get my_namespace:key my_namespace:keyb\r\n",
319
+ server.socket.written.string
320
+
321
+ expected = { 'key' => '0123456789', 'keyb' => '9876543210' }
322
+
323
+ assert_equal expected.sort, values.sort
324
+ end
325
+
326
+ def test_get_raw
327
+ server = FakeServer.new
328
+ server.socket.data.write "VALUE my_namespace:key 0 10\r\n"
329
+ server.socket.data.write "0123456789\r\n"
330
+ server.socket.data.write "END\r\n"
331
+ server.socket.data.rewind
332
+
333
+ @cache.servers = []
334
+ @cache.servers << server
204
335
 
205
- values = @cache.get_multi 'keya', 'keyb'
206
336
 
207
- assert_equal "get my_namespace:keya\r\nget my_namespace:keyb\r\n",
337
+ value = @cache.get 'key', true
338
+
339
+ assert_equal "get my_namespace:key\r\n",
208
340
  @cache.servers.first.socket.written.string
209
341
 
210
- expected = { 'keya' => '0123456789', 'keyb' => nil }
211
- assert_equal expected, values
342
+ assert_equal '0123456789', value
212
343
  end
213
344
 
214
345
  def test_get_server_for_key
@@ -239,6 +370,70 @@ class TestMemCache < Test::Unit::TestCase
239
370
  assert_equal 'No servers available', e.message
240
371
  end
241
372
 
373
+ def test_get_server_for_key_spaces
374
+ e = assert_raise ArgumentError do
375
+ @cache.get_server_for_key 'space key'
376
+ end
377
+ assert_equal 'illegal character in key "space key"', e.message
378
+ end
379
+
380
+ def test_get_server_for_key_length
381
+ @cache.get_server_for_key 'x' * 250
382
+ long_key = 'x' * 251
383
+ e = assert_raise ArgumentError do
384
+ @cache.get_server_for_key long_key
385
+ end
386
+ assert_equal "key too long #{long_key.inspect}", e.message
387
+ end
388
+
389
+ def test_incr
390
+ server = FakeServer.new
391
+ server.socket.data.write "5\r\n"
392
+ server.socket.data.rewind
393
+
394
+ @cache.servers = []
395
+ @cache.servers << server
396
+
397
+ value = @cache.incr 'key'
398
+
399
+ assert_equal "incr my_namespace:key 1\r\n",
400
+ @cache.servers.first.socket.written.string
401
+
402
+ assert_equal 5, value
403
+ end
404
+
405
+ def test_incr_not_found
406
+ server = FakeServer.new
407
+ server.socket.data.write "NOT_FOUND\r\n"
408
+ server.socket.data.rewind
409
+
410
+ @cache.servers = []
411
+ @cache.servers << server
412
+
413
+ value = @cache.incr 'key'
414
+
415
+ assert_equal "incr my_namespace:key 1\r\n",
416
+ @cache.servers.first.socket.written.string
417
+
418
+ assert_equal nil, value
419
+ end
420
+
421
+ def test_incr_space_padding
422
+ server = FakeServer.new
423
+ server.socket.data.write "5 \r\n"
424
+ server.socket.data.rewind
425
+
426
+ @cache.servers = []
427
+ @cache.servers << server
428
+
429
+ value = @cache.incr 'key'
430
+
431
+ assert_equal "incr my_namespace:key 1\r\n",
432
+ @cache.servers.first.socket.written.string
433
+
434
+ assert_equal 5, value
435
+ end
436
+
242
437
  def test_make_cache_key
243
438
  assert_equal 'my_namespace:key', @cache.make_cache_key('key')
244
439
  @cache.namespace = nil
@@ -253,6 +448,72 @@ class TestMemCache < Test::Unit::TestCase
253
448
  assert_equal 'cannot convert Object into MemCache::Server', e.message
254
449
  end
255
450
 
451
+ def test_set
452
+ server = FakeServer.new
453
+ server.socket.data.write "STORED\r\n"
454
+ server.socket.data.rewind
455
+ @cache.servers = []
456
+ @cache.servers << server
457
+
458
+ @cache.set 'key', 'value'
459
+
460
+ expected = "set my_namespace:key 0 0 9\r\n\004\b\"\nvalue\r\n"
461
+ assert_equal expected, server.socket.written.string
462
+ end
463
+
464
+ def test_set_expiry
465
+ server = FakeServer.new
466
+ server.socket.data.write "STORED\r\n"
467
+ server.socket.data.rewind
468
+ @cache.servers = []
469
+ @cache.servers << server
470
+
471
+ @cache.set 'key', 'value', 5
472
+
473
+ expected = "set my_namespace:key 0 5 9\r\n\004\b\"\nvalue\r\n"
474
+ assert_equal expected, server.socket.written.string
475
+ end
476
+
477
+ def test_set_raw
478
+ server = FakeServer.new
479
+ server.socket.data.write "STORED\r\n"
480
+ server.socket.data.rewind
481
+ @cache.servers = []
482
+ @cache.servers << server
483
+
484
+ @cache.set 'key', 'value', 0, true
485
+
486
+ expected = "set my_namespace:key 0 0 5\r\nvalue\r\n"
487
+ assert_equal expected, server.socket.written.string
488
+ end
489
+
490
+ def test_set_readonly
491
+ cache = MemCache.new :readonly => true
492
+
493
+ e = assert_raise MemCache::MemCacheError do
494
+ cache.set 'key', 'value'
495
+ end
496
+
497
+ assert_equal 'Update of readonly cache', e.message
498
+ end
499
+
500
+ def test_stats
501
+ socket = FakeSocket.new
502
+ socket.data.write "STAT pid 20188\r\nSTAT total_items 32\r\rEND\r\n"
503
+ socket.data.rewind
504
+ server = FakeServer.new socket
505
+ def server.host() "localhost"; end
506
+ def server.port() 11211; end
507
+
508
+ @cache.servers = []
509
+ @cache.servers << server
510
+
511
+ expected = {"localhost:11211"=>{"pid"=>"20188", "total_items"=>"32"}}
512
+ assert_equal expected, @cache.stats
513
+
514
+ assert_equal "stats\r\n", socket.written.string
515
+ end
516
+
256
517
  def test_basic_threaded_operations_should_work
257
518
  cache = MemCache.new :multithread => true,
258
519
  :namespace => 'my_namespace',
@@ -276,10 +537,10 @@ class TestMemCache < Test::Unit::TestCase
276
537
  cache.set "test", "test value"
277
538
  end
278
539
  end
279
-
540
+
280
541
  def util_setup_fake_server
281
542
  server = FakeServer.new
282
- server.socket.data.write "VALUE my_namepsace:key 0 14\r\n"
543
+ server.socket.data.write "VALUE my_namespace:key 0 14\r\n"
283
544
  server.socket.data.write "\004\b\"\0170123456789\r\n"
284
545
  server.socket.data.write "END\r\n"
285
546
  server.socket.data.rewind
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.0.7
2
+ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: memcache-client
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.2.1
7
- date: 2006-12-06 00:00:00 -08:00
6
+ version: 1.3.0
7
+ date: 2007-03-06 00:00:00 -08:00
8
8
  summary: A Ruby memcached client
9
9
  require_paths:
10
10
  - lib
@@ -52,20 +52,20 @@ requirements: []
52
52
 
53
53
  dependencies:
54
54
  - !ruby/object:Gem::Dependency
55
- name: hoe
55
+ name: ZenTest
56
56
  version_requirement:
57
57
  version_requirements: !ruby/object:Gem::Version::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 1.1.5
61
+ version: 3.4.2
62
62
  version:
63
63
  - !ruby/object:Gem::Dependency
64
- name: ZenTest
64
+ name: hoe
65
65
  version_requirement:
66
66
  version_requirements: !ruby/object:Gem::Version::Requirement
67
67
  requirements:
68
68
  - - ">="
69
69
  - !ruby/object:Gem::Version
70
- version: 3.4.2
70
+ version: 1.2.0
71
71
  version: