Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_persisted.py 13KB

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