1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
5 Test cases for L{jelly} object serialization.
15 from twisted.spread import jelly, pb
16 from twisted.python.compat import set, frozenset
17 from twisted.trial import unittest
18 from twisted.test.proto_helpers import StringTransport
21 class TestNode(object, jelly.Jellyable):
23 An object to test jellyfying of new style class instances.
27 def __init__(self, parent=None):
29 self.id = parent.id + 1
30 parent.children.append(self)
45 Method tp be used in serialization tests.
52 A dummy function to test function serialization.
64 Method to be used in serialization tests.
76 Method to be used in serialization tests.
83 Dummy new-style class.
90 Dummy new-style class with slots.
93 __slots__ = ("x", "y")
95 def __init__(self, x=None, y=None):
100 def __getstate__(self):
101 return {"x" : self.x, "y" : self.y}
104 def __setstate__(self, state):
110 class SimpleJellyTest:
111 def __init__(self, x, y):
115 def isTheSameAs(self, other):
116 return self.__dict__ == other.__dict__
120 class JellyTestCase(unittest.TestCase):
122 Testcases for L{jelly} module serialization.
124 @cvar decimalData: serialized version of decimal data, to be used in tests.
125 @type decimalData: C{list}
128 def _testSecurity(self, inputList, atom):
130 Helper test method to test security options for a type.
132 @param inputList: a sample input for the type.
133 @param inputList: C{list}
135 @param atom: atom identifier for the type.
138 c = jelly.jelly(inputList)
139 taster = jelly.SecurityOptions()
140 taster.allowBasicTypes()
141 # By default, it should succeed
142 jelly.unjelly(c, taster)
143 taster.allowedTypes.pop(atom)
144 # But it should raise an exception when disallowed
145 self.assertRaises(jelly.InsecureJelly, jelly.unjelly, c, taster)
148 def test_methodSelfIdentity(self):
151 a.bmethod = b.bmethod
153 im_ = jelly.unjelly(jelly.jelly(b)).a.bmethod
154 self.assertEqual(im_.im_class, im_.im_self.__class__)
157 def test_methodsNotSelfIdentity(self):
159 If a class change after an instance has been created, L{jelly.unjelly}
160 shoud raise a C{TypeError} when trying to unjelly the instance.
165 a.bmethod = c.cmethod
167 savecmethod = C.cmethod
170 self.assertRaises(TypeError, jelly.unjelly, jelly.jelly(b))
172 C.cmethod = savecmethod
175 def test_newStyle(self):
183 self.assertIsInstance(m, D)
184 self.assertIdentical(m.n2, m.n3)
187 def test_newStyleWithSlots(self):
189 A class defined with I{slots} can be jellied and unjellied with the
190 values for its attributes preserved.
196 self.assertIsInstance(m, E)
197 self.assertEqual(n.x, 1)
200 def test_typeOldStyle(self):
202 Test that an old style class type can be jellied and unjellied
203 to the original type.
206 r = jelly.unjelly(jelly.jelly(t))
207 self.assertEqual(t, r)
210 def test_typeNewStyle(self):
212 Test that a new style class type can be jellied and unjellied
213 to the original type.
216 r = jelly.unjelly(jelly.jelly(t))
217 self.assertEqual(t, r)
220 def test_typeBuiltin(self):
222 Test that a builtin type can be jellied and unjellied to the original
226 r = jelly.unjelly(jelly.jelly(t))
227 self.assertEqual(t, r)
230 def test_dateTime(self):
231 dtn = datetime.datetime.now()
232 dtd = datetime.datetime.now() - dtn
234 c = jelly.jelly(input)
235 output = jelly.unjelly(c)
236 self.assertEqual(input, output)
237 self.assertNotIdentical(input, output)
240 def test_decimal(self):
242 Jellying L{decimal.Decimal} instances and then unjellying the result
243 should produce objects which represent the values of the original
246 inputList = [decimal.Decimal('9.95'),
248 decimal.Decimal(123456),
249 decimal.Decimal('-78.901')]
250 c = jelly.jelly(inputList)
251 output = jelly.unjelly(c)
252 self.assertEqual(inputList, output)
253 self.assertNotIdentical(inputList, output)
256 decimalData = ['list', ['decimal', 995, -2], ['decimal', 0, 0],
257 ['decimal', 123456, 0], ['decimal', -78901, -3]]
260 def test_decimalUnjelly(self):
262 Unjellying the s-expressions produced by jelly for L{decimal.Decimal}
263 instances should result in L{decimal.Decimal} instances with the values
264 represented by the s-expressions.
266 This test also verifies that C{self.decimalData} contains valid jellied
267 data. This is important since L{test_decimalMissing} re-uses
268 C{self.decimalData} and is expected to be unable to produce
269 L{decimal.Decimal} instances even though the s-expression correctly
270 represents a list of them.
272 expected = [decimal.Decimal('9.95'),
274 decimal.Decimal(123456),
275 decimal.Decimal('-78.901')]
276 output = jelly.unjelly(self.decimalData)
277 self.assertEqual(output, expected)
280 def test_decimalMissing(self):
282 If decimal is unavailable on the unjelly side, L{jelly.unjelly} should
283 gracefully return L{jelly.Unpersistable} objects.
285 self.patch(jelly, 'decimal', None)
286 output = jelly.unjelly(self.decimalData)
287 self.assertEqual(len(output), 4)
289 self.assertIsInstance(output[i], jelly.Unpersistable)
290 self.assertEqual(output[0].reason,
291 "Could not unpersist decimal: 9.95")
292 self.assertEqual(output[1].reason,
293 "Could not unpersist decimal: 0")
294 self.assertEqual(output[2].reason,
295 "Could not unpersist decimal: 123456")
296 self.assertEqual(output[3].reason,
297 "Could not unpersist decimal: -78.901")
300 def test_decimalSecurity(self):
302 By default, C{decimal} objects should be allowed by
303 L{jelly.SecurityOptions}. If not allowed, L{jelly.unjelly} should raise
304 L{jelly.InsecureJelly} when trying to unjelly it.
306 inputList = [decimal.Decimal('9.95')]
307 self._testSecurity(inputList, "decimal")
310 skipReason = "decimal not available"
311 test_decimal.skip = skipReason
312 test_decimalUnjelly.skip = skipReason
313 test_decimalSecurity.skip = skipReason
318 Jellying C{set} instances and then unjellying the result
319 should produce objects which represent the values of the original
322 inputList = [set([1, 2, 3])]
323 output = jelly.unjelly(jelly.jelly(inputList))
324 self.assertEqual(inputList, output)
325 self.assertNotIdentical(inputList, output)
328 def test_frozenset(self):
330 Jellying C{frozenset} instances and then unjellying the result
331 should produce objects which represent the values of the original
334 inputList = [frozenset([1, 2, 3])]
335 output = jelly.unjelly(jelly.jelly(inputList))
336 self.assertEqual(inputList, output)
337 self.assertNotIdentical(inputList, output)
340 def test_setSecurity(self):
342 By default, C{set} objects should be allowed by
343 L{jelly.SecurityOptions}. If not allowed, L{jelly.unjelly} should raise
344 L{jelly.InsecureJelly} when trying to unjelly it.
346 inputList = [set([1, 2, 3])]
347 self._testSecurity(inputList, "set")
350 def test_frozensetSecurity(self):
352 By default, C{frozenset} objects should be allowed by
353 L{jelly.SecurityOptions}. If not allowed, L{jelly.unjelly} should raise
354 L{jelly.InsecureJelly} when trying to unjelly it.
356 inputList = [frozenset([1, 2, 3])]
357 self._testSecurity(inputList, "frozenset")
360 def test_oldSets(self):
362 Test jellying C{sets.Set}: it should serialize to the same thing as
363 C{set} jelly, and be unjellied as C{set} if available.
365 inputList = [jelly._sets.Set([1, 2, 3])]
366 inputJelly = jelly.jelly(inputList)
367 self.assertEqual(inputJelly, jelly.jelly([set([1, 2, 3])]))
368 output = jelly.unjelly(inputJelly)
369 # Even if the class is different, it should coerce to the same list
370 self.assertEqual(list(inputList[0]), list(output[0]))
371 if set is jelly._sets.Set:
372 self.assertIsInstance(output[0], jelly._sets.Set)
374 self.assertIsInstance(output[0], set)
377 def test_oldImmutableSets(self):
379 Test jellying C{sets.ImmutableSet}: it should serialize to the same
380 thing as C{frozenset} jelly, and be unjellied as C{frozenset} if
383 inputList = [jelly._sets.ImmutableSet([1, 2, 3])]
384 inputJelly = jelly.jelly(inputList)
385 self.assertEqual(inputJelly, jelly.jelly([frozenset([1, 2, 3])]))
386 output = jelly.unjelly(inputJelly)
387 # Even if the class is different, it should coerce to the same list
388 self.assertEqual(list(inputList[0]), list(output[0]))
389 if frozenset is jelly._sets.ImmutableSet:
390 self.assertIsInstance(output[0], jelly._sets.ImmutableSet)
392 self.assertIsInstance(output[0], frozenset)
395 def test_simple(self):
399 self.failUnless(SimpleJellyTest('a', 'b').isTheSameAs(
400 SimpleJellyTest('a', 'b')))
401 a = SimpleJellyTest(1, 2)
402 cereal = jelly.jelly(a)
403 b = jelly.unjelly(cereal)
404 self.failUnless(a.isTheSameAs(b))
407 def test_identity(self):
409 Test to make sure that objects retain identity properly.
415 self.assertIdentical(x[0], x[1])
416 self.assertIdentical(x[0][0], x)
419 self.assertIdentical(z[0], z[1])
420 self.assertIdentical(z[0][0], z)
423 def test_unicode(self):
425 y = jelly.unjelly(jelly.jelly(x))
426 self.assertEqual(x, y)
427 self.assertEqual(type(x), type(y))
430 def test_stressReferences(self):
432 toplevelTuple = ({'list': reref}, reref)
433 reref.append(toplevelTuple)
434 s = jelly.jelly(toplevelTuple)
436 self.assertIdentical(z[0]['list'], z[1])
437 self.assertIdentical(z[0]['list'][0], z)
440 def test_moreReferences(self):
446 self.assertIdentical(z[0][0][0], z)
449 def test_typeSecurity(self):
451 Test for type-level security of serialization.
453 taster = jelly.SecurityOptions()
454 dct = jelly.jelly({})
455 self.assertRaises(jelly.InsecureJelly, jelly.unjelly, dct, taster)
458 def test_newStyleClasses(self):
460 uj = jelly.unjelly(D)
461 self.assertIdentical(D, uj)
464 def test_lotsaTypes(self):
466 Test for all types currently supported in jelly
469 jelly.unjelly(jelly.jelly(a))
470 jelly.unjelly(jelly.jelly(a.amethod))
471 items = [afunc, [1, 2, 3], not bool(1), bool(1), 'test', 20.3,
472 (1, 2, 3), None, A, unittest, {'a': 1}, A.amethod]
474 self.assertEqual(i, jelly.unjelly(jelly.jelly(i)))
477 def test_setState(self):
480 def __init__(self, other):
482 def __getstate__(self):
484 def __setstate__(self, state):
485 self.other = state[0]
487 return hash(self.other)
491 t3 = TupleState((t1, t2))
492 d = {t1: t1, t2: t2, t3: t3, "t3": t3}
493 t3prime = jelly.unjelly(jelly.jelly(d))["t3"]
494 self.assertIdentical(t3prime.other[0].other, t3prime.other[1].other)
497 def test_classSecurity(self):
499 Test for class-level security of serialization.
501 taster = jelly.SecurityOptions()
502 taster.allowInstancesOf(A, B)
506 # add a little complexity to the data
509 # and a backreference
512 # first, a friendly insecure serialization
513 friendly = jelly.jelly(a, taster)
514 x = jelly.unjelly(friendly, taster)
515 self.assertIsInstance(x.c, jelly.Unpersistable)
516 # now, a malicious one
517 mean = jelly.jelly(a)
518 self.assertRaises(jelly.InsecureJelly, jelly.unjelly, mean, taster)
519 self.assertIdentical(x.x, x.b, "Identity mismatch")
520 # test class serialization
521 friendly = jelly.jelly(A, taster)
522 x = jelly.unjelly(friendly, taster)
523 self.assertIdentical(x, A, "A came back: %s" % x)
526 def test_unjellyable(self):
528 Test that if Unjellyable is used to deserialize a jellied object,
529 state comes out right.
531 class JellyableTestClass(jelly.Jellyable):
533 jelly.setUnjellyableForClass(JellyableTestClass, jelly.Unjellyable)
534 input = JellyableTestClass()
535 input.attribute = 'value'
536 output = jelly.unjelly(jelly.jelly(input))
537 self.assertEqual(output.attribute, 'value')
538 self.assertIsInstance(output, jelly.Unjellyable)
541 def test_persistentStorage(self):
543 def persistentStore(obj, jel, perst = perst):
544 perst[1] = perst[1] + 1
545 perst[0][perst[1]] = obj
548 def persistentLoad(pidstr, unj, perst = perst):
552 a = SimpleJellyTest(1, 2)
553 b = SimpleJellyTest(3, 4)
554 c = SimpleJellyTest(5, 6)
560 jel = jelly.jelly(a, persistentStore = persistentStore)
561 x = jelly.unjelly(jel, persistentLoad = persistentLoad)
563 self.assertIdentical(x.b, x.c.b)
564 self.failUnless(perst[0], "persistentStore was not called.")
565 self.assertIdentical(x.b, a.b, "Persistent storage identity failure.")
568 def test_newStyleClassesAttributes(self):
575 m = jelly.unjelly(jel)
576 # Check that it has been restored ok
577 self._check_newstyle(n, m)
580 def _check_newstyle(self, a, b):
581 self.assertEqual(a.id, b.id)
582 self.assertEqual(a.classAttr, 4)
583 self.assertEqual(b.classAttr, 4)
584 self.assertEqual(len(a.children), len(b.children))
585 for x, y in zip(a.children, b.children):
586 self._check_newstyle(x, y)
589 def test_referenceable(self):
591 A L{pb.Referenceable} instance jellies to a structure which unjellies to
592 a L{pb.RemoteReference}. The C{RemoteReference} has a I{luid} that
593 matches up with the local object key in the L{pb.Broker} which sent the
596 ref = pb.Referenceable()
597 jellyBroker = pb.Broker()
598 jellyBroker.makeConnection(StringTransport())
599 j = jelly.jelly(ref, invoker=jellyBroker)
601 unjellyBroker = pb.Broker()
602 unjellyBroker.makeConnection(StringTransport())
604 uj = jelly.unjelly(j, invoker=unjellyBroker)
605 self.assertIn(uj.luid, jellyBroker.localObjects)
609 class ClassA(pb.Copyable, pb.RemoteCopy):
611 self.ref = ClassB(self)
615 class ClassB(pb.Copyable, pb.RemoteCopy):
616 def __init__(self, ref):
621 class CircularReferenceTestCase(unittest.TestCase):
623 Tests for circular references handling in the jelly/unjelly process.
626 def test_simpleCircle(self):
627 jelly.setUnjellyableForClass(ClassA, ClassA)
628 jelly.setUnjellyableForClass(ClassB, ClassB)
629 a = jelly.unjelly(jelly.jelly(ClassA()))
630 self.assertIdentical(a.ref.ref, a,
631 "Identity not preserved in circular reference")
634 def test_circleWithInvoker(self):
635 class DummyInvokerClass:
637 dummyInvoker = DummyInvokerClass()
638 dummyInvoker.serializingPerspective = None
640 jelly.setUnjellyableForClass(ClassA, ClassA)
641 jelly.setUnjellyableForClass(ClassB, ClassB)
642 j = jelly.jelly(a0, invoker=dummyInvoker)
643 a1 = jelly.unjelly(j)
644 self.failUnlessIdentical(a1.ref.ref, a1,
645 "Identity not preserved in circular reference")
650 Check that a C{set} can contain a circular reference and be serialized
651 and unserialized without losing the reference.
654 a = SimpleJellyTest(s, None)
656 res = jelly.unjelly(jelly.jelly(a))
657 self.assertIsInstance(res.x, set)
658 self.assertEqual(list(res.x), [res])
661 def test_frozenset(self):
663 Check that a C{frozenset} can contain a circular reference and be
664 serializeserialized without losing the reference.
666 a = SimpleJellyTest(None, None)
669 res = jelly.unjelly(jelly.jelly(a))
670 self.assertIsInstance(res.x, frozenset)
671 self.assertEqual(list(res.x), [res])