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 |