| OLD | NEW |
| (Empty) |
| 1 # Copyright (c) 2001-2008 Twisted Matrix Laboratories. | |
| 2 # See LICENSE for details. | |
| 3 | |
| 4 | |
| 5 """ | |
| 6 Test cases for L{jelly} object serialization. | |
| 7 """ | |
| 8 | |
| 9 import datetime | |
| 10 | |
| 11 try: | |
| 12 import decimal | |
| 13 except ImportError: | |
| 14 decimal = None | |
| 15 | |
| 16 from twisted.spread import jelly, pb | |
| 17 from twisted.python.compat import set, frozenset | |
| 18 | |
| 19 from twisted.trial import unittest | |
| 20 | |
| 21 | |
| 22 | |
| 23 class TestNode(object, jelly.Jellyable): | |
| 24 """ | |
| 25 An object to test jellyfying of new style class instances. | |
| 26 """ | |
| 27 classAttr = 4 | |
| 28 | |
| 29 def __init__(self, parent=None): | |
| 30 if parent: | |
| 31 self.id = parent.id + 1 | |
| 32 parent.children.append(self) | |
| 33 else: | |
| 34 self.id = 1 | |
| 35 self.parent = parent | |
| 36 self.children = [] | |
| 37 | |
| 38 | |
| 39 | |
| 40 class A: | |
| 41 """ | |
| 42 Dummy class. | |
| 43 """ | |
| 44 | |
| 45 def amethod(self): | |
| 46 """ | |
| 47 Method tp be used in serialization tests. | |
| 48 """ | |
| 49 | |
| 50 | |
| 51 | |
| 52 def afunc(self): | |
| 53 """ | |
| 54 A dummy function to test function serialization. | |
| 55 """ | |
| 56 | |
| 57 | |
| 58 | |
| 59 class B: | |
| 60 """ | |
| 61 Dummy class. | |
| 62 """ | |
| 63 | |
| 64 def bmethod(self): | |
| 65 """ | |
| 66 Method to be used in serialization tests. | |
| 67 """ | |
| 68 | |
| 69 | |
| 70 | |
| 71 class C: | |
| 72 """ | |
| 73 Dummy class. | |
| 74 """ | |
| 75 | |
| 76 def cmethod(self): | |
| 77 """ | |
| 78 Method to be used in serialization tests. | |
| 79 """ | |
| 80 | |
| 81 | |
| 82 | |
| 83 class D(object): | |
| 84 """ | |
| 85 Dummy new-style class. | |
| 86 """ | |
| 87 | |
| 88 | |
| 89 | |
| 90 class SimpleJellyTest: | |
| 91 def __init__(self, x, y): | |
| 92 self.x = x | |
| 93 self.y = y | |
| 94 | |
| 95 def isTheSameAs(self, other): | |
| 96 return self.__dict__ == other.__dict__ | |
| 97 | |
| 98 | |
| 99 | |
| 100 class JellyTestCase(unittest.TestCase): | |
| 101 """ | |
| 102 Testcases for L{jelly} module serialization. | |
| 103 | |
| 104 @cvar decimalData: serialized version of decimal data, to be used in tests. | |
| 105 @type decimalData: C{list} | |
| 106 """ | |
| 107 | |
| 108 def _testSecurity(self, inputList, atom): | |
| 109 """ | |
| 110 Helper test method to test security options for a type. | |
| 111 | |
| 112 @param inputList: a sample input for the type. | |
| 113 @param inputList: C{list} | |
| 114 | |
| 115 @param atom: atom identifier for the type. | |
| 116 @type atom: C{str} | |
| 117 """ | |
| 118 c = jelly.jelly(inputList) | |
| 119 taster = jelly.SecurityOptions() | |
| 120 taster.allowBasicTypes() | |
| 121 # By default, it should succeed | |
| 122 jelly.unjelly(c, taster) | |
| 123 taster.allowedTypes.pop(atom) | |
| 124 # But it should raise an exception when disallowed | |
| 125 self.assertRaises(jelly.InsecureJelly, jelly.unjelly, c, taster) | |
| 126 | |
| 127 | |
| 128 def test_methodSelfIdentity(self): | |
| 129 a = A() | |
| 130 b = B() | |
| 131 a.bmethod = b.bmethod | |
| 132 b.a = a | |
| 133 im_ = jelly.unjelly(jelly.jelly(b)).a.bmethod | |
| 134 self.assertEquals(im_.im_class, im_.im_self.__class__) | |
| 135 | |
| 136 | |
| 137 def test_methodsNotSelfIdentity(self): | |
| 138 """ | |
| 139 If a class change after an instance has been created, L{jelly.unjelly} | |
| 140 shoud raise a C{TypeError} when trying to unjelly the instance. | |
| 141 """ | |
| 142 a = A() | |
| 143 b = B() | |
| 144 c = C() | |
| 145 a.bmethod = c.cmethod | |
| 146 b.a = a | |
| 147 savecmethod = C.cmethod | |
| 148 del C.cmethod | |
| 149 try: | |
| 150 self.assertRaises(TypeError, jelly.unjelly, jelly.jelly(b)) | |
| 151 finally: | |
| 152 C.cmethod = savecmethod | |
| 153 | |
| 154 | |
| 155 def test_newStyle(self): | |
| 156 n = D() | |
| 157 n.x = 1 | |
| 158 n2 = D() | |
| 159 n.n2 = n2 | |
| 160 n.n3 = n2 | |
| 161 c = jelly.jelly(n) | |
| 162 m = jelly.unjelly(c) | |
| 163 self.assertIsInstance(m, D) | |
| 164 self.assertIdentical(m.n2, m.n3) | |
| 165 | |
| 166 | |
| 167 def test_dateTime(self): | |
| 168 dtn = datetime.datetime.now() | |
| 169 dtd = datetime.datetime.now() - dtn | |
| 170 input = [dtn, dtd] | |
| 171 c = jelly.jelly(input) | |
| 172 output = jelly.unjelly(c) | |
| 173 self.assertEquals(input, output) | |
| 174 self.assertNotIdentical(input, output) | |
| 175 | |
| 176 | |
| 177 def test_decimal(self): | |
| 178 """ | |
| 179 Jellying L{decimal.Decimal} instances and then unjellying the result | |
| 180 should produce objects which represent the values of the original | |
| 181 inputs. | |
| 182 """ | |
| 183 inputList = [decimal.Decimal('9.95'), | |
| 184 decimal.Decimal(0), | |
| 185 decimal.Decimal(123456), | |
| 186 decimal.Decimal('-78.901')] | |
| 187 c = jelly.jelly(inputList) | |
| 188 output = jelly.unjelly(c) | |
| 189 self.assertEquals(inputList, output) | |
| 190 self.assertNotIdentical(inputList, output) | |
| 191 | |
| 192 | |
| 193 decimalData = ['list', ['decimal', 995, -2], ['decimal', 0, 0], | |
| 194 ['decimal', 123456, 0], ['decimal', -78901, -3]] | |
| 195 | |
| 196 | |
| 197 def test_decimalUnjelly(self): | |
| 198 """ | |
| 199 Unjellying the s-expressions produced by jelly for L{decimal.Decimal} | |
| 200 instances should result in L{decimal.Decimal} instances with the values | |
| 201 represented by the s-expressions. | |
| 202 | |
| 203 This test also verifies that C{self.decimalData} contains valid jellied | |
| 204 data. This is important since L{test_decimalMissing} re-uses | |
| 205 C{self.decimalData} and is expected to be unable to produce | |
| 206 L{decimal.Decimal} instances even though the s-expression correctly | |
| 207 represents a list of them. | |
| 208 """ | |
| 209 expected = [decimal.Decimal('9.95'), | |
| 210 decimal.Decimal(0), | |
| 211 decimal.Decimal(123456), | |
| 212 decimal.Decimal('-78.901')] | |
| 213 output = jelly.unjelly(self.decimalData) | |
| 214 self.assertEquals(output, expected) | |
| 215 | |
| 216 | |
| 217 def test_decimalMissing(self): | |
| 218 """ | |
| 219 If decimal is unavailable on the unjelly side, L{jelly.unjelly} should | |
| 220 gracefully return L{jelly.Unpersistable} objects. | |
| 221 """ | |
| 222 self.patch(jelly, 'decimal', None) | |
| 223 output = jelly.unjelly(self.decimalData) | |
| 224 self.assertEquals(len(output), 4) | |
| 225 for i in range(4): | |
| 226 self.assertIsInstance(output[i], jelly.Unpersistable) | |
| 227 self.assertEquals(output[0].reason, | |
| 228 "Could not unpersist decimal: 9.95") | |
| 229 self.assertEquals(output[1].reason, | |
| 230 "Could not unpersist decimal: 0") | |
| 231 self.assertEquals(output[2].reason, | |
| 232 "Could not unpersist decimal: 123456") | |
| 233 self.assertEquals(output[3].reason, | |
| 234 "Could not unpersist decimal: -78.901") | |
| 235 | |
| 236 | |
| 237 def test_decimalSecurity(self): | |
| 238 """ | |
| 239 By default, C{decimal} objects should be allowed by | |
| 240 L{jelly.SecurityOptions}. If not allowed, L{jelly.unjelly} should raise | |
| 241 L{jelly.InsecureJelly} when trying to unjelly it. | |
| 242 """ | |
| 243 inputList = [decimal.Decimal('9.95')] | |
| 244 self._testSecurity(inputList, "decimal") | |
| 245 | |
| 246 if decimal is None: | |
| 247 skipReason = "decimal not available" | |
| 248 test_decimal.skip = skipReason | |
| 249 test_decimalUnjelly.skip = skipReason | |
| 250 test_decimalSecurity.skip = skipReason | |
| 251 | |
| 252 | |
| 253 def test_set(self): | |
| 254 """ | |
| 255 Jellying C{set} instances and then unjellying the result | |
| 256 should produce objects which represent the values of the original | |
| 257 inputs. | |
| 258 """ | |
| 259 inputList = [set([1, 2, 3])] | |
| 260 output = jelly.unjelly(jelly.jelly(inputList)) | |
| 261 self.assertEquals(inputList, output) | |
| 262 self.assertNotIdentical(inputList, output) | |
| 263 | |
| 264 | |
| 265 def test_frozenset(self): | |
| 266 """ | |
| 267 Jellying C{frozenset} instances and then unjellying the result | |
| 268 should produce objects which represent the values of the original | |
| 269 inputs. | |
| 270 """ | |
| 271 inputList = [frozenset([1, 2, 3])] | |
| 272 output = jelly.unjelly(jelly.jelly(inputList)) | |
| 273 self.assertEquals(inputList, output) | |
| 274 self.assertNotIdentical(inputList, output) | |
| 275 | |
| 276 | |
| 277 def test_setSecurity(self): | |
| 278 """ | |
| 279 By default, C{set} objects should be allowed by | |
| 280 L{jelly.SecurityOptions}. If not allowed, L{jelly.unjelly} should raise | |
| 281 L{jelly.InsecureJelly} when trying to unjelly it. | |
| 282 """ | |
| 283 inputList = [set([1, 2, 3])] | |
| 284 self._testSecurity(inputList, "set") | |
| 285 | |
| 286 | |
| 287 def test_frozensetSecurity(self): | |
| 288 """ | |
| 289 By default, C{frozenset} objects should be allowed by | |
| 290 L{jelly.SecurityOptions}. If not allowed, L{jelly.unjelly} should raise | |
| 291 L{jelly.InsecureJelly} when trying to unjelly it. | |
| 292 """ | |
| 293 inputList = [frozenset([1, 2, 3])] | |
| 294 self._testSecurity(inputList, "frozenset") | |
| 295 | |
| 296 | |
| 297 def test_oldSets(self): | |
| 298 """ | |
| 299 Test jellying C{sets.Set}: it should serialize to the same thing as | |
| 300 C{set} jelly, and be unjellied as C{set} if available. | |
| 301 """ | |
| 302 inputList = [jelly._sets.Set([1, 2, 3])] | |
| 303 inputJelly = jelly.jelly(inputList) | |
| 304 self.assertEquals(inputJelly, jelly.jelly([set([1, 2, 3])])) | |
| 305 output = jelly.unjelly(inputJelly) | |
| 306 # Even if the class is different, it should coerce to the same list | |
| 307 self.assertEquals(list(inputList[0]), list(output[0])) | |
| 308 if set is jelly._sets.Set: | |
| 309 self.assertIsInstance(output[0], jelly._sets.Set) | |
| 310 else: | |
| 311 self.assertIsInstance(output[0], set) | |
| 312 | |
| 313 | |
| 314 def test_oldImmutableSets(self): | |
| 315 """ | |
| 316 Test jellying C{sets.ImmutableSet}: it should serialize to the same | |
| 317 thing as C{frozenset} jelly, and be unjellied as C{frozenset} if | |
| 318 available. | |
| 319 """ | |
| 320 inputList = [jelly._sets.ImmutableSet([1, 2, 3])] | |
| 321 inputJelly = jelly.jelly(inputList) | |
| 322 self.assertEquals(inputJelly, jelly.jelly([frozenset([1, 2, 3])])) | |
| 323 output = jelly.unjelly(inputJelly) | |
| 324 # Even if the class is different, it should coerce to the same list | |
| 325 self.assertEquals(list(inputList[0]), list(output[0])) | |
| 326 if frozenset is jelly._sets.ImmutableSet: | |
| 327 self.assertIsInstance(output[0], jelly._sets.ImmutableSet) | |
| 328 else: | |
| 329 self.assertIsInstance(output[0], frozenset) | |
| 330 | |
| 331 | |
| 332 def test_simple(self): | |
| 333 """ | |
| 334 Simplest test case. | |
| 335 """ | |
| 336 self.failUnless(SimpleJellyTest('a', 'b').isTheSameAs( | |
| 337 SimpleJellyTest('a', 'b'))) | |
| 338 a = SimpleJellyTest(1, 2) | |
| 339 cereal = jelly.jelly(a) | |
| 340 b = jelly.unjelly(cereal) | |
| 341 self.failUnless(a.isTheSameAs(b)) | |
| 342 | |
| 343 | |
| 344 def test_identity(self): | |
| 345 """ | |
| 346 Test to make sure that objects retain identity properly. | |
| 347 """ | |
| 348 x = [] | |
| 349 y = (x) | |
| 350 x.append(y) | |
| 351 x.append(y) | |
| 352 self.assertIdentical(x[0], x[1]) | |
| 353 self.assertIdentical(x[0][0], x) | |
| 354 s = jelly.jelly(x) | |
| 355 z = jelly.unjelly(s) | |
| 356 self.assertIdentical(z[0], z[1]) | |
| 357 self.assertIdentical(z[0][0], z) | |
| 358 | |
| 359 | |
| 360 def test_unicode(self): | |
| 361 x = unicode('blah') | |
| 362 y = jelly.unjelly(jelly.jelly(x)) | |
| 363 self.assertEquals(x, y) | |
| 364 self.assertEquals(type(x), type(y)) | |
| 365 | |
| 366 | |
| 367 def test_stressReferences(self): | |
| 368 reref = [] | |
| 369 toplevelTuple = ({'list': reref}, reref) | |
| 370 reref.append(toplevelTuple) | |
| 371 s = jelly.jelly(toplevelTuple) | |
| 372 z = jelly.unjelly(s) | |
| 373 self.assertIdentical(z[0]['list'], z[1]) | |
| 374 self.assertIdentical(z[0]['list'][0], z) | |
| 375 | |
| 376 | |
| 377 def test_moreReferences(self): | |
| 378 a = [] | |
| 379 t = (a,) | |
| 380 a.append((t,)) | |
| 381 s = jelly.jelly(t) | |
| 382 z = jelly.unjelly(s) | |
| 383 self.assertIdentical(z[0][0][0], z) | |
| 384 | |
| 385 | |
| 386 def test_typeSecurity(self): | |
| 387 """ | |
| 388 Test for type-level security of serialization. | |
| 389 """ | |
| 390 taster = jelly.SecurityOptions() | |
| 391 dct = jelly.jelly({}) | |
| 392 self.assertRaises(jelly.InsecureJelly, jelly.unjelly, dct, taster) | |
| 393 | |
| 394 | |
| 395 def test_newStyleClasses(self): | |
| 396 j = jelly.jelly(D) | |
| 397 uj = jelly.unjelly(D) | |
| 398 self.assertIdentical(D, uj) | |
| 399 | |
| 400 | |
| 401 def test_lotsaTypes(self): | |
| 402 """ | |
| 403 Test for all types currently supported in jelly | |
| 404 """ | |
| 405 a = A() | |
| 406 jelly.unjelly(jelly.jelly(a)) | |
| 407 jelly.unjelly(jelly.jelly(a.amethod)) | |
| 408 items = [afunc, [1, 2, 3], not bool(1), bool(1), 'test', 20.3, | |
| 409 (1, 2, 3), None, A, unittest, {'a': 1}, A.amethod] | |
| 410 for i in items: | |
| 411 self.assertEquals(i, jelly.unjelly(jelly.jelly(i))) | |
| 412 | |
| 413 | |
| 414 def test_setState(self): | |
| 415 global TupleState | |
| 416 class TupleState: | |
| 417 def __init__(self, other): | |
| 418 self.other = other | |
| 419 def __getstate__(self): | |
| 420 return (self.other,) | |
| 421 def __setstate__(self, state): | |
| 422 self.other = state[0] | |
| 423 def __hash__(self): | |
| 424 return hash(self.other) | |
| 425 a = A() | |
| 426 t1 = TupleState(a) | |
| 427 t2 = TupleState(a) | |
| 428 t3 = TupleState((t1, t2)) | |
| 429 d = {t1: t1, t2: t2, t3: t3, "t3": t3} | |
| 430 t3prime = jelly.unjelly(jelly.jelly(d))["t3"] | |
| 431 self.assertIdentical(t3prime.other[0].other, t3prime.other[1].other) | |
| 432 | |
| 433 | |
| 434 def test_classSecurity(self): | |
| 435 """ | |
| 436 Test for class-level security of serialization. | |
| 437 """ | |
| 438 taster = jelly.SecurityOptions() | |
| 439 taster.allowInstancesOf(A, B) | |
| 440 a = A() | |
| 441 b = B() | |
| 442 c = C() | |
| 443 # add a little complexity to the data | |
| 444 a.b = b | |
| 445 a.c = c | |
| 446 # and a backreference | |
| 447 a.x = b | |
| 448 b.c = c | |
| 449 # first, a friendly insecure serialization | |
| 450 friendly = jelly.jelly(a, taster) | |
| 451 x = jelly.unjelly(friendly, taster) | |
| 452 self.assertIsInstance(x.c, jelly.Unpersistable) | |
| 453 # now, a malicious one | |
| 454 mean = jelly.jelly(a) | |
| 455 self.assertRaises(jelly.InsecureJelly, jelly.unjelly, mean, taster) | |
| 456 self.assertIdentical(x.x, x.b, "Identity mismatch") | |
| 457 # test class serialization | |
| 458 friendly = jelly.jelly(A, taster) | |
| 459 x = jelly.unjelly(friendly, taster) | |
| 460 self.assertIdentical(x, A, "A came back: %s" % x) | |
| 461 | |
| 462 | |
| 463 def test_unjellyable(self): | |
| 464 """ | |
| 465 Test that if Unjellyable is used to deserialize a jellied object, | |
| 466 state comes out right. | |
| 467 """ | |
| 468 class JellyableTestClass(jelly.Jellyable): | |
| 469 pass | |
| 470 jelly.setUnjellyableForClass(JellyableTestClass, jelly.Unjellyable) | |
| 471 input = JellyableTestClass() | |
| 472 input.attribute = 'value' | |
| 473 output = jelly.unjelly(jelly.jelly(input)) | |
| 474 self.assertEquals(output.attribute, 'value') | |
| 475 self.assertIsInstance(output, jelly.Unjellyable) | |
| 476 | |
| 477 | |
| 478 def test_persistentStorage(self): | |
| 479 perst = [{}, 1] | |
| 480 def persistentStore(obj, jel, perst = perst): | |
| 481 perst[1] = perst[1] + 1 | |
| 482 perst[0][perst[1]] = obj | |
| 483 return str(perst[1]) | |
| 484 | |
| 485 def persistentLoad(pidstr, unj, perst = perst): | |
| 486 pid = int(pidstr) | |
| 487 return perst[0][pid] | |
| 488 | |
| 489 a = SimpleJellyTest(1, 2) | |
| 490 b = SimpleJellyTest(3, 4) | |
| 491 c = SimpleJellyTest(5, 6) | |
| 492 | |
| 493 a.b = b | |
| 494 a.c = c | |
| 495 c.b = b | |
| 496 | |
| 497 jel = jelly.jelly(a, persistentStore = persistentStore) | |
| 498 x = jelly.unjelly(jel, persistentLoad = persistentLoad) | |
| 499 | |
| 500 self.assertIdentical(x.b, x.c.b) | |
| 501 self.failUnless(perst[0], "persistentStore was not called.") | |
| 502 self.assertIdentical(x.b, a.b, "Persistent storage identity failure.") | |
| 503 | |
| 504 | |
| 505 def test_newStyleClassesAttributes(self): | |
| 506 n = TestNode() | |
| 507 n1 = TestNode(n) | |
| 508 n11 = TestNode(n1) | |
| 509 n2 = TestNode(n) | |
| 510 # Jelly it | |
| 511 jel = jelly.jelly(n) | |
| 512 m = jelly.unjelly(jel) | |
| 513 # Check that it has been restored ok | |
| 514 self._check_newstyle(n, m) | |
| 515 | |
| 516 | |
| 517 def _check_newstyle(self, a, b): | |
| 518 self.assertEqual(a.id, b.id) | |
| 519 self.assertEqual(a.classAttr, 4) | |
| 520 self.assertEqual(b.classAttr, 4) | |
| 521 self.assertEqual(len(a.children), len(b.children)) | |
| 522 for x, y in zip(a.children, b.children): | |
| 523 self._check_newstyle(x, y) | |
| 524 | |
| 525 | |
| 526 | |
| 527 class ClassA(pb.Copyable, pb.RemoteCopy): | |
| 528 def __init__(self): | |
| 529 self.ref = ClassB(self) | |
| 530 | |
| 531 | |
| 532 | |
| 533 class ClassB(pb.Copyable, pb.RemoteCopy): | |
| 534 def __init__(self, ref): | |
| 535 self.ref = ref | |
| 536 | |
| 537 | |
| 538 | |
| 539 class CircularReferenceTestCase(unittest.TestCase): | |
| 540 """ | |
| 541 Tests for circular references handling in the jelly/unjelly process. | |
| 542 """ | |
| 543 | |
| 544 def test_simpleCircle(self): | |
| 545 jelly.setUnjellyableForClass(ClassA, ClassA) | |
| 546 jelly.setUnjellyableForClass(ClassB, ClassB) | |
| 547 a = jelly.unjelly(jelly.jelly(ClassA())) | |
| 548 self.assertIdentical(a.ref.ref, a, | |
| 549 "Identity not preserved in circular reference") | |
| 550 | |
| 551 | |
| 552 def test_circleWithInvoker(self): | |
| 553 class DummyInvokerClass: | |
| 554 pass | |
| 555 dummyInvoker = DummyInvokerClass() | |
| 556 dummyInvoker.serializingPerspective = None | |
| 557 a0 = ClassA() | |
| 558 jelly.setUnjellyableForClass(ClassA, ClassA) | |
| 559 jelly.setUnjellyableForClass(ClassB, ClassB) | |
| 560 j = jelly.jelly(a0, invoker=dummyInvoker) | |
| 561 a1 = jelly.unjelly(j) | |
| 562 self.failUnlessIdentical(a1.ref.ref, a1, | |
| 563 "Identity not preserved in circular reference") | |
| 564 | |
| 565 | |
| 566 def test_set(self): | |
| 567 """ | |
| 568 Check that a C{set} can contain a circular reference and be serialized | |
| 569 and unserialized without losing the reference. | |
| 570 """ | |
| 571 s = set() | |
| 572 a = SimpleJellyTest(s, None) | |
| 573 s.add(a) | |
| 574 res = jelly.unjelly(jelly.jelly(a)) | |
| 575 self.assertIsInstance(res.x, set) | |
| 576 self.assertEquals(list(res.x), [res]) | |
| 577 | |
| 578 | |
| 579 def test_frozenset(self): | |
| 580 """ | |
| 581 Check that a C{frozenset} can contain a circular reference and be | |
| 582 serializeserialized without losing the reference. | |
| 583 """ | |
| 584 a = SimpleJellyTest(None, None) | |
| 585 s = frozenset([a]) | |
| 586 a.x = s | |
| 587 res = jelly.unjelly(jelly.jelly(a)) | |
| 588 self.assertIsInstance(res.x, frozenset) | |
| 589 self.assertEquals(list(res.x), [res]) | |
| OLD | NEW |