test_jelly.py 20 KB

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