Imported Upstream version 12.1.0
[contrib/python-twisted.git] / twisted / test / test_jelly.py
1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
3
4 """
5 Test cases for L{jelly} object serialization.
6 """
7
8 import datetime
9
10 try:
11     import decimal
12 except ImportError:
13     decimal = None
14
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
19
20
21 class TestNode(object, jelly.Jellyable):
22     """
23     An object to test jellyfying of new style class instances.
24     """
25     classAttr = 4
26
27     def __init__(self, parent=None):
28         if parent:
29             self.id = parent.id + 1
30             parent.children.append(self)
31         else:
32             self.id = 1
33         self.parent = parent
34         self.children = []
35
36
37
38 class A:
39     """
40     Dummy class.
41     """
42
43     def amethod(self):
44         """
45         Method tp be used in serialization tests.
46         """
47
48
49
50 def afunc(self):
51     """
52     A dummy function to test function serialization.
53     """
54
55
56
57 class B:
58     """
59     Dummy class.
60     """
61
62     def bmethod(self):
63         """
64         Method to be used in serialization tests.
65         """
66
67
68
69 class C:
70     """
71     Dummy class.
72     """
73
74     def cmethod(self):
75         """
76         Method to be used in serialization tests.
77         """
78
79
80
81 class D(object):
82     """
83     Dummy new-style class.
84     """
85
86
87
88 class E(object):
89     """
90     Dummy new-style class with slots.
91     """
92
93     __slots__ = ("x", "y")
94
95     def __init__(self, x=None, y=None):
96         self.x = x
97         self.y = y
98
99
100     def __getstate__(self):
101         return {"x" : self.x, "y" : self.y}
102
103
104     def __setstate__(self, state):
105         self.x = state["x"]
106         self.y = state["y"]
107
108
109
110 class SimpleJellyTest:
111     def __init__(self, x, y):
112         self.x = x
113         self.y = y
114
115     def isTheSameAs(self, other):
116         return self.__dict__ == other.__dict__
117
118
119
120 class JellyTestCase(unittest.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: C{list}
126     """
127
128     def _testSecurity(self, inputList, atom):
129         """
130         Helper test method to test security options for a type.
131
132         @param inputList: a sample input for the type.
133         @param inputList: C{list}
134
135         @param atom: atom identifier for the type.
136         @type atom: C{str}
137         """
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)
146
147
148     def test_methodSelfIdentity(self):
149         a = A()
150         b = B()
151         a.bmethod = b.bmethod
152         b.a = a
153         im_ = jelly.unjelly(jelly.jelly(b)).a.bmethod
154         self.assertEqual(im_.im_class, im_.im_self.__class__)
155
156
157     def test_methodsNotSelfIdentity(self):
158         """
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.
161         """
162         a = A()
163         b = B()
164         c = C()
165         a.bmethod = c.cmethod
166         b.a = a
167         savecmethod = C.cmethod
168         del C.cmethod
169         try:
170             self.assertRaises(TypeError, jelly.unjelly, jelly.jelly(b))
171         finally:
172             C.cmethod = savecmethod
173
174
175     def test_newStyle(self):
176         n = D()
177         n.x = 1
178         n2 = D()
179         n.n2 = n2
180         n.n3 = n2
181         c = jelly.jelly(n)
182         m = jelly.unjelly(c)
183         self.assertIsInstance(m, D)
184         self.assertIdentical(m.n2, m.n3)
185
186
187     def test_newStyleWithSlots(self):
188         """
189         A class defined with I{slots} can be jellied and unjellied with the
190         values for its attributes preserved.
191         """
192         n = E()
193         n.x = 1
194         c = jelly.jelly(n)
195         m = jelly.unjelly(c)
196         self.assertIsInstance(m, E)
197         self.assertEqual(n.x, 1)
198
199
200     def test_typeOldStyle(self):
201         """
202         Test that an old style class type can be jellied and unjellied
203         to the original type.
204         """
205         t = [C]
206         r = jelly.unjelly(jelly.jelly(t))
207         self.assertEqual(t, r)
208
209
210     def test_typeNewStyle(self):
211         """
212         Test that a new style class type can be jellied and unjellied
213         to the original type.
214         """
215         t = [D]
216         r = jelly.unjelly(jelly.jelly(t))
217         self.assertEqual(t, r)
218
219
220     def test_typeBuiltin(self):
221         """
222         Test that a builtin type can be jellied and unjellied to the original
223         type.
224         """
225         t = [str]
226         r = jelly.unjelly(jelly.jelly(t))
227         self.assertEqual(t, r)
228
229
230     def test_dateTime(self):
231         dtn = datetime.datetime.now()
232         dtd = datetime.datetime.now() - dtn
233         input = [dtn, dtd]
234         c = jelly.jelly(input)
235         output = jelly.unjelly(c)
236         self.assertEqual(input, output)
237         self.assertNotIdentical(input, output)
238
239
240     def test_decimal(self):
241         """
242         Jellying L{decimal.Decimal} instances and then unjellying the result
243         should produce objects which represent the values of the original
244         inputs.
245         """
246         inputList = [decimal.Decimal('9.95'),
247                      decimal.Decimal(0),
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)
254
255
256     decimalData = ['list', ['decimal', 995, -2], ['decimal', 0, 0],
257                            ['decimal', 123456, 0], ['decimal', -78901, -3]]
258
259
260     def test_decimalUnjelly(self):
261         """
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.
265
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.
271         """
272         expected = [decimal.Decimal('9.95'),
273                     decimal.Decimal(0),
274                     decimal.Decimal(123456),
275                     decimal.Decimal('-78.901')]
276         output = jelly.unjelly(self.decimalData)
277         self.assertEqual(output, expected)
278
279
280     def test_decimalMissing(self):
281         """
282         If decimal is unavailable on the unjelly side, L{jelly.unjelly} should
283         gracefully return L{jelly.Unpersistable} objects.
284         """
285         self.patch(jelly, 'decimal', None)
286         output = jelly.unjelly(self.decimalData)
287         self.assertEqual(len(output), 4)
288         for i in range(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")
298
299
300     def test_decimalSecurity(self):
301         """
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.
305         """
306         inputList = [decimal.Decimal('9.95')]
307         self._testSecurity(inputList, "decimal")
308
309     if decimal is None:
310         skipReason = "decimal not available"
311         test_decimal.skip = skipReason
312         test_decimalUnjelly.skip = skipReason
313         test_decimalSecurity.skip = skipReason
314
315
316     def test_set(self):
317         """
318         Jellying C{set} instances and then unjellying the result
319         should produce objects which represent the values of the original
320         inputs.
321         """
322         inputList = [set([1, 2, 3])]
323         output = jelly.unjelly(jelly.jelly(inputList))
324         self.assertEqual(inputList, output)
325         self.assertNotIdentical(inputList, output)
326
327
328     def test_frozenset(self):
329         """
330         Jellying C{frozenset} instances and then unjellying the result
331         should produce objects which represent the values of the original
332         inputs.
333         """
334         inputList = [frozenset([1, 2, 3])]
335         output = jelly.unjelly(jelly.jelly(inputList))
336         self.assertEqual(inputList, output)
337         self.assertNotIdentical(inputList, output)
338
339
340     def test_setSecurity(self):
341         """
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.
345         """
346         inputList = [set([1, 2, 3])]
347         self._testSecurity(inputList, "set")
348
349
350     def test_frozensetSecurity(self):
351         """
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.
355         """
356         inputList = [frozenset([1, 2, 3])]
357         self._testSecurity(inputList, "frozenset")
358
359
360     def test_oldSets(self):
361         """
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.
364         """
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)
373         else:
374             self.assertIsInstance(output[0], set)
375
376
377     def test_oldImmutableSets(self):
378         """
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
381         available.
382         """
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)
391         else:
392             self.assertIsInstance(output[0], frozenset)
393
394
395     def test_simple(self):
396         """
397         Simplest test case.
398         """
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))
405
406
407     def test_identity(self):
408         """
409         Test to make sure that objects retain identity properly.
410         """
411         x = []
412         y = (x)
413         x.append(y)
414         x.append(y)
415         self.assertIdentical(x[0], x[1])
416         self.assertIdentical(x[0][0], x)
417         s = jelly.jelly(x)
418         z = jelly.unjelly(s)
419         self.assertIdentical(z[0], z[1])
420         self.assertIdentical(z[0][0], z)
421
422
423     def test_unicode(self):
424         x = unicode('blah')
425         y = jelly.unjelly(jelly.jelly(x))
426         self.assertEqual(x, y)
427         self.assertEqual(type(x), type(y))
428
429
430     def test_stressReferences(self):
431         reref = []
432         toplevelTuple = ({'list': reref}, reref)
433         reref.append(toplevelTuple)
434         s = jelly.jelly(toplevelTuple)
435         z = jelly.unjelly(s)
436         self.assertIdentical(z[0]['list'], z[1])
437         self.assertIdentical(z[0]['list'][0], z)
438
439
440     def test_moreReferences(self):
441         a = []
442         t = (a,)
443         a.append((t,))
444         s = jelly.jelly(t)
445         z = jelly.unjelly(s)
446         self.assertIdentical(z[0][0][0], z)
447
448
449     def test_typeSecurity(self):
450         """
451         Test for type-level security of serialization.
452         """
453         taster = jelly.SecurityOptions()
454         dct = jelly.jelly({})
455         self.assertRaises(jelly.InsecureJelly, jelly.unjelly, dct, taster)
456
457
458     def test_newStyleClasses(self):
459         j = jelly.jelly(D)
460         uj = jelly.unjelly(D)
461         self.assertIdentical(D, uj)
462
463
464     def test_lotsaTypes(self):
465         """
466         Test for all types currently supported in jelly
467         """
468         a = A()
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]
473         for i in items:
474             self.assertEqual(i, jelly.unjelly(jelly.jelly(i)))
475
476
477     def test_setState(self):
478         global TupleState
479         class TupleState:
480             def __init__(self, other):
481                 self.other = other
482             def __getstate__(self):
483                 return (self.other,)
484             def __setstate__(self, state):
485                 self.other = state[0]
486             def __hash__(self):
487                 return hash(self.other)
488         a = A()
489         t1 = TupleState(a)
490         t2 = TupleState(a)
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)
495
496
497     def test_classSecurity(self):
498         """
499         Test for class-level security of serialization.
500         """
501         taster = jelly.SecurityOptions()
502         taster.allowInstancesOf(A, B)
503         a = A()
504         b = B()
505         c = C()
506         # add a little complexity to the data
507         a.b = b
508         a.c = c
509         # and a backreference
510         a.x = b
511         b.c = c
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)
524
525
526     def test_unjellyable(self):
527         """
528         Test that if Unjellyable is used to deserialize a jellied object,
529         state comes out right.
530         """
531         class JellyableTestClass(jelly.Jellyable):
532             pass
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)
539
540
541     def test_persistentStorage(self):
542         perst = [{}, 1]
543         def persistentStore(obj, jel, perst = perst):
544             perst[1] = perst[1] + 1
545             perst[0][perst[1]] = obj
546             return str(perst[1])
547
548         def persistentLoad(pidstr, unj, perst = perst):
549             pid = int(pidstr)
550             return perst[0][pid]
551
552         a = SimpleJellyTest(1, 2)
553         b = SimpleJellyTest(3, 4)
554         c = SimpleJellyTest(5, 6)
555
556         a.b = b
557         a.c = c
558         c.b = b
559
560         jel = jelly.jelly(a, persistentStore = persistentStore)
561         x = jelly.unjelly(jel, persistentLoad = persistentLoad)
562
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.")
566
567
568     def test_newStyleClassesAttributes(self):
569         n = TestNode()
570         n1 = TestNode(n)
571         n11 = TestNode(n1)
572         n2 = TestNode(n)
573         # Jelly it
574         jel = jelly.jelly(n)
575         m = jelly.unjelly(jel)
576         # Check that it has been restored ok
577         self._check_newstyle(n, m)
578
579
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)
587
588
589     def test_referenceable(self):
590         """
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
594         L{Referenceable}.
595         """
596         ref = pb.Referenceable()
597         jellyBroker = pb.Broker()
598         jellyBroker.makeConnection(StringTransport())
599         j = jelly.jelly(ref, invoker=jellyBroker)
600
601         unjellyBroker = pb.Broker()
602         unjellyBroker.makeConnection(StringTransport())
603
604         uj = jelly.unjelly(j, invoker=unjellyBroker)
605         self.assertIn(uj.luid, jellyBroker.localObjects)
606
607
608
609 class ClassA(pb.Copyable, pb.RemoteCopy):
610     def __init__(self):
611         self.ref = ClassB(self)
612
613
614
615 class ClassB(pb.Copyable, pb.RemoteCopy):
616     def __init__(self, ref):
617         self.ref = ref
618
619
620
621 class CircularReferenceTestCase(unittest.TestCase):
622     """
623     Tests for circular references handling in the jelly/unjelly process.
624     """
625
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")
632
633
634     def test_circleWithInvoker(self):
635         class DummyInvokerClass:
636             pass
637         dummyInvoker = DummyInvokerClass()
638         dummyInvoker.serializingPerspective = None
639         a0 = ClassA()
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")
646
647
648     def test_set(self):
649         """
650         Check that a C{set} can contain a circular reference and be serialized
651         and unserialized without losing the reference.
652         """
653         s = set()
654         a = SimpleJellyTest(s, None)
655         s.add(a)
656         res = jelly.unjelly(jelly.jelly(a))
657         self.assertIsInstance(res.x, set)
658         self.assertEqual(list(res.x), [res])
659
660
661     def test_frozenset(self):
662         """
663         Check that a C{frozenset} can contain a circular reference and be
664         serializeserialized without losing the reference.
665         """
666         a = SimpleJellyTest(None, None)
667         s = frozenset([a])
668         a.x = s
669         res = jelly.unjelly(jelly.jelly(a))
670         self.assertIsInstance(res.x, frozenset)
671         self.assertEqual(list(res.x), [res])