[Zodb-checkins] CVS: Zope/lib/python/ZODB/tests - sampledm.py:1.2 testConnection.py:1.2 testSerialize.py:1.2 test_datamanageradapter.py:1.2 util.py:1.2 ConflictResolution.py:1.14 StorageTestBase.py:1.33 testCache.py:1.18 testTransaction.py:1.18 testUtils.py:1.6

Jeremy Hylton jeremy at zope.com
Wed Feb 18 21:59:11 EST 2004


Update of /cvs-repository/Zope/lib/python/ZODB/tests
In directory cvs.zope.org:/tmp/cvs-serv11908/lib/python/ZODB/tests

Modified Files:
	ConflictResolution.py StorageTestBase.py testCache.py 
	testTransaction.py testUtils.py 
Added Files:
	sampledm.py testConnection.py testSerialize.py 
	test_datamanageradapter.py util.py 
Log Message:
Merge zope3-zodb3-devel-branch to the Zope head (Zope 2 head).

Add support for persistent weak references.  The implementation is in
pure Python right now; coptimizations.c was disabled.  We need to
restore the C code before going to beta.

The persistent reference format has evolved a little, but the code on
the branch doesn't document it.  The ref is usually a an oid-class
pair, where the class is an actual class object.  It can also be a
list, for weak references, or a bare oid.

Add support for data managers written against the ZODB4 transaction
API.  The transaction implementation provides an adapter between the
two interfaces.  There's a substantial doctest test suite for this
feature.

Add add() method to Connection to explicitly add an object and get it
an assigned _p_oid.  It's possible to add an unconnected object; this
isn't recommended, because it will be lost at pack time.


