consistent_hashing 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,143 @@
1
+ require File.dirname(__FILE__) + '/../lib/consistent_hashing'
2
+ require File.dirname(__FILE__) + '/spec_helper'
3
+
4
+ describe "Servers list with a duplicate" do
5
+ it "should raise a collision error" do
6
+
7
+ server = new_mock_server("127.0.0.1:11211")
8
+ lambda {
9
+ m = ConsistentHashing::Continuum.new([server, server])
10
+ }.should raise_error(ConsistentHashing::CollisionError)
11
+ end
12
+ end
13
+
14
+ describe "A continuum (with no collisions)" do
15
+ before do
16
+ servers = [
17
+ "192.168.0.1:11211",
18
+ "192.168.0.2:11211",
19
+ "192.168.0.3:11211",
20
+ "192.168.0.4:11211",
21
+ ].map{|address| new_mock_server(address)}
22
+ @continuum = ConsistentHashing::Continuum.new(servers)
23
+ @first_anchor = @continuum.anchors.first
24
+ @last_anchor = @continuum.anchors.last
25
+ end
26
+
27
+ it "should use the first anchor for a search value in (last anchor value, 1]" do
28
+ search_values = some_values_in_segment(@last_anchor.value, 1.0)
29
+
30
+ search_values.each do |search_value|
31
+ responsible_anchor(@continuum, search_value).should == @first_anchor
32
+ end
33
+ end
34
+
35
+ it "should use the first anchor for a search value in (0, first anchor value]" do
36
+ search_values = some_values_in_segment(0.0, @first_anchor.value)
37
+
38
+ search_values.each do |search_value|
39
+ responsible_anchor(@continuum, search_value).should == @first_anchor
40
+ end
41
+ end
42
+
43
+ it "should find the correct server for search value in (first anchor value, last anchor value]" do
44
+ search_values = some_values_in_segment(@first_anchor.value, @last_anchor.value)
45
+
46
+ search_values.each do |search_value|
47
+ responsible_anchor = responsible_anchor(@continuum, search_value)
48
+
49
+ previous_anchor = previous_anchor(responsible_anchor, @continuum)
50
+
51
+ previous_anchor.value.should < search_value
52
+ search_value.should <= responsible_anchor.value
53
+ end
54
+ end
55
+
56
+ it "should have a method to return the server reponsible for a key" do
57
+ search_value = @continuum.hash_to_unit_circle("foo")
58
+ responsible_anchor = responsible_anchor(@continuum, search_value)
59
+
60
+ @continuum.find_server(search_value).should == responsible_anchor.server
61
+ end
62
+
63
+ it "should be able to calculate the expected load" do
64
+ servers = []
65
+ 3.times do |i|
66
+ server = mock("server-#{i}")
67
+ server.stub!(:address).and_return("localhost:1121#{i}")
68
+ servers << server
69
+ end
70
+
71
+ # server[0]: [ 0, 0.1], (0.4, 0.5], (0.9, 1.0] => 0.1 + 0.1 + 0.1 = 0.3
72
+ # server[1]: (0.1, 0.2], (0.5, 0.7] => 0.1 + 0.2 = 0.3
73
+ # server[2]: (0.2, 0.4], (0.7, 0.9] => 0.2 + 0.2 = 0.4
74
+ value_server_pairs = [
75
+ [0.1, servers[0]],
76
+ [0.2, servers[1]],
77
+ [0.4, servers[2]],
78
+ [0.5, servers[0]],
79
+ [0.7, servers[1]],
80
+ [0.9, servers[2]],
81
+ ]
82
+
83
+ anchors = value_server_pairs.map do |value, server|
84
+ ConsistentHashing::Anchor.new(value, server)
85
+ end
86
+
87
+ address_to_expected_load = {
88
+ servers[0].address => 0.3,
89
+ servers[1].address => 0.3,
90
+ servers[2].address => 0.4,
91
+ }
92
+
93
+ @continuum.stub!(:anchors).and_return(anchors)
94
+ expected_load = @continuum.expected_load
95
+
96
+ # all (key, value) pairs in the hash should be correct
97
+ expected_load.each do |address, load|
98
+ load.should be_close(address_to_expected_load[address], 0.0001)
99
+ end
100
+
101
+ # the hash should be complete
102
+ expected_load.size.should == address_to_expected_load.size
103
+ end
104
+
105
+ it "should have a to_s method" do
106
+ expected_lines = @continuum.anchors.map{|anchor| anchor.to_s}
107
+ @continuum.to_s.split("\n").should == expected_lines
108
+ end
109
+ end
110
+
111
+ describe "A Continuum::Anchor" do
112
+ before do
113
+ @value = 0.123
114
+ @address = "localhost:11211"
115
+
116
+ @server = mock('server')
117
+ @server.stub!(:address).and_return(@address)
118
+
119
+ @anchor = ConsistentHashing::Anchor.new(@value, @server)
120
+ end
121
+
122
+ it "should have a to_s method that displays teh value and server address" do
123
+ @anchor.to_s.should == "value:#{@value}, server:#{@server.address}"
124
+ end
125
+
126
+ it "should have an inspect method" do
127
+ @anchor.inspect.should ==
128
+ "<ConsistentHashing::Anchor | value:#{@value}, server:#{@server.address}>"
129
+ end
130
+
131
+ it "should implement the <=> operator by comparing the value of the anchors" do
132
+ # Unfortunately, I cannot do
133
+ # @anchor.value.should_receieve(:<=>).with(some_value)
134
+ # So I will settle for a randomized test
135
+ 10.times do
136
+ random_value = rand
137
+ other_anchor = mock("other_anchor")
138
+ other_anchor.should_receive(:value).and_return(random_value)
139
+
140
+ (@anchor <=> other_anchor).should == (@anchor.value <=> random_value)
141
+ end
142
+ end
143
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,40 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ # For the purposes of testing, I want access to the anchor, not just the server
5
+ # so that I can easily verify that the correct anchor was returned.
6
+ def responsible_anchor(continuum, search_value)
7
+ continuum.send(:closest_anchor, search_value)
8
+ end
9
+
10
+ # Get the anchor responsible for the previous segment.
11
+ def previous_anchor(anchor, continuum)
12
+ index = continuum.anchors.index(anchor)
13
+ previous_index = index - 1
14
+ continuum.anchors[previous_index]
15
+ end
16
+
17
+ # NOTE: I use the word 'segment' to mean a range that excludes the lower bound
18
+ # but includes the upper bound. For example, (0, 10] is a segment containing
19
+ # the numbers 1-10.
20
+
21
+ # Get an array of values in the segment. The values will include some edge
22
+ # cases, and a few random values.
23
+ def some_values_in_segment(exclusive_lower_bound, inclusive_upper_bound)
24
+ raise "invalid segment" unless exclusive_lower_bound < inclusive_upper_bound
25
+
26
+ difference = inclusive_upper_bound - exclusive_lower_bound
27
+ values = [ 0.01, 0.25, 0.50, 0.75, 0.99 ].map do |r|
28
+ exclusive_lower_bound + r*difference
29
+ end
30
+ values << inclusive_upper_bound
31
+ values
32
+ end
33
+
34
+ def new_mock_server(address, weight=1)
35
+ server = Object.new
36
+ server.stub!(:address).and_return(address)
37
+ server.stub!(:weight).and_return(weight)
38
+ server
39
+ end
40
+
data/test/benchmark.rb ADDED
@@ -0,0 +1,32 @@
1
+ require 'rubygems'
2
+ require 'benchmark'
3
+
4
+ require 'memcache'
5
+ require File.dirname(__FILE__) + '/../lib/mem_cache_with_consistent_hashing'
6
+
7
+ class MemCache::Server
8
+ def alive?
9
+ true
10
+ end
11
+ end
12
+
13
+ servers = [*"localhost:11211".."localhost:11230"]
14
+
15
+ original_client = MemCache.new(servers)
16
+ consistent_hashing_client = MemCacheWithConsistentHashing.new(servers)
17
+
18
+ num_keys = 10_000
19
+ keys = [*0...num_keys].map{|i| rand.to_s}
20
+
21
+ time_for_original, time_for_consistent_hashing =
22
+ [original_client, consistent_hashing_client].map do |client|
23
+ Benchmark.realtime do
24
+ for key in keys
25
+ client.get_server_for_key(key)
26
+ end
27
+ end
28
+ end
29
+
30
+ printf("%4.2f <= time_for_original\n", time_for_original)
31
+ printf("%4.2f <= time_for_consistent_hashing\n", time_for_consistent_hashing)
32
+ printf("%4.2f <= time_for_original/time_for_consistent_hashing\n", time_for_original/time_for_consistent_hashing)
@@ -0,0 +1,586 @@
1
+ require 'stringio'
2
+ require 'test/unit'
3
+ require 'rubygems'
4
+ require 'test/zentest_assertions'
5
+
6
+ $TESTING = true
7
+
8
+ require File.dirname(__FILE__) + '/../lib/mem_cache_with_consistent_hashing'
9
+
10
+ class MemCache
11
+
12
+ attr_reader :servers
13
+ attr_writer :namespace
14
+
15
+ end
16
+
17
+ class MemCacheWithConsistentHashing
18
+
19
+ def add_server(server)
20
+ @continuum.anchors = (@continuum.anchors + @continuum.send(:create_anchors_for_server, server).sort)
21
+ @servers << server
22
+ end
23
+
24
+ end
25
+
26
+ class FakeSocket
27
+
28
+ attr_reader :written, :data
29
+
30
+ def initialize
31
+ @written = StringIO.new
32
+ @data = StringIO.new
33
+ end
34
+
35
+ def write(data)
36
+ @written.write data
37
+ end
38
+
39
+ def gets
40
+ @data.gets
41
+ end
42
+
43
+ def read(arg)
44
+ @data.read arg
45
+ end
46
+
47
+ end
48
+
49
+ class FakeServer
50
+
51
+ attr_reader :socket
52
+
53
+ @@port = 11211
54
+
55
+ def initialize(socket = nil)
56
+ @port = @@port
57
+ @@port += 1
58
+ @socket = socket || FakeSocket.new
59
+ @closed = false
60
+ end
61
+
62
+ def close
63
+ @closed = true
64
+ end
65
+
66
+ def alive?
67
+ !@closed
68
+ end
69
+
70
+ def weight
71
+ 1
72
+ end
73
+
74
+ def address
75
+ "localhost:#{@port}"
76
+ end
77
+
78
+ end
79
+
80
+ class TestMemCache < Test::Unit::TestCase
81
+
82
+ def setup
83
+ @cache = MemCacheWithConsistentHashing.new 'localhost:1', :namespace => 'my_namespace'
84
+ end
85
+
86
+ def test_cache_get
87
+ server = util_setup_fake_server
88
+
89
+ assert_equal "\004\b\"\0170123456789",
90
+ @cache.cache_get(server, 'my_namespace:key')
91
+
92
+ assert_equal "get my_namespace:key\r\n",
93
+ server.socket.written.string
94
+ end
95
+
96
+ def test_cache_get_bad_state
97
+ server = FakeServer.new
98
+ server.socket.data.write "bogus response\r\n"
99
+ server.socket.data.rewind
100
+
101
+ @cache.servers = []
102
+ @cache.add_server(server)
103
+
104
+ e = assert_raise MemCache::MemCacheError do
105
+ @cache.cache_get(server, 'my_namespace:key')
106
+ end
107
+
108
+ assert_equal "unexpected response \"bogus response\\r\\n\"", e.message
109
+
110
+ deny server.alive?
111
+
112
+ assert_equal "get my_namespace:key\r\n",
113
+ server.socket.written.string
114
+ end
115
+
116
+ def test_cache_get_miss
117
+ socket = FakeSocket.new
118
+ socket.data.write "END\r\n"
119
+ socket.data.rewind
120
+ server = FakeServer.new socket
121
+
122
+ assert_equal nil, @cache.cache_get(server, 'my_namespace:key')
123
+
124
+ assert_equal "get my_namespace:key\r\n",
125
+ socket.written.string
126
+ end
127
+
128
+ def test_cache_get_multi_bad_state
129
+ server = FakeServer.new
130
+ server.socket.data.write "bogus response\r\n"
131
+ server.socket.data.rewind
132
+
133
+ @cache.servers = []
134
+ @cache.add_server(server)
135
+
136
+ e = assert_raise MemCache::MemCacheError do
137
+ @cache.cache_get_multi(server, ['my_namespace:key'])
138
+ end
139
+
140
+ assert_equal "unexpected response \"bogus response\\r\\n\"", e.message
141
+
142
+ deny server.alive?
143
+
144
+ assert_equal "get my_namespace:key\r\n",
145
+ server.socket.written.string
146
+ end
147
+
148
+ def test_crc32_ITU_T
149
+ assert_equal 0, ''.crc32_ITU_T
150
+ assert_equal 1260851911, 'my_namespace:key'.crc32_ITU_T
151
+ end
152
+
153
+ def test_initialize
154
+ cache = MemCacheWithConsistentHashing.new :namespace => 'my_namespace', :readonly => true
155
+
156
+ assert_equal 'my_namespace', cache.namespace
157
+ assert_equal true, cache.readonly?
158
+ assert_equal true, cache.servers.empty?
159
+ end
160
+
161
+ def test_initialize_compatible
162
+ cache = MemCacheWithConsistentHashing.new ['localhost:11211', 'localhost:11212'],
163
+ :namespace => 'my_namespace', :readonly => true
164
+
165
+ assert_equal 'my_namespace', cache.namespace
166
+ assert_equal true, cache.readonly?
167
+ assert_equal false, cache.servers.empty?
168
+ end
169
+
170
+ def test_initialize_compatible_no_hash
171
+ cache = MemCacheWithConsistentHashing.new ['localhost:11211', 'localhost:11212']
172
+
173
+ assert_equal nil, cache.namespace
174
+ assert_equal false, cache.readonly?
175
+ assert_equal false, cache.servers.empty?
176
+ end
177
+
178
+ def test_initialize_compatible_one_server
179
+ cache = MemCacheWithConsistentHashing.new 'localhost:11211'
180
+
181
+ assert_equal nil, cache.namespace
182
+ assert_equal false, cache.readonly?
183
+ assert_equal false, cache.servers.empty?
184
+ end
185
+
186
+ def test_initialize_compatible_bad_arg
187
+ e = assert_raise ArgumentError do
188
+ cache = MemCacheWithConsistentHashing.new Object.new
189
+ end
190
+
191
+ assert_equal 'first argument must be Array, Hash or String', e.message
192
+ end
193
+
194
+ def test_initialize_multiple_servers
195
+ cache = MemCacheWithConsistentHashing.new %w[localhost:11211 localhost:11212],
196
+ :namespace => 'my_namespace', :readonly => true
197
+
198
+ assert_equal 'my_namespace', cache.namespace
199
+ assert_equal true, cache.readonly?
200
+ assert_equal false, cache.servers.empty?
201
+ deny_empty cache.instance_variable_get(:@buckets)
202
+ end
203
+
204
+ def test_initialize_too_many_args
205
+ assert_raises ArgumentError do
206
+ MemCacheWithConsistentHashing.new 1, 2, 3
207
+ end
208
+ end
209
+
210
+ def test_decr
211
+ server = FakeServer.new
212
+ server.socket.data.write "5\r\n"
213
+ server.socket.data.rewind
214
+
215
+ @cache.servers = []
216
+ @cache.add_server(server)
217
+
218
+ value = @cache.decr 'key'
219
+
220
+ assert_equal "decr my_namespace:key 1\r\n",
221
+ @cache.servers.first.socket.written.string
222
+
223
+ assert_equal 5, value
224
+ end
225
+
226
+ def test_decr_not_found
227
+ server = FakeServer.new
228
+ server.socket.data.write "NOT_FOUND\r\n"
229
+ server.socket.data.rewind
230
+
231
+ @cache.servers = []
232
+ @cache.add_server(server)
233
+
234
+ value = @cache.decr 'key'
235
+
236
+ assert_equal "decr my_namespace:key 1\r\n",
237
+ @cache.servers.first.socket.written.string
238
+
239
+ assert_equal nil, value
240
+ end
241
+
242
+ def test_decr_space_padding
243
+ server = FakeServer.new
244
+ server.socket.data.write "5 \r\n"
245
+ server.socket.data.rewind
246
+
247
+ @cache.servers = []
248
+ @cache.add_server(server)
249
+
250
+ value = @cache.decr 'key'
251
+
252
+ assert_equal "decr my_namespace:key 1\r\n",
253
+ @cache.servers.first.socket.written.string
254
+
255
+ assert_equal 5, value
256
+ end
257
+
258
+ def test_get
259
+ util_setup_fake_server
260
+
261
+ value = @cache.get 'key'
262
+
263
+ assert_equal "get my_namespace:key\r\n",
264
+ @cache.servers.first.socket.written.string
265
+
266
+ assert_equal '0123456789', value
267
+ end
268
+
269
+ def test_get_bad_key
270
+ util_setup_fake_server
271
+ assert_raise ArgumentError do @cache.get 'k y' end
272
+
273
+ util_setup_fake_server
274
+ assert_raise ArgumentError do @cache.get 'k' * 250 end
275
+ end
276
+
277
+ def test_get_cache_get_IOError
278
+ socket = Object.new
279
+ def socket.write(arg) raise IOError, 'some io error'; end
280
+ server = FakeServer.new socket
281
+
282
+ @cache.servers = []
283
+ @cache.add_server(server)
284
+
285
+ e = assert_raise MemCache::MemCacheError do
286
+ @cache.get 'my_namespace:key'
287
+ end
288
+
289
+ assert_equal 'some io error', e.message
290
+ end
291
+
292
+ def test_get_cache_get_SystemCallError
293
+ socket = Object.new
294
+ def socket.write(arg) raise SystemCallError, 'some syscall error'; end
295
+ server = FakeServer.new socket
296
+
297
+ @cache.servers = []
298
+ @cache.add_server(server)
299
+
300
+ e = assert_raise MemCache::MemCacheError do
301
+ @cache.get 'my_namespace:key'
302
+ end
303
+
304
+ assert_equal 'unknown error - some syscall error', e.message
305
+ end
306
+
307
+ def test_get_no_connection
308
+ @cache.servers = 'localhost:1'
309
+ e = assert_raise MemCache::MemCacheError do
310
+ @cache.get 'key'
311
+ end
312
+
313
+ assert_equal 'No connection to server', e.message
314
+ end
315
+
316
+ def test_get_no_servers
317
+ @cache.servers = []
318
+ e = assert_raise MemCache::MemCacheError do
319
+ @cache.get 'key'
320
+ end
321
+
322
+ assert_equal 'No active servers', e.message
323
+ end
324
+
325
+ def test_get_multi
326
+ server = FakeServer.new
327
+ server.socket.data.write "VALUE my_namespace:key 0 14\r\n"
328
+ server.socket.data.write "\004\b\"\0170123456789\r\n"
329
+ server.socket.data.write "VALUE my_namespace:keyb 0 14\r\n"
330
+ server.socket.data.write "\004\b\"\0179876543210\r\n"
331
+ server.socket.data.write "END\r\n"
332
+ server.socket.data.rewind
333
+
334
+ @cache.servers = []
335
+ @cache.add_server(server)
336
+
337
+ values = @cache.get_multi 'key', 'keyb'
338
+
339
+ assert_equal "get my_namespace:key my_namespace:keyb\r\n",
340
+ server.socket.written.string
341
+
342
+ expected = { 'key' => '0123456789', 'keyb' => '9876543210' }
343
+
344
+ assert_equal expected.sort, values.sort
345
+ end
346
+
347
+ def test_get_raw
348
+ server = FakeServer.new
349
+ server.socket.data.write "VALUE my_namespace:key 0 10\r\n"
350
+ server.socket.data.write "0123456789\r\n"
351
+ server.socket.data.write "END\r\n"
352
+ server.socket.data.rewind
353
+
354
+ @cache.servers = []
355
+ @cache.add_server(server)
356
+
357
+
358
+ value = @cache.get 'key', true
359
+
360
+ assert_equal "get my_namespace:key\r\n",
361
+ @cache.servers.first.socket.written.string
362
+
363
+ assert_equal '0123456789', value
364
+ end
365
+
366
+ def test_get_server_for_key
367
+ server = @cache.get_server_for_key 'key'
368
+ assert_equal 'localhost', server.host
369
+ assert_equal 1, server.port
370
+ end
371
+
372
+ def test_get_server_for_key_multiple
373
+ s1 = util_setup_server @cache, 'one.example.com', ''
374
+ s2 = util_setup_server @cache, 'two.example.com', ''
375
+ @cache.instance_variable_set :@servers, [s1, s2]
376
+ @cache.instance_variable_set :@buckets, [s1, s2]
377
+
378
+ server = @cache.get_server_for_key 'keya'
379
+ assert_equal 'two.example.com', server.host
380
+ server = @cache.get_server_for_key 'keyb'
381
+ assert_equal 'two.example.com', server.host
382
+ end
383
+
384
+ def test_get_server_for_key_no_servers
385
+ @cache.servers = []
386
+
387
+ e = assert_raise MemCache::MemCacheError do
388
+ @cache.get_server_for_key 'key'
389
+ end
390
+
391
+ assert_equal 'No servers available', e.message
392
+ end
393
+
394
+ def test_get_server_for_key_spaces
395
+ e = assert_raise ArgumentError do
396
+ @cache.get_server_for_key 'space key'
397
+ end
398
+ assert_equal 'illegal character in key "space key"', e.message
399
+ end
400
+
401
+ def test_get_server_for_key_length
402
+ @cache.get_server_for_key 'x' * 250
403
+ long_key = 'x' * 251
404
+ e = assert_raise ArgumentError do
405
+ @cache.get_server_for_key long_key
406
+ end
407
+ assert_equal "key too long #{long_key.inspect}", e.message
408
+ end
409
+
410
+ def test_incr
411
+ server = FakeServer.new
412
+ server.socket.data.write "5\r\n"
413
+ server.socket.data.rewind
414
+
415
+ @cache.servers = []
416
+ @cache.add_server(server)
417
+
418
+ value = @cache.incr 'key'
419
+
420
+ assert_equal "incr my_namespace:key 1\r\n",
421
+ @cache.servers.first.socket.written.string
422
+
423
+ assert_equal 5, value
424
+ end
425
+
426
+ def test_incr_not_found
427
+ server = FakeServer.new
428
+ server.socket.data.write "NOT_FOUND\r\n"
429
+ server.socket.data.rewind
430
+
431
+ @cache.servers = []
432
+ @cache.add_server(server)
433
+
434
+ value = @cache.incr 'key'
435
+
436
+ assert_equal "incr my_namespace:key 1\r\n",
437
+ @cache.servers.first.socket.written.string
438
+
439
+ assert_equal nil, value
440
+ end
441
+
442
+ def test_incr_space_padding
443
+ server = FakeServer.new
444
+ server.socket.data.write "5 \r\n"
445
+ server.socket.data.rewind
446
+
447
+ @cache.servers = []
448
+ @cache.add_server(server)
449
+
450
+ value = @cache.incr 'key'
451
+
452
+ assert_equal "incr my_namespace:key 1\r\n",
453
+ @cache.servers.first.socket.written.string
454
+
455
+ assert_equal 5, value
456
+ end
457
+
458
+ def test_make_cache_key
459
+ assert_equal 'my_namespace:key', @cache.make_cache_key('key')
460
+ @cache.namespace = nil
461
+ assert_equal 'key', @cache.make_cache_key('key')
462
+ end
463
+
464
+ def test_servers_equals_type_error
465
+ e = assert_raise TypeError do
466
+ @cache.servers = [Object.new]
467
+ end
468
+
469
+ assert_equal 'cannot convert Object into MemCache::Server', e.message
470
+ end
471
+
472
+ def test_set
473
+ server = FakeServer.new
474
+ server.socket.data.write "STORED\r\n"
475
+ server.socket.data.rewind
476
+ @cache.servers = []
477
+ @cache.add_server(server)
478
+
479
+ @cache.set 'key', 'value'
480
+
481
+ expected = "set my_namespace:key 0 0 9\r\n\004\b\"\nvalue\r\n"
482
+ assert_equal expected, server.socket.written.string
483
+ end
484
+
485
+ def test_set_expiry
486
+ server = FakeServer.new
487
+ server.socket.data.write "STORED\r\n"
488
+ server.socket.data.rewind
489
+ @cache.servers = []
490
+ @cache.add_server(server)
491
+
492
+ @cache.set 'key', 'value', 5
493
+
494
+ expected = "set my_namespace:key 0 5 9\r\n\004\b\"\nvalue\r\n"
495
+ assert_equal expected, server.socket.written.string
496
+ end
497
+
498
+ def test_set_raw
499
+ server = FakeServer.new
500
+ server.socket.data.write "STORED\r\n"
501
+ server.socket.data.rewind
502
+ @cache.servers = []
503
+ @cache.add_server(server)
504
+
505
+ @cache.set 'key', 'value', 0, true
506
+
507
+ expected = "set my_namespace:key 0 0 5\r\nvalue\r\n"
508
+ assert_equal expected, server.socket.written.string
509
+ end
510
+
511
+ def test_set_readonly
512
+ cache = MemCacheWithConsistentHashing.new :readonly => true
513
+
514
+ e = assert_raise MemCache::MemCacheError do
515
+ cache.set 'key', 'value'
516
+ end
517
+
518
+ assert_equal 'Update of readonly cache', e.message
519
+ end
520
+
521
+ def test_stats
522
+ socket = FakeSocket.new
523
+ socket.data.write "STAT pid 20188\r\nSTAT total_items 32\r\rEND\r\n"
524
+ socket.data.rewind
525
+ server = FakeServer.new socket
526
+ def server.host() "localhost"; end
527
+ def server.port() 11211; end
528
+
529
+ @cache.servers = []
530
+ @cache.add_server(server)
531
+
532
+ expected = {"localhost:11211"=>{"pid"=>"20188", "total_items"=>"32"}}
533
+ assert_equal expected, @cache.stats
534
+
535
+ assert_equal "stats\r\n", socket.written.string
536
+ end
537
+
538
+ def test_basic_threaded_operations_should_work
539
+ cache = MemCacheWithConsistentHashing.new :multithread => true,
540
+ :namespace => 'my_namespace',
541
+ :readonly => false
542
+ server = util_setup_server cache, 'example.com', "OK\r\n"
543
+ cache.instance_variable_set :@servers, [server]
544
+
545
+ assert_nothing_raised do
546
+ cache.set "test", "test value"
547
+ end
548
+ end
549
+
550
+ def test_basic_unthreaded_operations_should_work
551
+ cache = MemCacheWithConsistentHashing.new :multithread => false,
552
+ :namespace => 'my_namespace',
553
+ :readonly => false
554
+ server = util_setup_server cache, 'example.com', "OK\r\n"
555
+ cache.instance_variable_set :@servers, [server]
556
+
557
+ assert_nothing_raised do
558
+ cache.set "test", "test value"
559
+ end
560
+ end
561
+
562
+ def util_setup_fake_server
563
+ server = FakeServer.new
564
+ server.socket.data.write "VALUE my_namespace:key 0 14\r\n"
565
+ server.socket.data.write "\004\b\"\0170123456789\r\n"
566
+ server.socket.data.write "END\r\n"
567
+ server.socket.data.rewind
568
+
569
+ @cache.servers = []
570
+ @cache.add_server(server)
571
+
572
+ return server
573
+ end
574
+
575
+ def util_setup_server(memcache, host, responses)
576
+ server = MemCache::Server.new memcache, host
577
+ server.instance_variable_set :@sock, StringIO.new(responses)
578
+
579
+ @cache.servers = []
580
+ @cache.add_server(server)
581
+
582
+ return server
583
+ end
584
+
585
+ end
586
+