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