[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