123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498 |
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
-
-
- # System Imports
- import copyreg
- import io
- import pickle
- import sys
-
- # Twisted Imports
- from twisted.persisted import aot, crefutil, styles
- from twisted.trial import unittest
- from twisted.trial.unittest import TestCase
-
-
- class VersionTests(TestCase):
- def test_nullVersionUpgrade(self):
- global NullVersioned
-
- class NullVersioned:
- def __init__(self):
- self.ok = 0
-
- pkcl = pickle.dumps(NullVersioned())
-
- class NullVersioned(styles.Versioned):
- persistenceVersion = 1
-
- def upgradeToVersion1(self):
- self.ok = 1
-
- mnv = pickle.loads(pkcl)
- styles.doUpgrade()
- assert mnv.ok, "initial upgrade not run!"
-
- def test_versionUpgrade(self):
- global MyVersioned
-
- class MyVersioned(styles.Versioned):
- persistenceVersion = 2
- persistenceForgets = ["garbagedata"]
- v3 = 0
- v4 = 0
-
- def __init__(self):
- self.somedata = "xxx"
- self.garbagedata = lambda q: "cant persist"
-
- def upgradeToVersion3(self):
- self.v3 += 1
-
- def upgradeToVersion4(self):
- self.v4 += 1
-
- mv = MyVersioned()
- assert not (mv.v3 or mv.v4), "hasn't been upgraded yet"
- pickl = pickle.dumps(mv)
- MyVersioned.persistenceVersion = 4
- obj = pickle.loads(pickl)
- styles.doUpgrade()
- assert obj.v3, "didn't do version 3 upgrade"
- assert obj.v4, "didn't do version 4 upgrade"
- pickl = pickle.dumps(obj)
- obj = pickle.loads(pickl)
- styles.doUpgrade()
- assert obj.v3 == 1, "upgraded unnecessarily"
- assert obj.v4 == 1, "upgraded unnecessarily"
-
- def test_nonIdentityHash(self):
- global ClassWithCustomHash
-
- class ClassWithCustomHash(styles.Versioned):
- def __init__(self, unique, hash):
- self.unique = unique
- self.hash = hash
-
- def __hash__(self):
- return self.hash
-
- v1 = ClassWithCustomHash("v1", 0)
- v2 = ClassWithCustomHash("v2", 0)
-
- pkl = pickle.dumps((v1, v2))
- del v1, v2
- ClassWithCustomHash.persistenceVersion = 1
- ClassWithCustomHash.upgradeToVersion1 = lambda self: setattr(
- self, "upgraded", True
- )
- v1, v2 = pickle.loads(pkl)
- styles.doUpgrade()
- self.assertEqual(v1.unique, "v1")
- self.assertEqual(v2.unique, "v2")
- self.assertTrue(v1.upgraded)
- self.assertTrue(v2.upgraded)
-
- def test_upgradeDeserializesObjectsRequiringUpgrade(self):
- global ToyClassA, ToyClassB
-
- class ToyClassA(styles.Versioned):
- pass
-
- class ToyClassB(styles.Versioned):
- pass
-
- x = ToyClassA()
- y = ToyClassB()
- pklA, pklB = pickle.dumps(x), pickle.dumps(y)
- del x, y
- ToyClassA.persistenceVersion = 1
-
- def upgradeToVersion1(self):
- self.y = pickle.loads(pklB)
- styles.doUpgrade()
-
- ToyClassA.upgradeToVersion1 = upgradeToVersion1
- ToyClassB.persistenceVersion = 1
-
- def setUpgraded(self):
- setattr(self, "upgraded", True)
-
- ToyClassB.upgradeToVersion1 = setUpgraded
-
- x = pickle.loads(pklA)
- styles.doUpgrade()
- self.assertTrue(x.y.upgraded)
-
-
- class VersionedSubClass(styles.Versioned):
- pass
-
-
- class SecondVersionedSubClass(styles.Versioned):
- pass
-
-
- class VersionedSubSubClass(VersionedSubClass):
- pass
-
-
- class VersionedDiamondSubClass(VersionedSubSubClass, SecondVersionedSubClass):
- pass
-
-
- class AybabtuTests(TestCase):
- """
- L{styles._aybabtu} gets all of classes in the inheritance hierarchy of its
- argument that are strictly between L{Versioned} and the class itself.
- """
-
- def test_aybabtuStrictEmpty(self):
- """
- L{styles._aybabtu} of L{Versioned} itself is an empty list.
- """
- self.assertEqual(styles._aybabtu(styles.Versioned), [])
-
- def test_aybabtuStrictSubclass(self):
- """
- There are no classes I{between} L{VersionedSubClass} and L{Versioned},
- so L{styles._aybabtu} returns an empty list.
- """
- self.assertEqual(styles._aybabtu(VersionedSubClass), [])
-
- def test_aybabtuSubsubclass(self):
- """
- With a sub-sub-class of L{Versioned}, L{styles._aybabtu} returns a list
- containing the intervening subclass.
- """
- self.assertEqual(styles._aybabtu(VersionedSubSubClass), [VersionedSubClass])
-
- def test_aybabtuStrict(self):
- """
- For a diamond-shaped inheritance graph, L{styles._aybabtu} returns a
- list containing I{both} intermediate subclasses.
- """
- self.assertEqual(
- styles._aybabtu(VersionedDiamondSubClass),
- [VersionedSubSubClass, VersionedSubClass, SecondVersionedSubClass],
- )
-
-
- class MyEphemeral(styles.Ephemeral):
- def __init__(self, x):
- self.x = x
-
-
- class EphemeralTests(TestCase):
- def test_ephemeral(self):
- o = MyEphemeral(3)
- self.assertEqual(o.__class__, MyEphemeral)
- self.assertEqual(o.x, 3)
-
- pickl = pickle.dumps(o)
- o = pickle.loads(pickl)
-
- self.assertEqual(o.__class__, styles.Ephemeral)
- self.assertFalse(hasattr(o, "x"))
-
-
- class Pickleable:
- def __init__(self, x):
- self.x = x
-
- def getX(self):
- return self.x
-
-
- class NotPickleable:
- """
- A class that is not pickleable.
- """
-
- def __reduce__(self):
- """
- Raise an exception instead of pickling.
- """
- raise TypeError("Not serializable.")
-
-
- class CopyRegistered:
- """
- A class that is pickleable only because it is registered with the
- C{copyreg} module.
- """
-
- def __init__(self):
- """
- Ensure that this object is normally not pickleable.
- """
- self.notPickleable = NotPickleable()
-
-
- class CopyRegisteredLoaded:
- """
- L{CopyRegistered} after unserialization.
- """
-
-
- def reduceCopyRegistered(cr):
- """
- Externally implement C{__reduce__} for L{CopyRegistered}.
-
- @param cr: The L{CopyRegistered} instance.
-
- @return: a 2-tuple of callable and argument list, in this case
- L{CopyRegisteredLoaded} and no arguments.
- """
- return CopyRegisteredLoaded, ()
-
-
- copyreg.pickle(CopyRegistered, reduceCopyRegistered)
-
-
- class A:
- """
- dummy class
- """
-
- def amethod(self):
- pass
-
-
- class B:
- """
- dummy class
- """
-
- def bmethod(self):
- pass
-
-
- def funktion():
- pass
-
-
- class PicklingTests(TestCase):
- """Test pickling of extra object types."""
-
- def test_module(self):
- pickl = pickle.dumps(styles)
- o = pickle.loads(pickl)
- self.assertEqual(o, styles)
-
- def test_instanceMethod(self):
- obj = Pickleable(4)
- pickl = pickle.dumps(obj.getX)
- o = pickle.loads(pickl)
- self.assertEqual(o(), 4)
- self.assertEqual(type(o), type(obj.getX))
-
-
- class StringIOTransitionTests(TestCase):
- """
- When pickling a cStringIO in Python 2, it should unpickle as a BytesIO or a
- StringIO in Python 3, depending on the type of its contents.
- """
-
- def test_unpickleBytesIO(self):
- """
- A cStringIO pickled with bytes in it will yield an L{io.BytesIO} on
- python 3.
- """
- pickledStringIWithText = (
- b"ctwisted.persisted.styles\nunpickleStringI\np0\n"
- b"(S'test'\np1\nI0\ntp2\nRp3\n."
- )
- loaded = pickle.loads(pickledStringIWithText)
- self.assertIsInstance(loaded, io.StringIO)
- self.assertEqual(loaded.getvalue(), "test")
-
-
- class EvilSourceror:
- def __init__(self, x):
- self.a = self
- self.a.b = self
- self.a.b.c = x
-
-
- class NonDictState:
- def __getstate__(self):
- return self.state
-
- def __setstate__(self, state):
- self.state = state
-
-
- class AOTTests(TestCase):
- def test_simpleTypes(self):
- obj = (
- 1,
- 2.0,
- 3j,
- True,
- slice(1, 2, 3),
- "hello",
- "world",
- sys.maxsize + 1,
- None,
- Ellipsis,
- )
- rtObj = aot.unjellyFromSource(aot.jellyToSource(obj))
- self.assertEqual(obj, rtObj)
-
- def test_methodSelfIdentity(self):
- a = A()
- b = B()
- a.bmethod = b.bmethod
- b.a = a
- im_ = aot.unjellyFromSource(aot.jellyToSource(b)).a.bmethod
- self.assertEqual(aot._selfOfMethod(im_).__class__, aot._classOfMethod(im_))
-
- def test_methodNotSelfIdentity(self):
- """
- If a class change after an instance has been created,
- L{aot.unjellyFromSource} shoud raise a C{TypeError} when trying to
- unjelly the instance.
- """
- a = A()
- b = B()
- a.bmethod = b.bmethod
- b.a = a
- savedbmethod = B.bmethod
- del B.bmethod
- try:
- self.assertRaises(TypeError, aot.unjellyFromSource, aot.jellyToSource(b))
- finally:
- B.bmethod = savedbmethod
-
- def test_unsupportedType(self):
- """
- L{aot.jellyToSource} should raise a C{TypeError} when trying to jelly
- an unknown type without a C{__dict__} property or C{__getstate__}
- method.
- """
-
- class UnknownType:
- @property
- def __dict__(self):
- raise AttributeError()
-
- self.assertRaises(TypeError, aot.jellyToSource, UnknownType())
-
- def test_basicIdentity(self):
- # Anyone wanting to make this datastructure more complex, and thus this
- # test more comprehensive, is welcome to do so.
- aj = aot.AOTJellier().jellyToAO
- d = {"hello": "world", "method": aj}
- l = [
- 1,
- 2,
- 3,
- 'he\tllo\n\n"x world!',
- "goodbye \n\t\u1010 world!",
- 1,
- 1.0,
- 100 ** 100,
- unittest,
- aot.AOTJellier,
- d,
- funktion,
- ]
- t = tuple(l)
- l.append(l)
- l.append(t)
- l.append(t)
- uj = aot.unjellyFromSource(aot.jellyToSource([l, l]))
- assert uj[0] is uj[1]
- assert uj[1][0:5] == l[0:5]
-
- def test_nonDictState(self):
- a = NonDictState()
- a.state = "meringue!"
- assert aot.unjellyFromSource(aot.jellyToSource(a)).state == a.state
-
- def test_copyReg(self):
- """
- L{aot.jellyToSource} and L{aot.unjellyFromSource} honor functions
- registered in the pickle copy registry.
- """
- uj = aot.unjellyFromSource(aot.jellyToSource(CopyRegistered()))
- self.assertIsInstance(uj, CopyRegisteredLoaded)
-
- def test_funkyReferences(self):
- o = EvilSourceror(EvilSourceror([]))
- j1 = aot.jellyToAOT(o)
- oj = aot.unjellyFromAOT(j1)
-
- assert oj.a is oj
- assert oj.a.b is oj.b
- assert oj.c is not oj.c.c
-
- def test_circularTuple(self):
- """
- L{aot.jellyToAOT} can persist circular references through tuples.
- """
- l = []
- t = (l, 4321)
- l.append(t)
- j1 = aot.jellyToAOT(l)
- oj = aot.unjellyFromAOT(j1)
- self.assertIsInstance(oj[0], tuple)
- self.assertIs(oj[0][0], oj)
- self.assertEqual(oj[0][1], 4321)
-
-
- class CrefUtilTests(TestCase):
- """
- Tests for L{crefutil}.
- """
-
- def test_dictUnknownKey(self):
- """
- L{crefutil._DictKeyAndValue} only support keys C{0} and C{1}.
- """
- d = crefutil._DictKeyAndValue({})
- self.assertRaises(RuntimeError, d.__setitem__, 2, 3)
-
- def test_deferSetMultipleTimes(self):
- """
- L{crefutil._Defer} can be assigned a key only one time.
- """
- d = crefutil._Defer()
- d[0] = 1
- self.assertRaises(RuntimeError, d.__setitem__, 0, 1)
-
- def test_containerWhereAllElementsAreKnown(self):
- """
- A L{crefutil._Container} where all of its elements are known at
- construction time is nonsensical and will result in errors in any call
- to addDependant.
- """
- container = crefutil._Container([1, 2, 3], list)
- self.assertRaises(AssertionError, container.addDependant, {}, "ignore-me")
-
- def test_dontPutCircularReferencesInDictionaryKeys(self):
- """
- If a dictionary key contains a circular reference (which is probably a
- bad practice anyway) it will be resolved by a
- L{crefutil._DictKeyAndValue}, not by placing a L{crefutil.NotKnown}
- into a dictionary key.
- """
- self.assertRaises(
- AssertionError, dict().__setitem__, crefutil.NotKnown(), "value"
- )
-
- def test_dontCallInstanceMethodsThatArentReady(self):
- """
- L{crefutil._InstanceMethod} raises L{AssertionError} to indicate it
- should not be called. This should not be possible with any of its API
- clients, but is provided for helping to debug.
- """
- self.assertRaises(
- AssertionError,
- crefutil._InstanceMethod("no_name", crefutil.NotKnown(), type),
- )
-
-
- testCases = [VersionTests, EphemeralTests, PicklingTests]
|