[Checkins] SVN: persistent/trunk/ Convert 'edge case' doctests into unit tests.
Tres Seaver
cvs-admin at zope.org
Fri Jun 29 05:19:59 UTC 2012
Log message for revision 127184:
Convert 'edge case' doctests into unit tests.
Fix a couple of discrepancies in the Python persistence implementation
uncovered thereby.
Changed:
_U persistent/trunk/
U persistent/trunk/persistent/persistence.py
D persistent/trunk/persistent/tests/persistent.txt
U persistent/trunk/persistent/tests/test_persistence.py
-=-
Modified: persistent/trunk/persistent/persistence.py
===================================================================
--- persistent/trunk/persistent/persistence.py 2012-06-29 05:19:51 UTC (rev 127183)
+++ persistent/trunk/persistent/persistence.py 2012-06-29 05:19:55 UTC (rev 127184)
@@ -307,10 +307,15 @@
def _p_activate(self):
""" See IPersistent.
"""
+ before = self.__flags
if self.__flags is None:
self.__flags = 0
if self.__jar is not None and self.__oid is not None:
- self.__jar.setstate(self)
+ try:
+ self.__jar.setstate(self)
+ except:
+ self.__flags = before
+ raise
def _p_deactivate(self):
""" See IPersistent.
@@ -366,9 +371,10 @@
def _p_set_changed_flag(self, value):
if value:
before = self.__flags
- self.__flags |= _CHANGED
- if before != self.__flags:
+ after = self.__flags | _CHANGED
+ if before != after:
self._p_register()
+ self.__flags = after
else:
self.__flags &= ~_CHANGED
Deleted: persistent/trunk/persistent/tests/persistent.txt
===================================================================
--- persistent/trunk/persistent/tests/persistent.txt 2012-06-29 05:19:51 UTC (rev 127183)
+++ persistent/trunk/persistent/tests/persistent.txt 2012-06-29 05:19:55 UTC (rev 127184)
@@ -1,244 +0,0 @@
-Tests for `persistent.Persistent`
-=================================
-
-This document covers "edge case" tests for the Persistent base class.
-It should be replaced with normal unit tests. (The meat of the narrative
-documentation is now in ``docs/usage`` of the distribution).
-
-The tests use stub data managers. A data manager is responsible for
-loading and storing the state of a persistent object. It's stored in
-the ``_p_jar`` attribute of a persistent object.
-
- >>> class DM:
- ... def __init__(self):
- ... self.called = 0
- ... def register(self, ob):
- ... self.called += 1
- ... def setstate(self, ob):
- ... ob.__setstate__({'x': 42})
-
- >>> class BrokenDM(DM):
- ... def register(self,ob):
- ... self.called += 1
- ... raise NotImplementedError
- ... def setstate(self,ob):
- ... raise NotImplementedError
-
- >>> from persistent import Persistent
-
-
-Test failures
--------------
-
-The following tests cover various errors cases.
-
-When an object is modified, it registers with its data manager. If that
-registration fails, the exception is propagated and the object stays in the
-up-to-date state. It shouldn't change to the modified state, because it won't
-be saved when the transaction commits.
-
- >>> from persistent import Persistent
- >>> class P(Persistent):
- ... def __init__(self):
- ... self.x = 0
- ... def inc(self):
- ... self.x += 1
- >>>
- >>> p = P()
- >>> p._p_oid = 1
- >>> p._p_jar = BrokenDM()
- >>> p._p_state
- 0
- >>> p._p_jar.called
- 0
- >>> p._p_changed = 1
- Traceback (most recent call last):
- ...
- NotImplementedError
- >>> p._p_jar.called
- 1
- >>> p._p_state
- 0
-
-Make sure that exceptions that occur inside the data manager's ``setstate()``
-method propagate out to the caller.
-
- >>> p = P()
- >>> p._p_oid = 1
- >>> p._p_jar = BrokenDM()
- >>> p._p_deactivate()
- >>> p._p_state
- -1
- >>> p._p_activate()
- Traceback (most recent call last):
- ...
- NotImplementedError
- >>> p._p_state
- -1
-
-
-Special test to cover layout of ``__dict__``
---------------------------------------------
-
-We once had a bug in the `Persistent` class that calculated an incorrect
-offset for the ``__dict__`` attribute. It assigned ``__dict__`` and
-``_p_jar`` to the same location in memory. This is a simple test to make sure
-they have different locations.
-
- >>> p = P()
- >>> p.inc()
- >>> p.inc()
- >>> 'x' in p.__dict__
- True
- >>> p._p_jar
-
-
-Inheritance and metaclasses
----------------------------
-
-Simple tests to make sure it's possible to inherit from the `Persistent` base
-class multiple times. There used to be metaclasses involved in `Persistent`
-that probably made this a more interesting test.
-
- >>> class A(Persistent):
- ... pass
- >>> class B(Persistent):
- ... pass
- >>> class C(A, B):
- ... pass
- >>> class D(object):
- ... pass
- >>> class E(D, B):
- ... pass
- >>> a = A()
- >>> b = B()
- >>> c = C()
- >>> d = D()
- >>> e = E()
-
-Also make sure that it's possible to define `Persistent` classes that have a
-custom metaclass.
-
- >>> class alternateMeta(type):
- ... type
- >>> class alternate(object):
- ... __metaclass__ = alternateMeta
- >>> class mixedMeta(alternateMeta, type):
- ... pass
- >>> class mixed(alternate, Persistent):
- ... pass
- >>> class mixed(Persistent, alternate):
- ... pass
-
-
-Basic type structure
---------------------
-
- >>> Persistent.__dictoffset__
- 0
- >>> Persistent.__weakrefoffset__
- 0
- >>> Persistent.__basicsize__ > object.__basicsize__
- True
- >>> P.__dictoffset__ > 0
- True
- >>> P.__weakrefoffset__ > 0
- True
- >>> P.__dictoffset__ < P.__weakrefoffset__
- True
- >>> P.__basicsize__ > Persistent.__basicsize__
- True
-
-
-Slots
------
-
-These are some simple tests of classes that have an ``__slots__``
-attribute. Some of the classes should have slots, others shouldn't.
-
- >>> class noDict(object):
- ... __slots__ = ['foo']
- >>> class p_noDict(Persistent):
- ... __slots__ = ['foo']
- >>> class p_shouldHaveDict(p_noDict):
- ... pass
-
- >>> p_noDict.__dictoffset__
- 0
- >>> x = p_noDict()
- >>> x.foo = 1
- >>> x.foo
- 1
- >>> x.bar = 1
- Traceback (most recent call last):
- ...
- AttributeError: 'p_noDict' object has no attribute 'bar'
- >>> x._v_bar = 1
- Traceback (most recent call last):
- ...
- AttributeError: 'p_noDict' object has no attribute '_v_bar'
- >>> x.__dict__
- Traceback (most recent call last):
- ...
- AttributeError: 'p_noDict' object has no attribute '__dict__'
-
- The various _p_ attributes are unaffected by slots.
- >>> p._p_oid
- >>> p._p_jar
- >>> p._p_state
- 0
-
-If the most-derived class does not specify
-
- >>> p_shouldHaveDict.__dictoffset__ > 0
- True
- >>> x = p_shouldHaveDict()
- >>> isinstance(x.__dict__, dict)
- True
-
-
-Pickling
---------
-
-There's actually a substantial effort involved in making subclasses of
-`Persistent` work with plain-old pickle. The ZODB serialization layer never
-calls pickle on an object; it pickles the object's class description and its
-state as two separate pickles.
-
- >>> import pickle
- >>> p = P()
- >>> p.inc()
- >>> p2 = pickle.loads(pickle.dumps(p))
- >>> p2.__class__ is P
- True
- >>> p2.x == p.x
- True
-
-We should also test that pickle works with custom getstate and setstate.
-Perhaps even reduce. The problem is that pickling depends on finding the
-class in a particular module, and classes defined here won't appear in any
-module. We could require each user of the tests to define a base class, but
-that might be tedious.
-
-
-Interfaces
-----------
-
-Some versions of Zope and ZODB have the `zope.interface` package available.
-If it is available, then persistent will be associated with several
-interfaces. It's hard to write a doctest test that runs the tests only if
-`zope.interface` is available, so this test looks a little unusual. One
-problem is that the assert statements won't do anything if you run with `-O`.
-
- >>> try:
- ... import zope.interface
- ... except ImportError:
- ... pass
- ... else:
- ... from persistent.interfaces import IPersistent
- ... assert IPersistent.implementedBy(Persistent)
- ... p = Persistent()
- ... assert IPersistent.providedBy(p)
- ... assert IPersistent.implementedBy(P)
- ... p = P()
- ... assert IPersistent.providedBy(p)
Modified: persistent/trunk/persistent/tests/test_persistence.py
===================================================================
--- persistent/trunk/persistent/tests/test_persistence.py 2012-06-29 05:19:51 UTC (rev 127183)
+++ persistent/trunk/persistent/tests/test_persistence.py 2012-06-29 05:19:55 UTC (rev 127184)
@@ -36,6 +36,24 @@
jar._cache = self._makeCache(jar)
return jar
+ def _makeBrokenJar(self):
+ from zope.interface import implementer
+ from persistent.interfaces import IPersistentDataManager
+
+ @implementer(IPersistentDataManager)
+ class _BrokenJar(object):
+ def __init__(self):
+ self.called = 0
+ def register(self,ob):
+ self.called += 1
+ raise NotImplementedError
+ def setstate(self,ob):
+ raise NotImplementedError
+
+ jar = _BrokenJar()
+ jar._cache = self._makeCache(jar)
+ return jar
+
def _makeOneWithJar(self, klass=None):
from persistent.timestamp import _makeOctets
OID = _makeOctets('\x01' * 8)
@@ -1172,6 +1190,90 @@
self.assertEqual(list(jar._loaded), [OID])
self._checkMRU(jar, [OID])
+ def test_set__p_changed_w_broken_jar(self):
+ # When an object is modified, it registers with its data manager.
+ # If that registration fails, the exception is propagated and the
+ # object stays in the up-to-date state.
+ # It shouldn't change to the modified state, because it won't
+ # be saved when the transaction commits.
+ from persistent._compat import _b
+ class P(self._getTargetClass()):
+ def __init__(self):
+ self.x = 0
+ def inc(self):
+ self.x += 1
+ p = P()
+ p._p_oid = _b('1')
+ p._p_jar = self._makeBrokenJar()
+ self.assertEqual(p._p_state, 0)
+ self.assertEqual(p._p_jar.called, 0)
+ def _try():
+ p._p_changed = 1
+ self.assertRaises(NotImplementedError, _try)
+ self.assertEqual(p._p_jar.called, 1)
+ self.assertEqual(p._p_state, 0)
+
+ def test__p_activate_w_broken_jar(self):
+ # Make sure that exceptions that occur inside the data manager's
+ # ``setstate()`` method propagate out to the caller.
+ from persistent._compat import _b
+ class P(self._getTargetClass()):
+ def __init__(self):
+ self.x = 0
+ def inc(self):
+ self.x += 1
+ p = P()
+ p._p_oid = _b('1')
+ p._p_jar = self._makeBrokenJar()
+ p._p_deactivate()
+ self.assertEqual(p._p_state, -1)
+ self.assertRaises(NotImplementedError, p._p_activate)
+ self.assertEqual(p._p_state, -1)
+
+ def test__ancient_dict_layout_bug(self):
+ # We once had a bug in the `Persistent` class that calculated an
+ # incorrect offset for the ``__dict__`` attribute. It assigned
+ # ``__dict__`` and ``_p_jar`` to the same location in memory.
+ # This is a simple test to make sure they have different locations.
+ class P(self._getTargetClass()):
+ def __init__(self):
+ self.x = 0
+ def inc(self):
+ self.x += 1
+ p = P()
+ p.inc()
+ p.inc()
+ self.assertTrue('x' in p.__dict__)
+ self.assertTrue(p._p_jar is None)
+
+ def test_w_diamond_inheritance(self):
+ class A(self._getTargetClass()):
+ pass
+ class B(self._getTargetClass()):
+ pass
+ class C(A, B):
+ pass
+ class D(object):
+ pass
+ class E(D, B):
+ pass
+ # no raise
+ A(), B(), C(), D(), E()
+
+ def test_w_alternate_metaclass(self):
+ class alternateMeta(type):
+ pass
+ class alternate(object):
+ __metaclass__ = alternateMeta
+ class mixedMeta(alternateMeta, type):
+ pass
+ # no raise
+ class mixed1(alternate, self._getTargetClass()):
+ pass
+ class mixed2(self._getTargetClass(), alternate):
+ pass
+
+
class PyPersistentTests(unittest.TestCase, _Persistent_Base):
def _getTargetClass(self):
More information about the checkins
mailing list