=== Zope/lib/python/ZODB/tests/sampledm.py 1.1 => 1.2 ===
--- /dev/null	Wed Feb 18 21:59:11 2004
+++ Zope/lib/python/ZODB/tests/sampledm.py	Wed Feb 18 21:59:10 2004
@@ -0,0 +1,412 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Sample objects for use in tests
+
+$Id$
+"""
+
+class DataManager(object):
+    """Sample data manager
+
+       This class provides a trivial data-manager implementation and doc
+       strings to illustrate the the protocol and to provide a tool for
+       writing tests.
+
+       Our sample data manager has state that is updated through an inc
+       method and through transaction operations.
+
+       When we create a sample data manager:
+
+       >>> dm = DataManager()
+
+       It has two bits of state, state:
+
+       >>> dm.state
+       0
+
+       and delta:
+
+       >>> dm.delta
+       0
+
+       Both of which are initialized to 0.  state is meant to model
+       committed state, while delta represents tentative changes within a
+       transaction.  We change the state by calling inc:
+
+       >>> dm.inc()
+
+       which updates delta:
+
+       >>> dm.delta
+       1
+
+       but state isn't changed until we commit the transaction:
+
+       >>> dm.state
+       0
+
+       To commit the changes, we use 2-phase commit. We execute the first
+       stage by calling prepare.  We need to pass a transation. Our
+       sample data managers don't really use the transactions for much,
+       so we'll be lazy and use strings for transactions:
+
+       >>> t1 = '1'
+       >>> dm.prepare(t1)
+
+       The sample data manager updates the state when we call prepare:
+
+       >>> dm.state
+       1
+       >>> dm.delta
+       1
+
+       This is mainly so we can detect some affect of calling the methods.
+
+       Now if we call commit:
+
+       >>> dm.commit(t1)
+
+       Our changes are"permanent".  The state reflects the changes and the
+       delta has been reset to 0. 
+
+       >>> dm.state
+       1
+       >>> dm.delta
+       0
+       """
+
+    def __init__(self):
+        self.state = 0
+        self.sp = 0
+        self.transaction = None
+        self.delta = 0
+        self.prepared = False
+
+    def inc(self, n=1):
+        self.delta += n
+
+    def prepare(self, transaction):
+        """Prepare to commit data
+
+        >>> dm = DataManager()
+        >>> dm.inc()
+        >>> t1 = '1'
+        >>> dm.prepare(t1)
+        >>> dm.commit(t1)
+        >>> dm.state
+        1
+        >>> dm.inc()
+        >>> t2 = '2'
+        >>> dm.prepare(t2)
+        >>> dm.abort(t2)
+        >>> dm.state
+        1
+
+        It is en error to call prepare more than once without an intervening
+        commit or abort:
+
+        >>> dm.prepare(t1)
+
+        >>> dm.prepare(t1)
+        Traceback (most recent call last):
+        ...
+        TypeError: Already prepared
+
+        >>> dm.prepare(t2)
+        Traceback (most recent call last):
+        ...
+        TypeError: Already prepared
+
+        >>> dm.abort(t1)
+
+        If there was a preceeding savepoint, the transaction must match:
+
+        >>> rollback = dm.savepoint(t1)
+        >>> dm.prepare(t2)
+        Traceback (most recent call last):
+        ,,,
+        TypeError: ('Transaction missmatch', '2', '1')
+
+        >>> dm.prepare(t1)
+        
+        """
+        if self.prepared:
+            raise TypeError('Already prepared')
+        self._checkTransaction(transaction)
+        self.prepared = True
+        self.transaction = transaction
+        self.state += self.delta
+
+    def _checkTransaction(self, transaction):
+        if (transaction is not self.transaction
+            and self.transaction is not None):
+            raise TypeError("Transaction missmatch",
+                            transaction, self.transaction)
+
+    def abort(self, transaction):
+        """Abort a transaction
+
+        The abort method can be called before two-phase commit to
+        throw away work done in the transaction:
+
+        >>> dm = DataManager()
+        >>> dm.inc()
+        >>> dm.state, dm.delta
+        (0, 1)
+        >>> t1 = '1'
+        >>> dm.abort(t1)
+        >>> dm.state, dm.delta
+        (0, 0)
+
+        The abort method also throws away work done in savepoints:
+
+        >>> dm.inc()
+        >>> r = dm.savepoint(t1)
+        >>> dm.inc()
+        >>> r = dm.savepoint(t1)
+        >>> dm.state, dm.delta
+        (0, 2)
+        >>> dm.abort(t1)
+        >>> dm.state, dm.delta
+        (0, 0)
+
+        If savepoints are used, abort must be passed the same
+        transaction:
+        
+        >>> dm.inc()
+        >>> r = dm.savepoint(t1)
+        >>> t2 = '2'
+        >>> dm.abort(t2)
+        Traceback (most recent call last):
+        ...
+        TypeError: ('Transaction missmatch', '2', '1')
+
+        >>> dm.abort(t1)
+
+        The abort method is also used to abort a two-phase commit:
+
+        >>> dm.inc()
+        >>> dm.state, dm.delta
+        (0, 1)
+        >>> dm.prepare(t1)
+        >>> dm.state, dm.delta
+        (1, 1)
+        >>> dm.abort(t1)
+        >>> dm.state, dm.delta
+        (0, 0)
+
+        Of course, the transactions passed to prepare and abort must
+        match:
+        
+        >>> dm.prepare(t1)
+        >>> dm.abort(t2)
+        Traceback (most recent call last):
+        ...
+        TypeError: ('Transaction missmatch', '2', '1')
+        
+        >>> dm.abort(t1)
+        
+
+        """
+        self._checkTransaction(transaction)
+        if self.transaction is not None:
+            self.transaction = None
+
+        if self.prepared:
+            self.state -= self.delta
+            self.prepared = False
+
+        self.delta = 0
+
+    def commit(self, transaction):
+        """Complete two-phase commit
+
+        >>> dm = DataManager()
+        >>> dm.state
+        0
+        >>> dm.inc()
+
+        We start two-phase commit by calling prepare:
+
+        >>> t1 = '1'
+        >>> dm.prepare(t1)
+
+        We complete it by calling commit:
+
+        >>> dm.commit(t1)
+        >>> dm.state
+        1
+
+        It is an error ro call commit without calling prepare first:
+
+        >>> dm.inc()
+        >>> t2 = '2'
+        >>> dm.commit(t2)
+        Traceback (most recent call last):
+        ...
+        TypeError: Not prepared to commit
+
+        >>> dm.prepare(t2)
+        >>> dm.commit(t2)
+
+        If course, the transactions given to prepare and commit must
+        be the same:
+        
+        >>> dm.inc()
+        >>> t3 = '3'
+        >>> dm.prepare(t3)
+        >>> dm.commit(t2)
+        Traceback (most recent call last):
+        ...
+        TypeError: ('Transaction missmatch', '2', '3')
+        
+        """
+        if not self.prepared:
+            raise TypeError('Not prepared to commit')
+        self._checkTransaction(transaction)
+        self.delta = 0
+        self.transaction = None
+        self.prepared = False
+
+    def savepoint(self, transaction):
+        """Provide the ability to rollback transaction state
+
+        Savepoints provide a way to:
+
+        - Save partial transaction work. For some data managers, this
+          could allow resources to be used more efficiently.
+
+        - Provide the ability to revert state to a point in a
+          transaction without aborting the entire transaction.  In
+          other words, savepoints support partial aborts.
+
+        Savepoints don't use two-phase commit. If there are errors in
+        setting or rolling back to savepoints, the application should
+        abort the containing transaction.  This is *not* the
+        responsibility of the data manager.
+
+        Savepoints are always associated with a transaction. Any work
+        done in a savepoint's transaction is tentative until the
+        transaction is committed using two-phase commit.
+
+        >>> dm = DataManager()
+        >>> dm.inc()
+        >>> t1 = '1'
+        >>> r = dm.savepoint(t1)
+        >>> dm.state, dm.delta
+        (0, 1)
+        >>> dm.inc()
+        >>> dm.state, dm.delta
+        (0, 2)
+        >>> r.rollback()
+        >>> dm.state, dm.delta
+        (0, 1)
+        >>> dm.prepare(t1)
+        >>> dm.commit(t1)
+        >>> dm.state, dm.delta
+        (1, 0)
+
+        Savepoints must have the same transaction:
+
+        >>> r1 = dm.savepoint(t1)
+        >>> dm.state, dm.delta
+        (1, 0)
+        >>> dm.inc()
+        >>> dm.state, dm.delta
+        (1, 1)
+        >>> t2 = '2'
+        >>> r2 = dm.savepoint(t2)
+        Traceback (most recent call last):
+        ...
+        TypeError: ('Transaction missmatch', '2', '1')
+
+        >>> r2 = dm.savepoint(t1)
+        >>> dm.inc()
+        >>> dm.state, dm.delta
+        (1, 2)
+
+        If we rollback to an earlier savepoint, we discard all work
+        done later:
+
+        >>> r1.rollback()
+        >>> dm.state, dm.delta
+        (1, 0)
+
+        and we can no longer rollback to the later savepoint:
+
+        >>> r2.rollback()
+        Traceback (most recent call last):
+        ...
+        TypeError: ('Attempt to roll back to invalid save point', 3, 2)
+
+        We can roll back to a savepoint as often as we like:
+
+        >>> r1.rollback()
+        >>> r1.rollback()
+        >>> r1.rollback()
+        >>> dm.state, dm.delta
+        (1, 0)
+
+        >>> dm.inc()
+        >>> dm.inc()
+        >>> dm.inc()
+        >>> dm.state, dm.delta
+        (1, 3)
+        >>> r1.rollback()
+        >>> dm.state, dm.delta
+        (1, 0)
+
+        But we can't rollback to a savepoint after it has been
+        committed:
+
+        >>> dm.prepare(t1)
+        >>> dm.commit(t1)
+
+        >>> r1.rollback()
+        Traceback (most recent call last):
+        ...
+        TypeError: Attempt to rollback stale rollback
+
+        """
+        if self.prepared:
+            raise TypeError("Can't get savepoint during two-phase commit")
+        self._checkTransaction(transaction)
+        self.transaction = transaction
+        self.sp += 1
+        return Rollback(self)
+
+class Rollback(object):
+
+    def __init__(self, dm):
+        self.dm = dm
+        self.sp = dm.sp
+        self.delta = dm.delta
+        self.transaction = dm.transaction
+
+    def rollback(self):
+        if self.transaction is not self.dm.transaction:
+            raise TypeError("Attempt to rollback stale rollback")
+        if self.dm.sp < self.sp:
+            raise TypeError("Attempt to roll back to invalid save point",
+                            self.sp, self.dm.sp)
+        self.dm.sp = self.sp
+        self.dm.delta = self.delta
+
+
+def test_suite():
+    from doctest import DocTestSuite
+    return DocTestSuite()
+
+if __name__ == '__main__':
+    unittest.main()


=== Zope/lib/python/ZODB/tests/testConnection.py 1.1 => 1.2 ===
--- /dev/null	Wed Feb 18 21:59:11 2004
+++ Zope/lib/python/ZODB/tests/testConnection.py	Wed Feb 18 21:59:10 2004
@@ -0,0 +1,269 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Unit tests for the Connection class.
+"""
+
+import unittest
+from persistent import Persistent
+
+__metaclass__ = type
+
+class ConnectionDotAdd(unittest.TestCase):
+
+    def setUp(self):
+        from ZODB.Connection import Connection
+        self.datamgr = Connection()
+        self.db = StubDatabase()
+        self.datamgr._setDB(self.db)
+        self.transaction = StubTransaction()
+
+    def check_add(self):
+        from ZODB.POSException import InvalidObjectReference
+        obj = StubObject()
+        self.assert_(obj._p_oid is None)
+        self.assert_(obj._p_jar is None)
+        self.datamgr.add(obj)
+        self.assert_(obj._p_oid is not None)
+        self.assert_(obj._p_jar is self.datamgr)
+        self.assert_(self.datamgr[obj._p_oid] is obj)
+
+        # Only first-class persistent objects may be added.
+        self.assertRaises(TypeError, self.datamgr.add, object())
+
+        # Adding to the same connection does not fail. Object keeps the
+        # same oid.
+        oid = obj._p_oid
+        self.datamgr.add(obj)
+        self.assertEqual(obj._p_oid, oid)
+
+        # Cannot add an object from a diffrerent connection.
+        obj2 = StubObject()
+        obj2._p_jar = object()
+        self.assertRaises(InvalidObjectReference, self.datamgr.add, obj2)
+
+    def checkResetOnAbort(self):
+        # Check that _p_oid and _p_jar are reset when a transaction is
+        # aborted.
+        obj = StubObject()
+        self.datamgr.add(obj)
+        oid = obj._p_oid
+        self.datamgr.abort(obj, self.transaction)
+        self.assert_(obj._p_oid is None)
+        self.assert_(obj._p_jar is None)
+        self.assertRaises(KeyError, self.datamgr.__getitem__, oid)
+
+    def checkResetOnTpcAbort(self):
+        obj = StubObject()
+        self.datamgr.add(obj)
+        oid = obj._p_oid
+
+        # This case simulates when an error occurred committing some other
+        # object, so tpc_abort is called, clearing everything.
+        self.datamgr.tpc_begin(self.transaction)
+        # Let's pretend something bad happens here.
+        self.datamgr.tpc_abort(self.transaction)
+        self.assert_(obj._p_oid is None)
+        self.assert_(obj._p_jar is None)
+        self.assertRaises(KeyError, self.datamgr.__getitem__, oid)
+
+    def checkTcpAbortAfterCommit(self):
+        obj = StubObject()
+        self.datamgr.add(obj)
+        oid = obj._p_oid
+
+        self.datamgr.tpc_begin(self.transaction)
+        self.datamgr.commit(obj, self.transaction)
+        # Let's pretend something bad happened here.
+        self.datamgr.tpc_abort(self.transaction)
+        self.assert_(obj._p_oid is None)
+        self.assert_(obj._p_jar is None)
+        self.assertRaises(KeyError, self.datamgr.__getitem__, oid)
+        self.assertEquals(self.db._storage._stored, [oid])
+
+    def checkCommit(self):
+        obj = StubObject()
+        self.datamgr.add(obj)
+        oid = obj._p_oid
+
+        self.datamgr.tpc_begin(self.transaction)
+        self.datamgr.commit(obj, self.transaction)
+        self.datamgr.tpc_finish(self.transaction)
+        self.assert_(obj._p_oid is oid)
+        self.assert_(obj._p_jar is self.datamgr)
+
+        # This next assert_ is covered by an assert in tpc_finish.
+        ##self.assert_(not self.datamgr._added)
+
+        self.assertEquals(self.db._storage._stored, [oid])
+        self.assertEquals(self.db._storage._finished, [oid])
+
+    def checkModifyOnGetstate(self):
+        subobj = StubObject()
+        obj = ModifyOnGetStateObject(subobj)
+        
+        self.datamgr.tpc_begin(self.transaction)
+        self.datamgr.commit(obj, self.transaction)
+        self.datamgr.tpc_finish(self.transaction)
+        storage = self.db._storage
+        self.assert_(obj._p_oid in storage._stored, "object was not stored")
+        self.assert_(subobj._p_oid in storage._stored,
+                "subobject was not stored")
+        self.assert_(self.datamgr._added_during_commit is None)
+
+    def checkErrorDuringCommit(self):
+        # We need to check that _added_during_commit still gets set to None
+        # when there is an error during commit()/
+        obj = ErrorOnGetstateObject()
+        
+        self.datamgr.tpc_begin(self.transaction)
+        self.assertRaises(ErrorOnGetstateException,
+                self.datamgr.commit, obj, self.transaction)
+        self.assert_(self.datamgr._added_during_commit is None)
+
+    def checkUnusedAddWorks(self):
+        # When an object is added, but not committed, it shouldn't be stored,
+        # but also it should be an error.
+        obj = StubObject()
+        self.datamgr.add(obj)
+        self.datamgr.tpc_begin(self.transaction)
+        self.datamgr.tpc_finish(self.transaction)
+        self.assert_(obj._p_oid not in self.datamgr._storage._stored)
+
+# ---- stubs
+
+class StubObject(Persistent):
+    pass
+
+
+class StubTransaction:
+    pass
+
+class ErrorOnGetstateException(Exception):
+    pass
+    
+class ErrorOnGetstateObject(Persistent):
+
+    def __getstate__(self):
+        raise ErrorOnGetstateException
+
+class ModifyOnGetStateObject(Persistent):
+
+    def __init__(self, p):
+        self._v_p = p
+
+    def __getstate__(self):
+        self._p_jar.add(self._v_p)
+        self.p = self._v_p
+        return Persistent.__getstate__(self)
+
+
+class StubStorage:
+    """Very simple in-memory storage that does *just* enough to support tests.
+
+    Only one concurrent transaction is supported.
+    Voting is not supported.
+    Versions are not supported.
+
+    Inspect self._stored and self._finished to see how the storage has been
+    used during a unit test. Whenever an object is stored in the store()
+    method, its oid is appended to self._stored. When a transaction is
+    finished, the oids that have been stored during the transaction are
+    appended to self._finished.
+    """
+
+    sortKey = 'StubStorage sortKey'
+
+    # internal
+    _oid = 1
+    _transaction = None
+
+    def __init__(self):
+        # internal
+        self._stored = []
+        self._finished = []
+        self._data = {}
+        self._transdata = {}
+        self._transstored = []
+
+    def new_oid(self):
+        oid = str(self._oid)
+        self._oid += 1
+        return oid
+
+    def tpc_begin(self, transaction):
+        if transaction is None:
+            raise TypeError('transaction may not be None')
+        elif self._transaction is None:
+            self._transaction = transaction
+        elif self._transaction != transaction:
+            raise RuntimeError(
+                'StubStorage uses only one transaction at a time')
+
+    def tpc_abort(self, transaction):
+        if transaction is None:
+            raise TypeError('transaction may not be None')
+        elif self._transaction != transaction:
+            raise RuntimeError(
+                'StubStorage uses only one transaction at a time')
+        del self._transaction
+        self._transdata.clear()
+
+    def tpc_finish(self, transaction, callback):
+        if transaction is None:
+            raise TypeError('transaction may not be None')
+        elif self._transaction != transaction:
+            raise RuntimeError(
+                'StubStorage uses only one transaction at a time')
+        self._finished.extend(self._transstored)
+        self._data.update(self._transdata)
+        callback(transaction)
+        del self._transaction
+        self._transdata.clear()
+        self._transstored = []
+
+    def load(self, oid, version):
+        if version != '':
+            raise TypeError('StubStorage does not support versions.')
+        return self._data[oid]
+
+    def store(self, oid, serial, p, version, transaction):
+        if version != '':
+            raise TypeError('StubStorage does not support versions.')
+        if transaction is None:
+            raise TypeError('transaction may not be None')
+        elif self._transaction != transaction:
+            raise RuntimeError(
+                'StubStorage uses only one transaction at a time')
+        self._stored.append(oid)
+        self._transstored.append(oid)
+        self._transdata[oid] = (p, serial)
+        # Explicitly returing None, as we're not pretending to be a ZEO
+        # storage
+        return None
+
+
+class StubDatabase:
+
+    def __init__(self):
+        self._storage = StubStorage()
+
+    _classFactory = None
+
+    def invalidate(self, transaction, dict_with_oid_keys, connection):
+        pass
+
+
+def test_suite():
+    s = unittest.makeSuite(ConnectionDotAdd, 'check')
+    return s


