test_persisted.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. from __future__ import division, absolute_import
  4. # System Imports
  5. import sys
  6. from twisted.trial import unittest
  7. try:
  8. import cPickle as pickle
  9. except ImportError:
  10. import pickle
  11. import io
  12. try:
  13. from cStringIO import StringIO as _oldStyleCStringIO
  14. except ImportError:
  15. skipStringIO = "No cStringIO available."
  16. else:
  17. skipStringIO = None
  18. try:
  19. import copyreg
  20. except:
  21. import copy_reg as copyreg
  22. # Twisted Imports
  23. from twisted.persisted import styles, aot, crefutil
  24. from twisted.python.compat import _PY3
  25. class VersionTests(unittest.TestCase):
  26. def test_nullVersionUpgrade(self):
  27. global NullVersioned
  28. class NullVersioned(object):
  29. def __init__(self):
  30. self.ok = 0
  31. pkcl = pickle.dumps(NullVersioned())
  32. class NullVersioned(styles.Versioned, object):
  33. persistenceVersion = 1
  34. def upgradeToVersion1(self):
  35. self.ok = 1
  36. mnv = pickle.loads(pkcl)
  37. styles.doUpgrade()
  38. assert mnv.ok, "initial upgrade not run!"
  39. def test_versionUpgrade(self):
  40. global MyVersioned
  41. class MyVersioned(styles.Versioned):
  42. persistenceVersion = 2
  43. persistenceForgets = ['garbagedata']
  44. v3 = 0
  45. v4 = 0
  46. def __init__(self):
  47. self.somedata = 'xxx'
  48. self.garbagedata = lambda q: 'cant persist'
  49. def upgradeToVersion3(self):
  50. self.v3 += 1
  51. def upgradeToVersion4(self):
  52. self.v4 += 1
  53. mv = MyVersioned()
  54. assert not (mv.v3 or mv.v4), "hasn't been upgraded yet"
  55. pickl = pickle.dumps(mv)
  56. MyVersioned.persistenceVersion = 4
  57. obj = pickle.loads(pickl)
  58. styles.doUpgrade()
  59. assert obj.v3, "didn't do version 3 upgrade"
  60. assert obj.v4, "didn't do version 4 upgrade"
  61. pickl = pickle.dumps(obj)
  62. obj = pickle.loads(pickl)
  63. styles.doUpgrade()
  64. assert obj.v3 == 1, "upgraded unnecessarily"
  65. assert obj.v4 == 1, "upgraded unnecessarily"
  66. def test_nonIdentityHash(self):
  67. global ClassWithCustomHash
  68. class ClassWithCustomHash(styles.Versioned):
  69. def __init__(self, unique, hash):
  70. self.unique = unique
  71. self.hash = hash
  72. def __hash__(self):
  73. return self.hash
  74. v1 = ClassWithCustomHash('v1', 0)
  75. v2 = ClassWithCustomHash('v2', 0)
  76. pkl = pickle.dumps((v1, v2))
  77. del v1, v2
  78. ClassWithCustomHash.persistenceVersion = 1
  79. ClassWithCustomHash.upgradeToVersion1 = lambda self: setattr(self, 'upgraded', True)
  80. v1, v2 = pickle.loads(pkl)
  81. styles.doUpgrade()
  82. self.assertEqual(v1.unique, 'v1')
  83. self.assertEqual(v2.unique, 'v2')
  84. self.assertTrue(v1.upgraded)
  85. self.assertTrue(v2.upgraded)
  86. def test_upgradeDeserializesObjectsRequiringUpgrade(self):
  87. global ToyClassA, ToyClassB
  88. class ToyClassA(styles.Versioned):
  89. pass
  90. class ToyClassB(styles.Versioned):
  91. pass
  92. x = ToyClassA()
  93. y = ToyClassB()
  94. pklA, pklB = pickle.dumps(x), pickle.dumps(y)
  95. del x, y
  96. ToyClassA.persistenceVersion = 1
  97. def upgradeToVersion1(self):
  98. self.y = pickle.loads(pklB)
  99. styles.doUpgrade()
  100. ToyClassA.upgradeToVersion1 = upgradeToVersion1
  101. ToyClassB.persistenceVersion = 1
  102. ToyClassB.upgradeToVersion1 = lambda self: setattr(self, 'upgraded', True)
  103. x = pickle.loads(pklA)
  104. styles.doUpgrade()
  105. self.assertTrue(x.y.upgraded)
  106. class VersionedSubClass(styles.Versioned):
  107. pass
  108. class SecondVersionedSubClass(styles.Versioned):
  109. pass
  110. class VersionedSubSubClass(VersionedSubClass):
  111. pass
  112. class VersionedDiamondSubClass(VersionedSubSubClass, SecondVersionedSubClass):
  113. pass
  114. class AybabtuTests(unittest.TestCase):
  115. """
  116. L{styles._aybabtu} gets all of classes in the inheritance hierarchy of its
  117. argument that are strictly between L{Versioned} and the class itself.
  118. """
  119. def test_aybabtuStrictEmpty(self):
  120. """
  121. L{styles._aybabtu} of L{Versioned} itself is an empty list.
  122. """
  123. self.assertEqual(styles._aybabtu(styles.Versioned), [])
  124. def test_aybabtuStrictSubclass(self):
  125. """
  126. There are no classes I{between} L{VersionedSubClass} and L{Versioned},
  127. so L{styles._aybabtu} returns an empty list.
  128. """
  129. self.assertEqual(styles._aybabtu(VersionedSubClass), [])
  130. def test_aybabtuSubsubclass(self):
  131. """
  132. With a sub-sub-class of L{Versioned}, L{styles._aybabtu} returns a list
  133. containing the intervening subclass.
  134. """
  135. self.assertEqual(styles._aybabtu(VersionedSubSubClass),
  136. [VersionedSubClass])
  137. def test_aybabtuStrict(self):
  138. """
  139. For a diamond-shaped inheritance graph, L{styles._aybabtu} returns a
  140. list containing I{both} intermediate subclasses.
  141. """
  142. self.assertEqual(
  143. styles._aybabtu(VersionedDiamondSubClass),
  144. [VersionedSubSubClass, VersionedSubClass, SecondVersionedSubClass])
  145. class MyEphemeral(styles.Ephemeral):
  146. def __init__(self, x):
  147. self.x = x
  148. class EphemeralTests(unittest.TestCase):
  149. def test_ephemeral(self):
  150. o = MyEphemeral(3)
  151. self.assertEqual(o.__class__, MyEphemeral)
  152. self.assertEqual(o.x, 3)
  153. pickl = pickle.dumps(o)
  154. o = pickle.loads(pickl)
  155. self.assertEqual(o.__class__, styles.Ephemeral)
  156. self.assertFalse(hasattr(o, 'x'))
  157. class Pickleable:
  158. def __init__(self, x):
  159. self.x = x
  160. def getX(self):
  161. return self.x
  162. class NotPickleable(object):
  163. """
  164. A class that is not pickleable.
  165. """
  166. def __reduce__(self):
  167. """
  168. Raise an exception instead of pickling.
  169. """
  170. raise TypeError("Not serializable.")
  171. class CopyRegistered(object):
  172. """
  173. A class that is pickleable only because it is registered with the
  174. C{copyreg} module.
  175. """
  176. def __init__(self):
  177. """
  178. Ensure that this object is normally not pickleable.
  179. """
  180. self.notPickleable = NotPickleable()
  181. class CopyRegisteredLoaded(object):
  182. """
  183. L{CopyRegistered} after unserialization.
  184. """
  185. def reduceCopyRegistered(cr):
  186. """
  187. Externally implement C{__reduce__} for L{CopyRegistered}.
  188. @param cr: The L{CopyRegistered} instance.
  189. @return: a 2-tuple of callable and argument list, in this case
  190. L{CopyRegisteredLoaded} and no arguments.
  191. """
  192. return CopyRegisteredLoaded, ()
  193. copyreg.pickle(CopyRegistered, reduceCopyRegistered)
  194. class A:
  195. """
  196. dummy class
  197. """
  198. def amethod(self):
  199. pass
  200. class B:
  201. """
  202. dummy class
  203. """
  204. def bmethod(self):
  205. pass
  206. def funktion():
  207. pass
  208. class PicklingTests(unittest.TestCase):
  209. """Test pickling of extra object types."""
  210. def test_module(self):
  211. pickl = pickle.dumps(styles)
  212. o = pickle.loads(pickl)
  213. self.assertEqual(o, styles)
  214. def test_classMethod(self):
  215. """
  216. After importing L{twisted.persisted.styles}, it is possible to pickle
  217. classmethod objects.
  218. """
  219. pickl = pickle.dumps(Pickleable.getX)
  220. o = pickle.loads(pickl)
  221. self.assertEqual(o, Pickleable.getX)
  222. if sys.version_info > (3, 4):
  223. test_classMethod.skip = (
  224. "As of Python 3.4 it is no longer possible to globally change "
  225. "the behavior of function pickling."
  226. )
  227. def test_instanceMethod(self):
  228. obj = Pickleable(4)
  229. pickl = pickle.dumps(obj.getX)
  230. o = pickle.loads(pickl)
  231. self.assertEqual(o(), 4)
  232. self.assertEqual(type(o), type(obj.getX))
  233. def test_stringIO(self):
  234. f = _oldStyleCStringIO()
  235. f.write("abc")
  236. pickl = pickle.dumps(f)
  237. o = pickle.loads(pickl)
  238. self.assertEqual(type(o), type(f))
  239. self.assertEqual(f.getvalue(), "abc")
  240. if skipStringIO:
  241. test_stringIO.skip = skipStringIO
  242. class StringIOTransitionTests(unittest.TestCase):
  243. """
  244. When pickling a cStringIO in Python 2, it should unpickle as a BytesIO or a
  245. StringIO in Python 3, depending on the type of its contents.
  246. """
  247. if not _PY3:
  248. skip = "In Python 2 we can still unpickle cStringIO as such."
  249. def test_unpickleBytesIO(self):
  250. """
  251. A cStringIO pickled with bytes in it will yield an L{io.BytesIO} on
  252. python 3.
  253. """
  254. pickledStringIWithText = (
  255. b"ctwisted.persisted.styles\nunpickleStringI\np0\n"
  256. b"(S'test'\np1\nI0\ntp2\nRp3\n."
  257. )
  258. loaded = pickle.loads(pickledStringIWithText)
  259. self.assertIsInstance(loaded, io.StringIO)
  260. self.assertEqual(loaded.getvalue(), u"test")
  261. class EvilSourceror:
  262. def __init__(self, x):
  263. self.a = self
  264. self.a.b = self
  265. self.a.b.c = x
  266. class NonDictState:
  267. def __getstate__(self):
  268. return self.state
  269. def __setstate__(self, state):
  270. self.state = state
  271. class AOTTests(unittest.TestCase):
  272. def test_simpleTypes(self):
  273. obj = (1, 2.0, 3j, True, slice(1, 2, 3), 'hello', u'world',
  274. sys.maxsize + 1, None, Ellipsis)
  275. rtObj = aot.unjellyFromSource(aot.jellyToSource(obj))
  276. self.assertEqual(obj, rtObj)
  277. def test_methodSelfIdentity(self):
  278. a = A()
  279. b = B()
  280. a.bmethod = b.bmethod
  281. b.a = a
  282. im_ = aot.unjellyFromSource(aot.jellyToSource(b)).a.bmethod
  283. self.assertEqual(aot._selfOfMethod(im_).__class__,
  284. aot._classOfMethod(im_))
  285. def test_methodNotSelfIdentity(self):
  286. """
  287. If a class change after an instance has been created,
  288. L{aot.unjellyFromSource} shoud raise a C{TypeError} when trying to
  289. unjelly the instance.
  290. """
  291. a = A()
  292. b = B()
  293. a.bmethod = b.bmethod
  294. b.a = a
  295. savedbmethod = B.bmethod
  296. del B.bmethod
  297. try:
  298. self.assertRaises(TypeError, aot.unjellyFromSource,
  299. aot.jellyToSource(b))
  300. finally:
  301. B.bmethod = savedbmethod
  302. def test_unsupportedType(self):
  303. """
  304. L{aot.jellyToSource} should raise a C{TypeError} when trying to jelly
  305. an unknown type without a C{__dict__} property or C{__getstate__}
  306. method.
  307. """
  308. class UnknownType(object):
  309. @property
  310. def __dict__(self):
  311. raise AttributeError()
  312. self.assertRaises(TypeError, aot.jellyToSource, UnknownType())
  313. def test_basicIdentity(self):
  314. # Anyone wanting to make this datastructure more complex, and thus this
  315. # test more comprehensive, is welcome to do so.
  316. aj = aot.AOTJellier().jellyToAO
  317. d = {'hello': 'world', "method": aj}
  318. l = [1, 2, 3,
  319. "he\tllo\n\n\"x world!",
  320. u"goodbye \n\t\u1010 world!",
  321. 1, 1.0, 100 ** 100, unittest, aot.AOTJellier, d,
  322. funktion
  323. ]
  324. t = tuple(l)
  325. l.append(l)
  326. l.append(t)
  327. l.append(t)
  328. uj = aot.unjellyFromSource(aot.jellyToSource([l, l]))
  329. assert uj[0] is uj[1]
  330. assert uj[1][0:5] == l[0:5]
  331. def test_nonDictState(self):
  332. a = NonDictState()
  333. a.state = "meringue!"
  334. assert aot.unjellyFromSource(aot.jellyToSource(a)).state == a.state
  335. def test_copyReg(self):
  336. """
  337. L{aot.jellyToSource} and L{aot.unjellyFromSource} honor functions
  338. registered in the pickle copy registry.
  339. """
  340. uj = aot.unjellyFromSource(aot.jellyToSource(CopyRegistered()))
  341. self.assertIsInstance(uj, CopyRegisteredLoaded)
  342. def test_funkyReferences(self):
  343. o = EvilSourceror(EvilSourceror([]))
  344. j1 = aot.jellyToAOT(o)
  345. oj = aot.unjellyFromAOT(j1)
  346. assert oj.a is oj
  347. assert oj.a.b is oj.b
  348. assert oj.c is not oj.c.c
  349. def test_circularTuple(self):
  350. """
  351. L{aot.jellyToAOT} can persist circular references through tuples.
  352. """
  353. l = []
  354. t = (l, 4321)
  355. l.append(t)
  356. j1 = aot.jellyToAOT(l)
  357. oj = aot.unjellyFromAOT(j1)
  358. self.assertIsInstance(oj[0], tuple)
  359. self.assertIs(oj[0][0], oj)
  360. self.assertEqual(oj[0][1], 4321)
  361. class CrefUtilTests(unittest.TestCase):
  362. """
  363. Tests for L{crefutil}.
  364. """
  365. def test_dictUnknownKey(self):
  366. """
  367. L{crefutil._DictKeyAndValue} only support keys C{0} and C{1}.
  368. """
  369. d = crefutil._DictKeyAndValue({})
  370. self.assertRaises(RuntimeError, d.__setitem__, 2, 3)
  371. def test_deferSetMultipleTimes(self):
  372. """
  373. L{crefutil._Defer} can be assigned a key only one time.
  374. """
  375. d = crefutil._Defer()
  376. d[0] = 1
  377. self.assertRaises(RuntimeError, d.__setitem__, 0, 1)
  378. def test_containerWhereAllElementsAreKnown(self):
  379. """
  380. A L{crefutil._Container} where all of its elements are known at
  381. construction time is nonsensical and will result in errors in any call
  382. to addDependant.
  383. """
  384. container = crefutil._Container([1, 2, 3], list)
  385. self.assertRaises(AssertionError,
  386. container.addDependant, {}, "ignore-me")
  387. def test_dontPutCircularReferencesInDictionaryKeys(self):
  388. """
  389. If a dictionary key contains a circular reference (which is probably a
  390. bad practice anyway) it will be resolved by a
  391. L{crefutil._DictKeyAndValue}, not by placing a L{crefutil.NotKnown}
  392. into a dictionary key.
  393. """
  394. self.assertRaises(AssertionError,
  395. dict().__setitem__, crefutil.NotKnown(), "value")
  396. def test_dontCallInstanceMethodsThatArentReady(self):
  397. """
  398. L{crefutil._InstanceMethod} raises L{AssertionError} to indicate it
  399. should not be called. This should not be possible with any of its API
  400. clients, but is provided for helping to debug.
  401. """
  402. self.assertRaises(AssertionError,
  403. crefutil._InstanceMethod(
  404. "no_name", crefutil.NotKnown(), type))
  405. testCases = [VersionTests, EphemeralTests, PicklingTests]