consistent_hashing 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,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
+