=== Zope/lib/python/ZODB/tests/testSerialize.py 1.1 => 1.2 ===
--- /dev/null	Wed Feb 18 21:59:11 2004
+++ Zope/lib/python/ZODB/tests/testSerialize.py	Wed Feb 18 21:59:10 2004
@@ -0,0 +1,121 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Tests of the serializer."""
+
+import cPickle
+import cStringIO as StringIO
+import sys
+import unittest
+
+from ZODB import serialize
+
+
+class ClassWithNewargs(int):
+    def __new__(cls, value):
+        return int.__new__(cls, value)
+
+    def __getnewargs__(self):
+        return int(self),
+
+class ClassWithoutNewargs(object):
+    def __init__(self, value):
+        self.value = value
+
+def make_pickle(ob):
+    sio = StringIO.StringIO()
+    p = cPickle.Pickler(sio, 1)
+    p.dump(ob)
+    return sio.getvalue()
+
+
+class SerializerTestCase(unittest.TestCase):
+
+    # old format:  (module, name), None
+    old_style_without_newargs = make_pickle(
+        ((__name__, "ClassWithoutNewargs"), None))
+
+    # old format:  (module, name), argtuple
+    old_style_with_newargs = make_pickle(
+        ((__name__, "ClassWithNewargs"), (1,)))
+
+    # new format:  klass
+    new_style_without_newargs = make_pickle(
+        ClassWithoutNewargs)
+
+    # new format:  klass, argtuple
+    new_style_with_newargs = make_pickle(
+        (ClassWithNewargs, (1,)))
+
+    def test_getClassName(self):
+        r = serialize.BaseObjectReader()
+        eq = self.assertEqual
+        eq(r.getClassName(self.old_style_with_newargs),
+           __name__ + ".ClassWithNewargs")
+        eq(r.getClassName(self.new_style_with_newargs),
+           __name__ + ".ClassWithNewargs")
+        eq(r.getClassName(self.old_style_without_newargs),
+           __name__ + ".ClassWithoutNewargs")
+        eq(r.getClassName(self.new_style_without_newargs),
+           __name__ + ".ClassWithoutNewargs")
+
+    def test_getGhost(self):
+        # Use a TestObjectReader since we need _get_class() to be
+        # implemented; otherwise this is just a BaseObjectReader.
+
+        class TestObjectReader(serialize.BaseObjectReader):
+            # A production object reader would optimize this, but we
+            # don't need to in a test
+            def _get_class(self, module, name):
+                __import__(module)
+                return getattr(sys.modules[module], name)
+
+        r = TestObjectReader()
+        g = r.getGhost(self.old_style_with_newargs)
+        self.assert_(isinstance(g, ClassWithNewargs))
+        self.assertEqual(g, 1)
+        g = r.getGhost(self.old_style_without_newargs)
+        self.assert_(isinstance(g, ClassWithoutNewargs))
+        g = r.getGhost(self.new_style_with_newargs)
+        self.assert_(isinstance(g, ClassWithNewargs))
+        g = r.getGhost(self.new_style_without_newargs)
+        self.assert_(isinstance(g, ClassWithoutNewargs))
+
+    def test_myhasattr(self):
+
+        class OldStyle:
+            bar = "bar"
+            def __getattr__(self, name):
+                if name == "error":
+                    raise ValueError("whee!")
+                else:
+                    raise AttributeError, name
+
+        class NewStyle(object):
+            bar = "bar"
+            def _raise(self):
+                raise ValueError("whee!")
+            error = property(_raise)
+
+        self.assertRaises(ValueError,
+                          serialize.myhasattr, OldStyle(), "error")
+        self.assertRaises(ValueError,
+                          serialize.myhasattr, NewStyle(), "error")
+        self.assert_(serialize.myhasattr(OldStyle(), "bar"))
+        self.assert_(serialize.myhasattr(NewStyle(), "bar"))
+        self.assert_(not serialize.myhasattr(OldStyle(), "rat"))
+        self.assert_(not serialize.myhasattr(NewStyle(), "rat"))
+
+
+def test_suite():
+    return unittest.makeSuite(SerializerTestCase)


=== Zope/lib/python/ZODB/tests/test_datamanageradapter.py 1.1 => 1.2 ===
--- /dev/null	Wed Feb 18 21:59:11 2004
+++ Zope/lib/python/ZODB/tests/test_datamanageradapter.py	Wed Feb 18 21:59:10 2004
@@ -0,0 +1,810 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""XXX short summary goes here.
+
+$Id$
+"""
+import unittest
+from doctest import DocTestSuite
+from ZODB.Transaction import DataManagerAdapter
+from ZODB.tests.sampledm import DataManager
+
+def test_normal_commit():
+    """
+    So, we have a data manager:
+
+    >>> dm = DataManager()
+
+    and we do some work that modifies uncommited state:
+
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    Now we'll commit the changes.  When the data manager joins a transaction,
+    the transaction will create an adapter.
+    
+    >>> dma = DataManagerAdapter(dm)
+
+    and register it as a modified object. At commit time, the
+    transaction will get the "jar" like this:
+
+    >>> jar = getattr(dma, '_p_jar', dma)
+
+    and, of course, the jar and the adapter will be the same:
+
+    >>> jar is dma
+    True
+
+    The transaction will call tpc_begin:
+
+    >>> t1 = '1'
+    >>> jar.tpc_begin(t1)
+
+    Then the transaction will call commit on the jar:
+
+    >>> jar.commit(dma, t1)
+
+    This doesn't actually do anything. :)
+
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    The transaction will then call tpc_vote:
+
+    >>> jar.tpc_vote(t1)
+
+    This prepares the data manager:
+
+    >>> dm.state, dm.delta
+    (1, 1)
+    >>> dm.prepared
+    True
+
+    Finally, tpc_finish is called:
+
+    >>> jar.tpc_finish(t1)
+
+    and the data manager finishes the two-phase commit:
+    
+    >>> dm.state, dm.delta
+    (1, 0)
+    >>> dm.prepared
+    False
+    """
+
+def test_abort():
+    """
+    So, we have a data manager:
+
+    >>> dm = DataManager()
+
+    and we do some work that modifies uncommited state:
+
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    When the data manager joins a transaction,
+    the transaction will create an adapter.
+    
+    >>> dma = DataManagerAdapter(dm)
+
+    and register it as a modified object.
+
+    Now we'll abort the transaction. The transaction will get the
+    "jar" like this:
+
+    >>> jar = getattr(dma, '_p_jar', dma)
+
+    and, of course, the jar and the adapter will be the same:
+
+    >>> jar is dma
+    True
+
+    Then the transaction will call abort on the jar:
+
+    >>> t1 = '1'
+    >>> jar.abort(dma, t1)
+
+    Which aborts the changes in the data manager:
+
+    >>> dm.state, dm.delta
+    (0, 0)
+    """
+
+def test_tpc_abort_phase1():
+    """
+    So, we have a data manager:
+
+    >>> dm = DataManager()
+
+    and we do some work that modifies uncommited state:
+
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    Now we'll commit the changes.  When the data manager joins a transaction,
+    the transaction will create an adapter.
+    
+    >>> dma = DataManagerAdapter(dm)
+
+    and register it as a modified object. At commit time, the
+    transaction will get the "jar" like this:
+
+    >>> jar = getattr(dma, '_p_jar', dma)
+
+    and, of course, the jar and the adapter will be the same:
+
+    >>> jar is dma
+    True
+
+    The transaction will call tpc_begin:
+
+    >>> t1 = '1'
+    >>> jar.tpc_begin(t1)
+
+    Then the transaction will call commit on the jar:
+
+    >>> jar.commit(dma, t1)
+
+    This doesn't actually do anything. :)
+
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    At this point, the transaction decides to abort. It calls tpc_abort:
+
+    >>> jar.tpc_abort(t1)
+
+    Which causes the state of the data manager to be restored:
+
+    >>> dm.state, dm.delta
+    (0, 0)
+    """
+
+def test_tpc_abort_phase2():
+    """
+    So, we have a data manager:
+
+    >>> dm = DataManager()
+
+    and we do some work that modifies uncommited state:
+
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    Now we'll commit the changes.  When the data manager joins a transaction,
+    the transaction will create an adapter.
+    
+    >>> dma = DataManagerAdapter(dm)
+
+    and register it as a modified object. At commit time, the
+    transaction will get the "jar" like this:
+
+    >>> jar = getattr(dma, '_p_jar', dma)
+
+    and, of course, the jar and the adapter will be the same:
+
+    >>> jar is dma
+    True
+
+    The transaction will call tpc_begin:
+
+    >>> t1 = '1'
+    >>> jar.tpc_begin(t1)
+
+    Then the transaction will call commit on the jar:
+
+    >>> jar.commit(dma, t1)
+
+    This doesn't actually do anything. :)
+
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    The transaction calls vote:
+
+    >>> jar.tpc_vote(t1)
+
+    This prepares the data manager:
+
+    >>> dm.state, dm.delta
+    (1, 1)
+    >>> dm.prepared
+    True
+
+    At this point, the transaction decides to abort. It calls tpc_abort:
+
+    >>> jar.tpc_abort(t1)
+
+    Which causes the state of the data manager to be restored:
+
+    >>> dm.state, dm.delta
+    (0, 0)
+    >>> dm.prepared
+    False
+    """
+
+def test_commit_w_subtransactions():
+    """
+    So, we have a data manager:
+
+    >>> dm = DataManager()
+
+    and we do some work that modifies uncommited state:
+
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    Now we'll commit the changes in a subtransaction.  When the data
+    manager joins a transaction, the transaction will create an
+    adapter.
+    
+    >>> dma = DataManagerAdapter(dm)
+
+    and register it as a modified object. At commit time, the
+    transaction will get the "jar" like this:
+
+    >>> jar = getattr(dma, '_p_jar', dma)
+
+    and, of course, the jar and the adapter will be the same:
+
+    >>> jar is dma
+    True
+
+    The transaction will call tpc_begin:
+
+    >>> t1 = '1'
+    >>> jar.tpc_begin(t1, 1) # 1 -> subtxn
+
+    Then the transaction will call commit on the jar:
+
+    >>> jar.commit(dma, t1)
+
+    This doesn't actually do anything. :)
+
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    The transaction will then call tpc_vote:
+
+    >>> jar.tpc_vote(t1)
+
+    This doesn't do anything either, because zodb4 data managers don't
+    actually do two-phase commit for subtransactions.
+
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    Finally, we call tpc_finish. This does actally create a savepoint,
+    but we can't really tell that from outside.
+
+    >>> jar.tpc_finish(t1)
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    We'll do more of the above:
+
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 2)
+    >>> jar.tpc_begin(t1, 1) # 1 -> subtxn
+    >>> jar.commit(dma, t1)
+    >>> jar.tpc_vote(t1)
+    >>> jar.tpc_finish(t1)
+    >>> dm.state, dm.delta
+    (0, 2)
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 3)
+    >>> jar.tpc_begin(t1, 1) # 1 -> subtxn
+    >>> jar.commit(dma, t1)
+    >>> jar.tpc_vote(t1)
+    >>> jar.tpc_finish(t1)
+    >>> dm.state, dm.delta
+    (0, 3)
+
+    Note that the bove works *because* the same transaction is used
+    for each subtransaction.
+
+    Finally, we'll do a little more work:
+
+    >>> dm.inc()
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 5)
+    
+    and then commit the top-level transaction.
+
+    The transaction  will actually go through the steps for a subtransaction:
+
+    >>> jar.tpc_begin(t1, 1) # 1 -> subtxn
+    >>> jar.commit(dma, t1)
+    >>> jar.tpc_vote(t1)
+    >>> jar.tpc_finish(t1)
+
+    And then call commit_sub:
+
+    >>> jar.commit_sub(t1)
+
+    As usual, this doesn't actually do anything. ;)
+
+    >>> dm.state, dm.delta
+    (0, 5)
+
+    The transaction manager doesn's call tpc_begin, because commit_sub
+    implies the start of two-phase commit. Next, it does call commit:
+
+    >>> jar.commit(dma, t1)
+
+    which doesn't do anything.
+
+    Finally, the transaction calls tpc_vote:
+
+    >>> jar.tpc_vote(t1)
+
+    which actually does something (because this is the top-level txn):
+
+    >>> dm.state, dm.delta
+    (5, 5)
+    >>> dm.prepared
+    True
+
+    Finally, tpc_finish is called:
+
+    >>> jar.tpc_finish(t1)
+
+    and the data manager finishes the two-phase commit:
+    
+    >>> dm.state, dm.delta
+    (5, 0)
+    >>> dm.prepared
+    False
+    """
+
+def test_commit_w_subtransactions_featuring_subtransaction_abort():
+    """
+    So, we have a data manager:
+
+    >>> dm = DataManager()
+
+    and we do some work that modifies uncommited state:
+
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    Now we'll commit the changes in a subtransaction.  When the data
+    manager joins a transaction, the transaction will create an
+    adapter.
+    
+    >>> dma = DataManagerAdapter(dm)
+
+    and register it as a modified object. At commit time, the
+    transaction will get the "jar" like this:
+
+    >>> jar = getattr(dma, '_p_jar', dma)
+
+    and, of course, the jar and the adapter will be the same:
+
+    >>> jar is dma
+    True
+
+    The transaction will call tpc_begin:
+
+    >>> t1 = '1'
+    >>> jar.tpc_begin(t1, 1) # 1 -> subtxn
+
+    Then the transaction will call commit on the jar:
+
+    >>> jar.commit(dma, t1)
+
+    This doesn't actually do anything. :)
+
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    The transaction will then call tpc_vote:
+
+    >>> jar.tpc_vote(t1)
+
+    This doesn't do anything either, because zodb4 data managers don't
+    actually do two-phase commit for subtransactions.
+
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    Finally, we call tpc_finish. This does actally create a savepoint,
+    but we can't really tell that from outside.
+
+    >>> jar.tpc_finish(t1)
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    We'll do more of the above:
+
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 2)
+    >>> jar.tpc_begin(t1, 1) # 1 -> subtxn
+    >>> jar.commit(dma, t1)
+    >>> jar.tpc_vote(t1)
+    >>> jar.tpc_finish(t1)
+    >>> dm.state, dm.delta
+    (0, 2)
+
+    
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 3)
+
+    But then we'll decide to abort a subtransaction.
+
+    The transaction will just call abort as usual:
+
+    >>> jar.abort(dma, t1)
+
+    This will cause a rollback to the last savepoint:
+    >>> dm.state, dm.delta
+    (0, 2)
+
+    Then we do more work:
+    
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 3)
+    >>> jar.tpc_begin(t1, 1) # 1 -> subtxn
+    >>> jar.commit(dma, t1)
+    >>> jar.tpc_vote(t1)
+    >>> jar.tpc_finish(t1)
+    >>> dm.state, dm.delta
+    (0, 3)
+
+    Note that the bove works *because* the same transaction is used
+    for each subtransaction.
+
+    Finally, we'll do a little more work:
+
+    >>> dm.inc()
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 5)
+    
+    and then commit the top-level transaction.
+
+    The transaction  will actually go through the steps for a subtransaction:
+
+    >>> jar.tpc_begin(t1, 1) # 1 -> subtxn
+    >>> jar.commit(dma, t1)
+    >>> jar.tpc_vote(t1)
+    >>> jar.tpc_finish(t1)
+
+    And then call commit_sub:
+
+    >>> jar.commit_sub(t1)
+
+    As usual, this doesn't actually do anything. ;)
+
+    >>> dm.state, dm.delta
+    (0, 5)
+
+    The transaction manager doesn's call tpc_begin, because commit_sub
+    implies the start of two-phase commit. Next, it does call commit:
+
+    >>> jar.commit(dma, t1)
+
+    which doesn't do anything.
+
+    Finally, the transaction calls tpc_vote:
+
+    >>> jar.tpc_vote(t1)
+
+    which actually does something (because this is the top-level txn):
+
+    >>> dm.state, dm.delta
+    (5, 5)
+    >>> dm.prepared
+    True
+
+    Finally, tpc_finish is called:
+
+    >>> jar.tpc_finish(t1)
+
+    and the data manager finishes the two-phase commit:
+    
+    >>> dm.state, dm.delta
+    (5, 0)
+    >>> dm.prepared
+    False
+    """
+
+def test_abort_w_subtransactions():
+    """
+    So, we have a data manager:
+
+    >>> dm = DataManager()
+
+    and we do some work that modifies uncommited state:
+
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    Now we'll commit the changes in a subtransaction.  When the data
+    manager joins a transaction, the transaction will create an
+    adapter.
+    
+    >>> dma = DataManagerAdapter(dm)
+
+    and register it as a modified object. At commit time, the
+    transaction will get the "jar" like this:
+
+    >>> jar = getattr(dma, '_p_jar', dma)
+
+    and, of course, the jar and the adapter will be the same:
+
+    >>> jar is dma
+    True
+
+    The transaction will call tpc_begin:
+
+    >>> t1 = '1'
+    >>> jar.tpc_begin(t1, 1) # 1 -> subtxn
+
+    Then the transaction will call commit on the jar:
+
+    >>> jar.commit(dma, t1)
+
+    This doesn't actually do anything. :)
+
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    The transaction will then call tpc_vote:
+
+    >>> jar.tpc_vote(t1)
+
+    This doesn't do anything either, because zodb4 data managers don't
+    actually do two-phase commit for subtransactions.
+
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    Finally, we call tpc_finish. This does actally create a savepoint,
+    but we can't really tell that from outside.
+
+    >>> jar.tpc_finish(t1)
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    We'll do more of the above:
+
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 2)
+    >>> jar.tpc_begin(t1, 1) # 1 -> subtxn
+    >>> jar.commit(dma, t1)
+    >>> jar.tpc_vote(t1)
+    >>> jar.tpc_finish(t1)
+    >>> dm.state, dm.delta
+    (0, 2)
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 3)
+    >>> jar.tpc_begin(t1, 1) # 1 -> subtxn
+    >>> jar.commit(dma, t1)
+    >>> jar.tpc_vote(t1)
+    >>> jar.tpc_finish(t1)
+    >>> dm.state, dm.delta
+    (0, 3)
+
+    Note that the bove works *because* the same transaction is used
+    for each subtransaction.
+
+    Finally, we'll do a little more work:
+
+    >>> dm.inc()
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 5)
+    
+    and then abort the top-level transaction.
+
+    The transaction first call abort on the jar:
+
+    >>> jar.abort(dma, t1)
+
+    This will have the effect of aborting the subtrancation:
+
+    >>> dm.state, dm.delta
+    (0, 3)
+    
+    Then the transaction will call abort_sub:
+
+    >>> jar.abort_sub(t1)
+
+    This will abort all of the subtransactions:
+
+    >>> dm.state, dm.delta
+    (0, 0)
+    """
+
+
+def test_tpc_abort_w_subtransactions_featuring_subtransaction_abort():
+    """
+    So, we have a data manager:
+
+    >>> dm = DataManager()
+
+    and we do some work that modifies uncommited state:
+
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    Now we'll commit the changes in a subtransaction.  When the data
+    manager joins a transaction, the transaction will create an
+    adapter.
+    
+    >>> dma = DataManagerAdapter(dm)
+
+    and register it as a modified object. At commit time, the
+    transaction will get the "jar" like this:
+
+    >>> jar = getattr(dma, '_p_jar', dma)
+
+    and, of course, the jar and the adapter will be the same:
+
+    >>> jar is dma
+    True
+
+    The transaction will call tpc_begin:
+
+    >>> t1 = '1'
+    >>> jar.tpc_begin(t1, 1) # 1 -> subtxn
+
+    Then the transaction will call commit on the jar:
+
+    >>> jar.commit(dma, t1)
+
+    This doesn't actually do anything. :)
+
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    The transaction will then call tpc_vote:
+
+    >>> jar.tpc_vote(t1)
+
+    This doesn't do anything either, because zodb4 data managers don't
+    actually do two-phase commit for subtransactions.
+
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    Finally, we call tpc_finish. This does actally create a savepoint,
+    but we can't really tell that from outside.
+
+    >>> jar.tpc_finish(t1)
+    >>> dm.state, dm.delta
+    (0, 1)
+
+    We'll do more of the above:
+
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 2)
+    >>> jar.tpc_begin(t1, 1) # 1 -> subtxn
+    >>> jar.commit(dma, t1)
+    >>> jar.tpc_vote(t1)
+    >>> jar.tpc_finish(t1)
+    >>> dm.state, dm.delta
+    (0, 2)
+
+    
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 3)
+
+    But then we'll decide to abort a subtransaction.
+
+    The transaction will just call abort as usual:
+
+    >>> jar.abort(dma, t1)
+
+    This will cause a rollback to the last savepoint:
+    >>> dm.state, dm.delta
+    (0, 2)
+
+    Then we do more work:
+    
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 3)
+    >>> jar.tpc_begin(t1, 1) # 1 -> subtxn
+    >>> jar.commit(dma, t1)
+    >>> jar.tpc_vote(t1)
+    >>> jar.tpc_finish(t1)
+    >>> dm.state, dm.delta
+    (0, 3)
+
+    Note that the bove works *because* the same transaction is used
+    for each subtransaction.
+
+    Finally, we'll do a little more work:
+
+    >>> dm.inc()
+    >>> dm.inc()
+    >>> dm.state, dm.delta
+    (0, 5)
+    
+    and then commit the top-level transaction.
+
+    The transaction  will actually go through the steps for a subtransaction:
+
+    >>> jar.tpc_begin(t1, 1) # 1 -> subtxn
+    >>> jar.commit(dma, t1)
+    >>> jar.tpc_vote(t1)
+    >>> jar.tpc_finish(t1)
+
+    And then call commit_sub:
+
+    >>> jar.commit_sub(t1)
+
+    As usual, this doesn't actually do anything. ;)
+
+    >>> dm.state, dm.delta
+    (0, 5)
+
+    The transaction manager doesn's call tpc_begin, because commit_sub
+    implies the start of two-phase commit. Next, it does call commit:
+
+    >>> jar.commit(dma, t1)
+
+    which doesn't do anything.
+
+    Finally, the transaction calls tpc_vote:
+
+    >>> jar.tpc_vote(t1)
+
+    which actually does something (because this is the top-level txn):
+
+    >>> dm.state, dm.delta
+    (5, 5)
+    >>> dm.prepared
+    True
+
+    Now, at the last minute, the transaction is aborted (possibly due
+    to a "no vote" from another data manager):
+
+    >>> jar.tpc_abort(t1)
+
+    An the changes are undone:
+    
+    >>> dm.state, dm.delta
+    (0, 0)
+    >>> dm.prepared
+    False
+    """
+
+def test_suite():
+    return unittest.TestSuite((
+        DocTestSuite(),
+        ))
+
+if __name__ == '__main__': unittest.main()


