| 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 |