OLD | NEW |
| (Empty) |
1 # Copyright (c) 2007 Twisted Matrix Laboratories. | |
2 # See LICENSE for details. | |
3 | |
4 """ | |
5 Test the memcache client protocol. | |
6 """ | |
7 | |
8 from twisted.protocols.memcache import MemCacheProtocol, NoSuchCommand | |
9 from twisted.protocols.memcache import ClientError, ServerError | |
10 | |
11 from twisted.trial.unittest import TestCase | |
12 from twisted.test.proto_helpers import StringTransportWithDisconnection | |
13 from twisted.internet.task import Clock | |
14 from twisted.internet.defer import Deferred, gatherResults, TimeoutError | |
15 | |
16 | |
17 | |
18 class MemCacheTestCase(TestCase): | |
19 """ | |
20 Test client protocol class L{MemCacheProtocol}. | |
21 """ | |
22 | |
23 def setUp(self): | |
24 """ | |
25 Create a memcache client, connect it to a string protocol, and make it | |
26 use a deterministic clock. | |
27 """ | |
28 self.proto = MemCacheProtocol() | |
29 self.clock = Clock() | |
30 self.proto.callLater = self.clock.callLater | |
31 self.transport = StringTransportWithDisconnection() | |
32 self.transport.protocol = self.proto | |
33 self.proto.makeConnection(self.transport) | |
34 | |
35 | |
36 def _test(self, d, send, recv, result): | |
37 """ | |
38 Shortcut method for classic tests. | |
39 | |
40 @param d: the resulting deferred from the memcache command. | |
41 @type d: C{Deferred} | |
42 | |
43 @param send: the expected data to be sent. | |
44 @type send: C{str} | |
45 | |
46 @param recv: the data to simulate as reception. | |
47 @type recv: C{str} | |
48 | |
49 @param result: the expected result. | |
50 @type result: C{any} | |
51 """ | |
52 def cb(res): | |
53 self.assertEquals(res, result) | |
54 self.assertEquals(self.transport.value(), send) | |
55 d.addCallback(cb) | |
56 self.proto.dataReceived(recv) | |
57 return d | |
58 | |
59 | |
60 def test_get(self): | |
61 """ | |
62 L{MemCacheProtocol.get} should return a L{Deferred} which is | |
63 called back with the value and the flag associated with the given key | |
64 if the server returns a successful result. | |
65 """ | |
66 return self._test(self.proto.get("foo"), "get foo\r\n", | |
67 "VALUE foo 0 3\r\nbar\r\nEND\r\n", (0, "bar")) | |
68 | |
69 | |
70 def test_emptyGet(self): | |
71 """ | |
72 Test getting a non-available key: it should succeed but return C{None} | |
73 as value and C{0} as flag. | |
74 """ | |
75 return self._test(self.proto.get("foo"), "get foo\r\n", | |
76 "END\r\n", (0, None)) | |
77 | |
78 | |
79 def test_set(self): | |
80 """ | |
81 L{MemCacheProtocol.set} should return a L{Deferred} which is | |
82 called back with C{True} when the operation succeeds. | |
83 """ | |
84 return self._test(self.proto.set("foo", "bar"), | |
85 "set foo 0 0 3\r\nbar\r\n", "STORED\r\n", True) | |
86 | |
87 | |
88 def test_add(self): | |
89 """ | |
90 L{MemCacheProtocol.add} should return a L{Deferred} which is | |
91 called back with C{True} when the operation succeeds. | |
92 """ | |
93 return self._test(self.proto.add("foo", "bar"), | |
94 "add foo 0 0 3\r\nbar\r\n", "STORED\r\n", True) | |
95 | |
96 | |
97 def test_replace(self): | |
98 """ | |
99 L{MemCacheProtocol.replace} should return a L{Deferred} which | |
100 is called back with C{True} when the operation succeeds. | |
101 """ | |
102 return self._test(self.proto.replace("foo", "bar"), | |
103 "replace foo 0 0 3\r\nbar\r\n", "STORED\r\n", True) | |
104 | |
105 | |
106 def test_errorAdd(self): | |
107 """ | |
108 Test an erroneous add: if a L{MemCacheProtocol.add} is called but the | |
109 key already exists on the server, it returns a B{NOT STORED} answer, | |
110 which should callback the resulting L{Deferred} with C{False}. | |
111 """ | |
112 return self._test(self.proto.add("foo", "bar"), | |
113 "add foo 0 0 3\r\nbar\r\n", "NOT STORED\r\n", False) | |
114 | |
115 | |
116 def test_errorReplace(self): | |
117 """ | |
118 Test an erroneous replace: if a L{MemCacheProtocol.replace} is called | |
119 but the key doesn't exist on the server, it returns a B{NOT STORED} | |
120 answer, which should callback the resulting L{Deferred} with C{False}. | |
121 """ | |
122 return self._test(self.proto.replace("foo", "bar"), | |
123 "replace foo 0 0 3\r\nbar\r\n", "NOT STORED\r\n", False) | |
124 | |
125 | |
126 def test_delete(self): | |
127 """ | |
128 L{MemCacheProtocol.delete} should return a L{Deferred} which is | |
129 called back with C{True} when the server notifies a success. | |
130 """ | |
131 return self._test(self.proto.delete("bar"), "delete bar\r\n", | |
132 "DELETED\r\n", True) | |
133 | |
134 | |
135 def test_errorDelete(self): | |
136 """ | |
137 Test a error during a delete: if key doesn't exist on the server, it | |
138 returns a B{NOT FOUND} answer which should callback the resulting | |
139 L{Deferred} with C{False}. | |
140 """ | |
141 return self._test(self.proto.delete("bar"), "delete bar\r\n", | |
142 "NOT FOUND\r\n", False) | |
143 | |
144 | |
145 def test_increment(self): | |
146 """ | |
147 Test incrementing a variable: L{MemCacheProtocol.increment} should | |
148 return a L{Deferred} which is called back with the incremented value of | |
149 the given key. | |
150 """ | |
151 return self._test(self.proto.increment("foo"), "incr foo 1\r\n", | |
152 "4\r\n", 4) | |
153 | |
154 | |
155 def test_decrement(self): | |
156 """ | |
157 Test decrementing a variable: L{MemCacheProtocol.decrement} should | |
158 return a L{Deferred} which is called back with the decremented value of | |
159 the given key. | |
160 """ | |
161 return self._test( | |
162 self.proto.decrement("foo"), "decr foo 1\r\n", "5\r\n", 5) | |
163 | |
164 | |
165 def test_incrementVal(self): | |
166 """ | |
167 L{MemCacheProtocol.increment} takes an optional argument C{value} which | |
168 should replace the default value of 1 when specified. | |
169 """ | |
170 return self._test(self.proto.increment("foo", 8), "incr foo 8\r\n", | |
171 "4\r\n", 4) | |
172 | |
173 | |
174 def test_decrementVal(self): | |
175 """ | |
176 L{MemCacheProtocol.decrement} takes an optional argument C{value} which | |
177 should replace the default value of 1 when specified. | |
178 """ | |
179 return self._test(self.proto.decrement("foo", 3), "decr foo 3\r\n", | |
180 "5\r\n", 5) | |
181 | |
182 | |
183 def test_stats(self): | |
184 """ | |
185 Test retrieving server statistics via the L{MemCacheProtocol.stats} | |
186 command: it should parse the data sent by the server and call back the | |
187 resulting L{Deferred} with a dictionary of the received statistics. | |
188 """ | |
189 return self._test(self.proto.stats(), "stats\r\n", | |
190 "STAT foo bar\r\nSTAT egg spam\r\nEND\r\n", | |
191 {"foo": "bar", "egg": "spam"}) | |
192 | |
193 | |
194 def test_version(self): | |
195 """ | |
196 Test version retrieval via the L{MemCacheProtocol.version} command: it | |
197 should return a L{Deferred} which is called back with the version sent | |
198 by the server. | |
199 """ | |
200 return self._test(self.proto.version(), "version\r\n", | |
201 "VERSION 1.1\r\n", "1.1") | |
202 | |
203 | |
204 def test_flushAll(self): | |
205 """ | |
206 L{MemCacheProtocol.flushAll} should return a L{Deferred} which is | |
207 called back with C{True} if the server acknowledges success. | |
208 """ | |
209 return self._test(self.proto.flushAll(), "flush_all\r\n", | |
210 "OK\r\n", True) | |
211 | |
212 | |
213 def test_invalidGetResponse(self): | |
214 """ | |
215 If the value returned doesn't match the expected key of the current, we | |
216 should get an error in L{MemCacheProtocol.dataReceived}. | |
217 """ | |
218 self.proto.get("foo") | |
219 s = "spamegg" | |
220 self.assertRaises(RuntimeError, | |
221 self.proto.dataReceived, | |
222 "VALUE bar 0 %s\r\n%s\r\nEND\r\n" % (len(s), s)) | |
223 | |
224 | |
225 def test_timeOut(self): | |
226 """ | |
227 Test the timeout on outgoing requests: when timeout is detected, all | |
228 current commands should fail with a L{TimeoutError}, and the | |
229 connection should be closed. | |
230 """ | |
231 d1 = self.proto.get("foo") | |
232 d2 = self.proto.get("bar") | |
233 d3 = Deferred() | |
234 self.proto.connectionLost = d3.callback | |
235 | |
236 self.clock.advance(self.proto.persistentTimeOut) | |
237 self.assertFailure(d1, TimeoutError) | |
238 self.assertFailure(d2, TimeoutError) | |
239 def checkMessage(error): | |
240 self.assertEquals(str(error), "Connection timeout") | |
241 d1.addCallback(checkMessage) | |
242 return gatherResults([d1, d2, d3]) | |
243 | |
244 | |
245 def test_timeoutRemoved(self): | |
246 """ | |
247 When a request gets a response, no pending timeout call should remain | |
248 around. | |
249 """ | |
250 d = self.proto.get("foo") | |
251 | |
252 self.clock.advance(self.proto.persistentTimeOut - 1) | |
253 self.proto.dataReceived("VALUE foo 0 3\r\nbar\r\nEND\r\n") | |
254 | |
255 def check(result): | |
256 self.assertEquals(result, (0, "bar")) | |
257 self.assertEquals(len(self.clock.calls), 0) | |
258 d.addCallback(check) | |
259 return d | |
260 | |
261 | |
262 def test_timeOutRaw(self): | |
263 """ | |
264 Test the timeout when raw mode was started: the timeout should not be | |
265 reset until all the data has been received, so we can have a | |
266 L{TimeoutError} when waiting for raw data. | |
267 """ | |
268 d1 = self.proto.get("foo") | |
269 d2 = Deferred() | |
270 self.proto.connectionLost = d2.callback | |
271 | |
272 self.proto.dataReceived("VALUE foo 0 10\r\n12345") | |
273 self.clock.advance(self.proto.persistentTimeOut) | |
274 self.assertFailure(d1, TimeoutError) | |
275 return gatherResults([d1, d2]) | |
276 | |
277 | |
278 def test_timeOutStat(self): | |
279 """ | |
280 Test the timeout when stat command has started: the timeout should not | |
281 be reset until the final B{END} is received. | |
282 """ | |
283 d1 = self.proto.stats() | |
284 d2 = Deferred() | |
285 self.proto.connectionLost = d2.callback | |
286 | |
287 self.proto.dataReceived("STAT foo bar\r\n") | |
288 self.clock.advance(self.proto.persistentTimeOut) | |
289 self.assertFailure(d1, TimeoutError) | |
290 return gatherResults([d1, d2]) | |
291 | |
292 | |
293 def test_timeoutPipelining(self): | |
294 """ | |
295 When two requests are sent, a timeout call should remain around for the | |
296 second request, and its timeout time should be correct. | |
297 """ | |
298 d1 = self.proto.get("foo") | |
299 d2 = self.proto.get("bar") | |
300 d3 = Deferred() | |
301 self.proto.connectionLost = d3.callback | |
302 | |
303 self.clock.advance(self.proto.persistentTimeOut - 1) | |
304 self.proto.dataReceived("VALUE foo 0 3\r\nbar\r\nEND\r\n") | |
305 | |
306 def check(result): | |
307 self.assertEquals(result, (0, "bar")) | |
308 self.assertEquals(len(self.clock.calls), 1) | |
309 for i in range(self.proto.persistentTimeOut): | |
310 self.clock.advance(1) | |
311 return self.assertFailure(d2, TimeoutError).addCallback(checkTime) | |
312 def checkTime(ignored): | |
313 # Check that the timeout happened C{self.proto.persistentTimeOut} | |
314 # after the last response | |
315 self.assertEquals(self.clock.seconds(), | |
316 2 * self.proto.persistentTimeOut - 1) | |
317 d1.addCallback(check) | |
318 return d1 | |
319 | |
320 | |
321 def test_timeoutNotReset(self): | |
322 """ | |
323 Check that timeout is not resetted for every command, but keep the | |
324 timeout from the first command without response. | |
325 """ | |
326 d1 = self.proto.get("foo") | |
327 d3 = Deferred() | |
328 self.proto.connectionLost = d3.callback | |
329 | |
330 self.clock.advance(self.proto.persistentTimeOut - 1) | |
331 d2 = self.proto.get("bar") | |
332 self.clock.advance(1) | |
333 self.assertFailure(d1, TimeoutError) | |
334 self.assertFailure(d2, TimeoutError) | |
335 return gatherResults([d1, d2, d3]) | |
336 | |
337 | |
338 def test_tooLongKey(self): | |
339 """ | |
340 Test that an error is raised when trying to use a too long key: the | |
341 called command should return a L{Deferred} which fail with a | |
342 L{ClientError}. | |
343 """ | |
344 d1 = self.assertFailure(self.proto.set("a" * 500, "bar"), ClientError) | |
345 d2 = self.assertFailure(self.proto.increment("a" * 500), ClientError) | |
346 d3 = self.assertFailure(self.proto.get("a" * 500), ClientError) | |
347 d4 = self.assertFailure(self.proto.append("a" * 500, "bar"), ClientError
) | |
348 d5 = self.assertFailure(self.proto.prepend("a" * 500, "bar"), ClientErro
r) | |
349 return gatherResults([d1, d2, d3, d4, d5]) | |
350 | |
351 | |
352 def test_invalidCommand(self): | |
353 """ | |
354 When an unknown command is sent directly (not through public API), the | |
355 server answers with an B{ERROR} token, and the command should fail with | |
356 L{NoSuchCommand}. | |
357 """ | |
358 d = self.proto._set("egg", "foo", "bar", 0, 0, "") | |
359 self.assertEquals(self.transport.value(), "egg foo 0 0 3\r\nbar\r\n") | |
360 self.assertFailure(d, NoSuchCommand) | |
361 self.proto.dataReceived("ERROR\r\n") | |
362 return d | |
363 | |
364 | |
365 def test_clientError(self): | |
366 """ | |
367 Test the L{ClientError} error: when the server send a B{CLIENT_ERROR} | |
368 token, the originating command should fail with L{ClientError}, and the | |
369 error should contain the text sent by the server. | |
370 """ | |
371 a = "eggspamm" | |
372 d = self.proto.set("foo", a) | |
373 self.assertEquals(self.transport.value(), | |
374 "set foo 0 0 8\r\neggspamm\r\n") | |
375 self.assertFailure(d, ClientError) | |
376 def check(err): | |
377 self.assertEquals(str(err), "We don't like egg and spam") | |
378 d.addCallback(check) | |
379 self.proto.dataReceived("CLIENT_ERROR We don't like egg and spam\r\n") | |
380 return d | |
381 | |
382 | |
383 def test_serverError(self): | |
384 """ | |
385 Test the L{ServerError} error: when the server send a B{SERVER_ERROR} | |
386 token, the originating command should fail with L{ServerError}, and the | |
387 error should contain the text sent by the server. | |
388 """ | |
389 a = "eggspamm" | |
390 d = self.proto.set("foo", a) | |
391 self.assertEquals(self.transport.value(), | |
392 "set foo 0 0 8\r\neggspamm\r\n") | |
393 self.assertFailure(d, ServerError) | |
394 def check(err): | |
395 self.assertEquals(str(err), "zomg") | |
396 d.addCallback(check) | |
397 self.proto.dataReceived("SERVER_ERROR zomg\r\n") | |
398 return d | |
399 | |
400 | |
401 def test_unicodeKey(self): | |
402 """ | |
403 Using a non-string key as argument to commands should raise an error. | |
404 """ | |
405 d1 = self.assertFailure(self.proto.set(u"foo", "bar"), ClientError) | |
406 d2 = self.assertFailure(self.proto.increment(u"egg"), ClientError) | |
407 d3 = self.assertFailure(self.proto.get(1), ClientError) | |
408 d4 = self.assertFailure(self.proto.delete(u"bar"), ClientError) | |
409 d5 = self.assertFailure(self.proto.append(u"foo", "bar"), ClientError) | |
410 d6 = self.assertFailure(self.proto.prepend(u"foo", "bar"), ClientError) | |
411 return gatherResults([d1, d2, d3, d4, d5, d6]) | |
412 | |
413 | |
414 def test_unicodeValue(self): | |
415 """ | |
416 Using a non-string value should raise an error. | |
417 """ | |
418 return self.assertFailure(self.proto.set("foo", u"bar"), ClientError) | |
419 | |
420 | |
421 def test_pipelining(self): | |
422 """ | |
423 Test that multiple requests can be sent subsequently to the server, and | |
424 that the protocol order the responses correctly and dispatch to the | |
425 corresponding client command. | |
426 """ | |
427 d1 = self.proto.get("foo") | |
428 d1.addCallback(self.assertEquals, (0, "bar")) | |
429 d2 = self.proto.set("bar", "spamspamspam") | |
430 d2.addCallback(self.assertEquals, True) | |
431 d3 = self.proto.get("egg") | |
432 d3.addCallback(self.assertEquals, (0, "spam")) | |
433 self.assertEquals(self.transport.value(), | |
434 "get foo\r\nset bar 0 0 12\r\nspamspamspam\r\nget egg\r\n") | |
435 self.proto.dataReceived("VALUE foo 0 3\r\nbar\r\nEND\r\n" | |
436 "STORED\r\n" | |
437 "VALUE egg 0 4\r\nspam\r\nEND\r\n") | |
438 return gatherResults([d1, d2, d3]) | |
439 | |
440 | |
441 def test_getInChunks(self): | |
442 """ | |
443 If the value retrieved by a C{get} arrive in chunks, the protocol | |
444 should be able to reconstruct it and to produce the good value. | |
445 """ | |
446 d = self.proto.get("foo") | |
447 d.addCallback(self.assertEquals, (0, "0123456789")) | |
448 self.assertEquals(self.transport.value(), "get foo\r\n") | |
449 self.proto.dataReceived("VALUE foo 0 10\r\n0123456") | |
450 self.proto.dataReceived("789") | |
451 self.proto.dataReceived("\r\nEND") | |
452 self.proto.dataReceived("\r\n") | |
453 return d | |
454 | |
455 | |
456 def test_append(self): | |
457 """ | |
458 L{MemCacheProtocol.append} behaves like a L{MemCacheProtocol.set} | |
459 method: it should return a L{Deferred} which is called back with | |
460 C{True} when the operation succeeds. | |
461 """ | |
462 return self._test(self.proto.append("foo", "bar"), | |
463 "append foo 0 0 3\r\nbar\r\n", "STORED\r\n", True) | |
464 | |
465 | |
466 def test_prepend(self): | |
467 """ | |
468 L{MemCacheProtocol.prepend} behaves like a L{MemCacheProtocol.set} | |
469 method: it should return a L{Deferred} which is called back with | |
470 C{True} when the operation succeeds. | |
471 """ | |
472 return self._test(self.proto.prepend("foo", "bar"), | |
473 "prepend foo 0 0 3\r\nbar\r\n", "STORED\r\n", True) | |
474 | |
475 | |
476 def test_gets(self): | |
477 """ | |
478 L{MemCacheProtocol.get} should handle an additional cas result when | |
479 C{withIdentifier} is C{True} and forward it in the resulting | |
480 L{Deferred}. | |
481 """ | |
482 return self._test(self.proto.get("foo", True), "gets foo\r\n", | |
483 "VALUE foo 0 3 1234\r\nbar\r\nEND\r\n", (0, "1234", "bar")) | |
484 | |
485 | |
486 def test_emptyGets(self): | |
487 """ | |
488 Test getting a non-available key with gets: it should succeed but | |
489 return C{None} as value, C{0} as flag and an empty cas value. | |
490 """ | |
491 return self._test(self.proto.get("foo", True), "gets foo\r\n", | |
492 "END\r\n", (0, "", None)) | |
493 | |
494 | |
495 def test_checkAndSet(self): | |
496 """ | |
497 L{MemCacheProtocol.checkAndSet} passes an additional cas identifier that
the | |
498 server should handle to check if the data has to be updated. | |
499 """ | |
500 return self._test(self.proto.checkAndSet("foo", "bar", cas="1234"), | |
501 "cas foo 0 0 3 1234\r\nbar\r\n", "STORED\r\n", True) | |
502 | |
503 | |
504 def test_casUnknowKey(self): | |
505 """ | |
506 When L{MemCacheProtocol.checkAndSet} response is C{EXISTS}, the resultin
g | |
507 L{Deferred} should fire with C{False}. | |
508 """ | |
509 return self._test(self.proto.checkAndSet("foo", "bar", cas="1234"), | |
510 "cas foo 0 0 3 1234\r\nbar\r\n", "EXISTS\r\n", False) | |
OLD | NEW |