=== Zope/lib/python/ZODB/tests/util.py 1.1 => 1.2 ===
--- /dev/null	Wed Feb 18 21:59:11 2004
+++ Zope/lib/python/ZODB/tests/util.py	Wed Feb 18 21:59:10 2004
@@ -0,0 +1,43 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Conventience function for creating test databases
+
+$Id$
+"""
+
+import time
+import persistent
+from ZODB.DemoStorage import DemoStorage
+from ZODB.DB import DB as _DB
+try:
+    from transaction import get_transaction
+except ImportError:
+    pass # else assume ZODB will install it as a builtin
+
+def DB(name='Test'):
+    return _DB(DemoStorage(name))
+
+def commit():
+    get_transaction().commit()
+
+def pack(db):
+    db.pack(time.time()+1)
+
+class P(persistent.Persistent):
+    
+    def __init__(self, name):
+        self.name = name
+
+    def __repr__(self):
+        return 'P(%s)' % self.name


=== Zope/lib/python/ZODB/tests/ConflictResolution.py 1.13 => 1.14 ===
--- Zope/lib/python/ZODB/tests/ConflictResolution.py:1.13	Tue Feb 17 20:12:59 2004
+++ Zope/lib/python/ZODB/tests/ConflictResolution.py	Wed Feb 18 21:59:10 2004
@@ -102,9 +102,9 @@
             self.fail("Expected ConflictError")
 
     def checkZClassesArentResolved(self):
-        from ZODB.ConflictResolution import bad_class
+        from ZODB.ConflictResolution import find_global, BadClassName
         dummy_class_tuple = ('*foobar', ())
-        assert bad_class(dummy_class_tuple) == 1
+        self.assertRaises(BadClassName, find_global, '*foobar', ())
 
     def checkBuggyResolve1(self):
         obj = PCounter3()


=== Zope/lib/python/ZODB/tests/StorageTestBase.py 1.32 => 1.33 ===
--- Zope/lib/python/ZODB/tests/StorageTestBase.py:1.32	Tue Feb 17 20:12:59 2004
+++ Zope/lib/python/ZODB/tests/StorageTestBase.py	Wed Feb 18 21:59:10 2004
@@ -79,19 +79,24 @@
     u.persistent_load = persistent_load
     klass_info = u.load()
     if isinstance(klass_info, types.TupleType):
-        if isinstance(klass_info[0], types.TupleType):
-            modname, klassname = klass_info[0]
+        if isinstance(klass_info[0], type):
+            # XXX what is the second part of klass_info?
+            klass, xxx = klass_info
+            assert not xxx
         else:
-            modname, klassname = klass_info
-        if modname == "__main__":
-            ns = globals()
-        else:
-            mod = import_helper(modname)
-            ns = mod.__dict__
-        try:
-            klass = ns[klassname]
-        except KeyError:
-            print >> sys.stderr, "can't find %s in %r" % (klassname, ns)
+            if isinstance(klass_info[0], tuple):
+                modname, klassname = klass_info[0]
+            else:
+                modname, klassname = klass_info
+            if modname == "__main__":
+                ns = globals()
+            else:
+                mod = import_helper(modname)
+                ns = mod.__dict__
+            try:
+                klass = ns[klassname]
+            except KeyError:
+                print >> sys.stderr, "can't find %s in %r" % (klassname, ns)
         inst = klass()
     else:
         raise ValueError, "expected class info: %s" % repr(klass_info)


=== Zope/lib/python/ZODB/tests/testCache.py 1.17 => 1.18 ===
--- Zope/lib/python/ZODB/tests/testCache.py:1.17	Fri Nov 28 11:44:54 2003
+++ Zope/lib/python/ZODB/tests/testCache.py	Wed Feb 18 21:59:10 2004
@@ -21,6 +21,7 @@
 import time
 import types
 import unittest
+import gc
 
 import ZODB
 import ZODB.MappingStorage
@@ -102,14 +103,14 @@
     def checkFullSweep(self):
         old_size = self.db.cacheSize()
         time.sleep(3)
-        self.db.cacheFullSweep(0)
+        self.db.cacheFullSweep()
         new_size = self.db.cacheSize()
         self.assert_(new_size < old_size, "%s < %s" % (old_size, new_size))
 
     def checkMinimize(self):
         old_size = self.db.cacheSize()
         time.sleep(3)
-        self.db.cacheMinimize(0)
+        self.db.cacheMinimize()
         new_size = self.db.cacheSize()
         self.assert_(new_size < old_size, "%s < %s" % (old_size, new_size))
 
@@ -178,13 +179,19 @@
         CONNS = 3
         for i in range(CONNS):
             self.noodle_new_connection()
-
+        
         self.assertEquals(self.db.cacheSize(), CACHE_SIZE * CONNS)
         details = self.db.cacheDetailSize()
         self.assertEquals(len(details), CONNS)
         for d in details:
             self.assertEquals(d['ngsize'], CACHE_SIZE)
-            self.assertEquals(d['size'], CACHE_SIZE)
+
+            # The assertion below is non-sensical
+            # The (poorly named) cache size is a target for non-ghosts.
+            # The cache *usually* contains non-ghosts, so that the
+            # size normally exceeds the target size.
+            
+            #self.assertEquals(d['size'], CACHE_SIZE)
 
     def checkDetail(self):
         CACHE_SIZE = 10
@@ -193,6 +200,28 @@
         CONNS = 3
         for i in range(CONNS):
             self.noodle_new_connection()
+
+        gc.collect()
+
+        # XXX The above gc.collect call is necessary to make this test
+        # pass.
+        #
+        # This test then only works because the other of computations
+        # and object accesses in the "noodle" calls is such that the
+        # persistent mapping containing the MinPO objects is
+        # deactivated before the MinPO objects.
+        #
+        # - Without the gc call, the cache will contain ghost MinPOs
+        #   and the check of the MinPO count below will fail. That's 
+        #   because the counts returned by cacheDetail include ghosts.
+        #
+        # - If the mapping object containing the MinPOs isn't
+        #   deactivated, there will be one fewer non-ghost MinPO and
+        #   the test will fail anyway.
+        #
+        # This test really needs to be thought through and documented
+        # better. 
+
 
         for klass, count in self.db.cacheDetail():
             if klass.endswith('MinPO'):


=== Zope/lib/python/ZODB/tests/testTransaction.py 1.17 => 1.18 ===
--- Zope/lib/python/ZODB/tests/testTransaction.py:1.17	Wed Dec 24 11:01:58 2003
+++ Zope/lib/python/ZODB/tests/testTransaction.py	Wed Feb 18 21:59:10 2004
@@ -628,9 +628,39 @@
         HoserJar.committed += 1
 
 
+def test_join():
+    """White-box test of the join method
+
+    The join method is provided for "backward-compatability" with ZODB 4
+    data managers.
+
+    The argument to join must be a zodb4 data manager,
+    transaction.interfaces.IDataManager.
+
+    >>> from ZODB.tests.sampledm import DataManager
+    >>> from ZODB.Transaction import DataManagerAdapter
+    >>> t = Transaction.Transaction()
+    >>> dm = DataManager()
+    >>> t.join(dm)
+
+    The end result is that a data manager adapter is one of the
+    transaction's objects:
+
+    >>> isinstance(t._objects[0], DataManagerAdapter)
+    True
+    >>> t._objects[0]._datamanager is dm
+    True
+    
+    """
+
 def test_suite():
+    from doctest import DocTestSuite
+    return unittest.TestSuite((
+        DocTestSuite(),
+        unittest.makeSuite(TransactionTests),
+        ))
 
-    return unittest.makeSuite(TransactionTests)
 
 if __name__ == '__main__':
     unittest.TextTestRunner().run(test_suite())
+


=== Zope/lib/python/ZODB/tests/testUtils.py 1.5 => 1.6 ===
--- Zope/lib/python/ZODB/tests/testUtils.py:1.5	Thu Dec 11 00:16:50 2003
+++ Zope/lib/python/ZODB/tests/testUtils.py	Wed Feb 18 21:59:10 2004
@@ -16,7 +16,6 @@
 import random
 import unittest
 from persistent import Persistent
-import ZODB.coptimizations
 
 NUM = 100
 
@@ -47,11 +46,12 @@
         self.assertEquals(U64("\000\000\000\001\000\000\000\000"), 1L<<32)
 
     def checkPersistentIdHandlesDescriptor(self):
+        from ZODB.serialize import BaseObjectWriter
         class P(Persistent):
             pass
-        L = []
-        pid = ZODB.coptimizations.new_persistent_id(None, L)
-        pid(P)
+
+        writer = BaseObjectWriter(None)
+        self.assertEqual(writer.persistent_id(P), None)
 
 def test_suite():
     return unittest.makeSuite(TestUtils, 'check')




More information about the Zodb-checkins mailing list