[Zope-Checkins] CVS: Zope3/lib/python/ZODB/tests - MTStorage.py:1.2.14.1 ReadOnlyStorage.py:1.2.14.1 testfsIndex.py:1.4.10.1 BasicStorage.py:1.14.6.3 ConflictResolution.py:1.6.30.2 Corruption.py:1.2.36.1 HistoryStorage.py:1.5.24.1 IteratorStorage.py:1.4.24.1 StorageTestBase.py:1.9.6.2 Synchronization.py:1.2.36.2 TransactionalUndoStorage.py:1.13.24.1 TransactionalUndoVersionStorage.py:1.4.66.2 VersionStorage.py:1.11.10.1 speed.py:1.1.88.1 testFileStorage.py:1.13.12.1 testMappingStorage.py:1.1.14.1 testTransaction.py:1.2.40.1

Jeremy Hylton jeremy@zope.com
Sat, 9 Mar 2002 00:33:34 -0500


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

Modified Files:
      Tag: Zope-3x-branch
	BasicStorage.py ConflictResolution.py Corruption.py 
	HistoryStorage.py IteratorStorage.py StorageTestBase.py 
	Synchronization.py TransactionalUndoStorage.py 
	TransactionalUndoVersionStorage.py VersionStorage.py speed.py 
	testFileStorage.py testMappingStorage.py testTransaction.py 
Added Files:
      Tag: Zope-3x-branch
	MTStorage.py ReadOnlyStorage.py testfsIndex.py 
Log Message:
Merge ZODB changes from Zope2 HEAD.



=== Added File Zope3/lib/python/ZODB/tests/MTStorage.py ===
import random
import threading
import time

import ZODB
from Persistence import PersistentMapping

from ZODB.tests.StorageTestBase \
     import StorageTestBase, zodb_pickle, zodb_unpickle, handle_serials
from ZODB.tests.MinPO import MinPO
from ZODB.Transaction import Transaction
from ZODB.POSException import ConflictError

SHORT_DELAY = 0.01

def sort(l):
    "Sort a list in place and return it."
    l.sort()
    return l

class ZODBClientThread(threading.Thread):

    __super_init = threading.Thread.__init__

    def __init__(self, db, test, commits=10, delay=SHORT_DELAY):
        self.__super_init()
        self.db = db
        self.test = test
        self.commits = commits
        self.delay = delay

    def run(self):
        conn = self.db.open()
        root = conn.root()
        d = self.get_thread_dict(root)
        if d is None:
            self.test.fail()
        else:
            for i in range(self.commits):
                self.commit(d, i)
        self.test.assertEqual(sort(d.keys()), range(self.commits))

    def commit(self, d, num):
        d[num] = time.time()
        time.sleep(self.delay)
        get_transaction().commit()
        time.sleep(self.delay)

    def get_thread_dict(self, root):
        name = self.getName()
        # arbitrarily limit to 10 re-tries
        for i in range(10):
            try:
                m = PersistentMapping()
                root[name] = m
                get_transaction().commit()
                break
            except ConflictError, err:
                get_transaction().abort()
        for i in range(10):
            try:
                return root.get(name)
            except ConflictError, err:
                get_transaction().abort()

class StorageClientThread(threading.Thread):

    __super_init = threading.Thread.__init__

    def __init__(self, storage, test, commits=10, delay=SHORT_DELAY):
        self.__super_init()
        self.storage = storage
        self.test = test
        self.commits = commits
        self.delay = delay
        self.oids = {}

    def run(self):
        for i in range(self.commits):
            self.dostore(i)
        self.check()

    def check(self):
        for oid, revid in self.oids.items():
            data, serial = self.storage.load(oid, '')
            self.test.assertEqual(serial, revid)
            obj = zodb_unpickle(data)
            self.test.assertEqual(obj.value[0], self.getName())

    def pause(self):
        time.sleep(self.delay)

    def oid(self):
        oid = self.storage.new_oid()
        self.oids[oid] = None
        return oid

    def dostore(self, i):
        data = zodb_pickle(MinPO((self.getName(), i)))
        t = Transaction()
        oid = self.oid()
        self.pause()

        self.storage.tpc_begin(t)
        self.pause()

        # Always create a new object, signified by None for revid
        r1 = self.storage.store(oid, None, data, '', t)
        self.pause()

        r2 = self.storage.tpc_vote(t)
        self.pause()

        self.storage.tpc_finish(t)
        self.pause()

        revid = handle_serials(oid, r1, r2)
        self.oids[oid] = revid

class ExtStorageClientThread(StorageClientThread):

    def run(self):
        # pick some other storage ops to execute
        ops = [getattr(self, meth) for meth in dir(ExtStorageClientThread)
               if meth.startswith('do_')]
        assert ops, "Didn't find an storage ops in %s" % self.storage
        # do a store to guarantee there's at least one oid in self.oids
        self.dostore(0)

        for i in range(self.commits - 1):
            meth = random.choice(ops)
            meth()
            self.dostore(i)
        self.check()

    def pick_oid(self):
        return random.choice(self.oids.keys())

    def do_load(self):
        oid = self.pick_oid()
        self.storage.load(oid, '')

    def do_loadSerial(self):
        oid = self.pick_oid()
        self.storage.loadSerial(oid, self.oids[oid])

    def do_modifiedInVersion(self):
        oid = self.pick_oid()
        self.storage.modifiedInVersion(oid)

    def do_undoLog(self):
        self.storage.undoLog(0, -20)

    def do_iterator(self):
        try:
            iter = self.storage.iterator()
        except AttributeError:
            # XXX It's hard to detect that a ZEO ClientStorage
            # doesn't have this method, but does have all the others.
            return
        for obj in iter:
            pass

class MTStorage:
    "Test a storage with multiple client threads executing concurrently."

    def _checkNThreads(self, n, constructor, *args):
        threads = [constructor(*args) for i in range(n)]
        for t in threads:
            t.start()
        for t in threads:
            t.join()
    
    def check2ZODBThreads(self):
        db = ZODB.DB(self._storage)
        self._checkNThreads(2, ZODBClientThread, db, self)

    def check7ZODBThreads(self):
        db = ZODB.DB(self._storage)
        self._checkNThreads(7, ZODBClientThread, db, self)

    def check2StorageThreads(self):
        self._checkNThreads(2, StorageClientThread, self._storage, self)
    
    def check7StorageThreads(self):
        self._checkNThreads(7, StorageClientThread, self._storage, self)

    def check4ExtStorageThread(self):
        self._checkNThreads(4, ExtStorageClientThread, self._storage, self)
        


=== Added File Zope3/lib/python/ZODB/tests/ReadOnlyStorage.py ===
from ZODB.POSException import ReadOnlyError
from ZODB.Transaction import Transaction

class ReadOnlyStorage:

    def _create_data(self):
        # test a read-only storage that already has some data
        self.oids = {}
        for i in range(10):
            oid = self._storage.new_oid()
            revid = self._dostore(oid)
            self.oids[oid] = revid

    def _make_readonly(self):
        self._storage.close()
        self.open(read_only=1)
        self.assert_(self._storage.isReadOnly())

    def checkReadMethods(self):
        self._create_data()
        self._make_readonly()
        # XXX not going to bother checking all read methods
        for oid in self.oids.keys():
            data, revid = self._storage.load(oid, '')
            self.assertEqual(revid, self.oids[oid])
            self.assert_(not self._storage.modifiedInVersion(oid))
            _data = self._storage.loadSerial(oid, revid)
            self.assertEqual(data, _data)

    def checkWriteMethods(self):
        self._make_readonly()
        self.assertRaises(ReadOnlyError, self._storage.new_oid)
        self.assertRaises(ReadOnlyError, self._storage.undo,
                          '\000' * 8)

        t = Transaction()
        self._storage.tpc_begin(t)
        self.assertRaises(ReadOnlyError, self._storage.abortVersion,
                          '', t)
        self._storage.tpc_abort(t)
        
        t = Transaction()
        self._storage.tpc_begin(t)
        self.assertRaises(ReadOnlyError, self._storage.commitVersion,
                          '', '', t)
        self._storage.tpc_abort(t)

        t = Transaction()
        self._storage.tpc_begin(t)
        self.assertRaises(ReadOnlyError, self._storage.store,
                          '\000' * 8, None, '', '', t)
        self._storage.tpc_abort(t)

        if self._storage.supportsTransactionalUndo():
            t = Transaction()
            self._storage.tpc_begin(t)
            self.assertRaises(ReadOnlyError, self._storage.transactionalUndo,
                              '\000' * 8, t)
            self._storage.tpc_abort(t)
            



=== Added File Zope3/lib/python/ZODB/tests/testfsIndex.py ===

import unittest, sys
from ZODB.fsIndex import fsIndex
from ZODB.utils import p64


class Test(unittest.TestCase):

    def testInserts(self):
        index=fsIndex()

        for i in range(200):
            index[p64(i*1000)]=(i*1000L+1)

        for i in range(0,200):
            self.assertEqual((i,index[p64(i*1000)]), (i,(i*1000L+1)))
            
        self.assertEqual(len(index), 200)

        key=p64(2000)

        self.assertEqual(index.get(key), 2001)

        key=p64(2001)
        self.assertEqual(index.get(key), None)
        self.assertEqual(index.get(key, ''), '')

        # self.failUnless(len(index._data) > 1)

    def testUpdate(self):
        index=fsIndex()
        d={}

        for i in range(200):
            d[p64(i*1000)]=(i*1000L+1)

        index.update(d)

        for i in range(400,600):
            d[p64(i*1000)]=(i*1000L+1)
        
        index.update(d)

        for i in range(100, 500):
            d[p64(i*1000)]=(i*1000L+2)
            
        index.update(d)

        self.assertEqual(index.get(p64(2000)), 2001)
        self.assertEqual(index.get(p64(599000)), 599001)
        self.assertEqual(index.get(p64(399000)), 399002)
        self.assertEqual(len(index), 600)


def test_suite():
    loader=unittest.TestLoader()
    return loader.loadTestsFromTestCase(Test)

if __name__=='__main__':
    unittest.TextTestRunner().run(test_suite())


=== Zope3/lib/python/ZODB/tests/BasicStorage.py 1.14.6.2 => 1.14.6.3 ===
 
 from ZODB.tests.MinPO import MinPO
-from ZODB.tests.StorageTestBase import zodb_unpickle, zodb_pickle
+from ZODB.tests.StorageTestBase \
+     import zodb_unpickle, zodb_pickle, handle_serials
 
 ZERO = '\0'*8
 
@@ -31,14 +32,15 @@
 
 class BasicStorage:
     def checkBasics(self):
-        self._storage.tpc_begin(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
         # This should simply return
-        self._storage.tpc_begin(self._transaction)
+        self._storage.tpc_begin(t)
         # Aborting is easy
-        self._storage.tpc_abort(self._transaction)
+        self._storage.tpc_abort(t)
         # Test a few expected exceptions when we're doing operations giving a
         # different Transaction object than the one we've begun on.
-        self._storage.tpc_begin(self._transaction)
+        self._storage.tpc_begin(t)
         self.assertRaises(
             POSException.StorageTransactionError,
             self._storage.store,
@@ -77,12 +79,12 @@
             POSException.StorageTransactionError,
             self._storage.store,
             0, 1, 2, 3, Transaction())
-        self._storage.tpc_abort(self._transaction)
+        self._storage.tpc_abort(t)
 
     def checkSerialIsNoneForInitialRevision(self):
         eq = self.assertEqual
         oid = self._storage.new_oid()
-        txn = self._transaction
+        txn = Transaction()
         self._storage.tpc_begin(txn)
         # Use None for serial.  Don't use _dostore() here because that coerces
         # serial=None to serial=ZERO.
@@ -90,7 +92,7 @@
                                        '', txn)
         r2 = self._storage.tpc_vote(txn)
         self._storage.tpc_finish(txn)
-        newrevid = self._handle_serials(oid, r1, r2)
+        newrevid = handle_serials(oid, r1, r2)
         data, revid = self._storage.load(oid, '')
         value = zodb_unpickle(data)
         eq(value, MinPO(11))
@@ -131,13 +133,12 @@
 
     def checkWriteAfterAbort(self):
         oid = self._storage.new_oid()
-        self._storage.tpc_begin(self._transaction)
-        self._storage.store(oid, ZERO, zodb_pickle(MinPO(5)),
-                            '', self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        self._storage.store(oid, ZERO, zodb_pickle(MinPO(5)), '', t)
         # Now abort this transaction
-        self._storage.tpc_abort(self._transaction)
+        self._storage.tpc_abort(t)
         # Now start all over again
-        self._transaction = Transaction()
         oid = self._storage.new_oid()
         self._dostore(oid=oid, data=MinPO(6))
 
@@ -145,14 +146,13 @@
         oid1 = self._storage.new_oid()
         revid1 = self._dostore(oid=oid1, data=MinPO(-2))
         oid = self._storage.new_oid()
-        self._storage.tpc_begin(self._transaction)
-        self._storage.store(oid, ZERO, zodb_pickle(MinPO(5)),
-                            '', self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        self._storage.store(oid, ZERO, zodb_pickle(MinPO(5)), '', t)
         # Now abort this transaction
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_abort(self._transaction)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_abort(t)
         # Now start all over again
-        self._transaction = Transaction()
         oid = self._storage.new_oid()
         revid = self._dostore(oid=oid, data=MinPO(6))
 
@@ -186,3 +186,14 @@
         # And another one
         revid2 = self._dostore(oid, revid=revid1, data=p42)
         eq(revid2, self._storage.getSerial(oid))
+
+    def checkTwoArgBegin(self):
+        # XXX how standard is three-argument tpc_begin()?
+        t = Transaction()
+        tid = chr(42) * 8
+        self._storage.tpc_begin(t, tid)
+        oid = self._storage.new_oid()
+        data = zodb_pickle(MinPO(8))
+        self._storage.store(oid, None, data, '', t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)


=== Zope3/lib/python/ZODB/tests/ConflictResolution.py 1.6.30.1 => 1.6.30.2 ===
         info = self._storage.undoInfo()
         tid = info[1]['id']
-        self._storage.tpc_begin(self._transaction)
-        self._storage.transactionalUndo(tid, self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        self._storage.transactionalUndo(tid, t)
+        self._storage.tpc_finish(t)
 
     def checkUndoUnresolvable(self):
         # This test is based on checkNotUndoable in the
@@ -164,9 +165,9 @@
         # Start the undo
         info = self._storage.undoInfo()
         tid = info[1]['id']
-        self._storage.tpc_begin(self._transaction)
-        self.assertRaises(UndoError,
-                          self._storage.transactionalUndo,
-                          tid, self._transaction)
-        self._storage.tpc_abort(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        self.assertRaises(UndoError, self._storage.transactionalUndo,
+                          tid, t)
+        self._storage.tpc_abort(t)
 


=== Zope3/lib/python/ZODB/tests/Corruption.py 1.2 => 1.2.36.1 ===
 
 class FileStorageCorruptTests(StorageTestBase):
-    __super_setUp = StorageTestBase.setUp
-    __super_tearDown = StorageTestBase.tearDown
 
     def setUp(self):
         self.path = tempfile.mktemp()
         self._storage = ZODB.FileStorage.FileStorage(self.path, create=1)
-        self.__super_setUp()
 
     def tearDown(self):
-        self.__super_tearDown()
+        self._storage.close()
         for ext in '', '.old', '.tmp', '.lock', '.index':
             path = self.path + ext
             if os.path.exists(path):


=== Zope3/lib/python/ZODB/tests/HistoryStorage.py 1.5 => 1.5.24.1 ===
 """
 
+from ZODB.Transaction import Transaction
 from ZODB.tests.MinPO import MinPO
 from ZODB.tests.StorageTestBase import zodb_unpickle
 
@@ -111,10 +112,11 @@
         revid6 = self._dostore(oid, revid=revid5, data=MinPO(16),
                                version=version)
         # Now commit the version
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.commitVersion(version, '', self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.commitVersion(version, '', t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         # After consultation with Jim, we agreed that the semantics of
         # revision id's after a version commit is that the committed object
         # gets a new serial number (a.k.a. revision id).  Note that
@@ -168,10 +170,11 @@
         revid6 = self._dostore(oid, revid=revid5, data=MinPO(16),
                                version=version)
         # Now commit the version
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.abortVersion(version, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.abortVersion(version, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         # After consultation with Jim, we agreed that the semantics of
         # revision id's after a version commit is that the committed object
         # gets a new serial number (a.k.a. revision id).  Note that


=== Zope3/lib/python/ZODB/tests/IteratorStorage.py 1.4 => 1.4.24.1 ===
 from ZODB.tests.MinPO import MinPO
 from ZODB.tests.StorageTestBase import zodb_unpickle
-
+from ZODB.utils import U64, p64
+from ZODB.Transaction import Transaction
 
 
-class IteratorStorage:
-    def checkSimpleIteration(self):
+class IteratorCompare:
+
+    def iter_verify(self, txniter, revids, val0):
         eq = self.assertEqual
-        # Store a bunch of revisions of a single object
-        oid = self._storage.new_oid()
-        revid1 = self._dostore(oid, data=MinPO(11))
-        revid2 = self._dostore(oid, revid=revid1, data=MinPO(12))
-        revid3 = self._dostore(oid, revid=revid2, data=MinPO(13))
-        # Now iterate over all the transactions
-        val = 11
-        txniter = self._storage.iterator()
-        for reciter, revid in zip(txniter, (revid1, revid2, revid3)):
+        oid = self._oid
+        val = val0
+        for reciter, revid in zip(txniter, revids + [None]):
             eq(reciter.tid, revid)
             for rec in reciter:
                 eq(rec.oid, oid)
@@ -28,3 +24,147 @@
                 eq(rec.version, '')
                 eq(zodb_unpickle(rec.data), MinPO(val))
                 val = val + 1
+        eq(val, val0 + len(revids))
+
+class IteratorStorage(IteratorCompare):
+
+    def checkSimpleIteration(self):
+        # Store a bunch of revisions of a single object
+        self._oid = oid = self._storage.new_oid()
+        revid1 = self._dostore(oid, data=MinPO(11))
+        revid2 = self._dostore(oid, revid=revid1, data=MinPO(12))
+        revid3 = self._dostore(oid, revid=revid2, data=MinPO(13))
+        # Now iterate over all the transactions and compare carefully
+        txniter = self._storage.iterator()
+        self.iter_verify(txniter, [revid1, revid2, revid3], 11)
+
+    def checkClose(self):
+        self._oid = oid = self._storage.new_oid()
+        revid1 = self._dostore(oid, data=MinPO(11))
+        txniter = self._storage.iterator()
+        txniter.close()
+        self.assertRaises(IOError, txniter.__getitem__, 0)
+
+    def checkVersionIterator(self):
+        if not self._storage.supportsVersions():
+            return
+        self._dostore()
+        self._dostore(version='abort')
+        self._dostore()
+        self._dostore(version='abort')
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        self._storage.abortVersion('abort', t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
+
+        self._dostore(version='commit')
+        self._dostore()
+        self._dostore(version='commit')
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        self._storage.commitVersion('commit', '', t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
+
+        txniter = self._storage.iterator()
+        for trans in txniter:
+            for data in trans:
+                pass
+
+    def checkUndoZombieNonVersion(self):
+        if not hasattr(self._storage, 'supportsTransactionalUndo'):
+            return
+        if not self._storage.supportsTransactionalUndo():
+            return
+
+        oid = self._storage.new_oid()
+        revid = self._dostore(oid, data=MinPO(94))
+        # Get the undo information
+        info = self._storage.undoInfo()
+        tid = info[0]['id']
+        # Undo the creation of the object, rendering it a zombie
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.transactionalUndo(tid, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
+        # Now attempt to iterator over the storage
+        iter = self._storage.iterator()
+        for txn in iter:
+            for rec in txn:
+                pass
+
+        # The last transaction performed an undo of the transaction that
+        # created object oid.  (As Barry points out, the object is now in the
+        # George Bailey state.)  Assert that the final data record contains
+        # None in the data attribute.
+        self.assertEqual(rec.oid, oid)
+        self.assertEqual(rec.data, None)
+
+
+class ExtendedIteratorStorage(IteratorCompare):
+
+    def checkExtendedIteration(self):
+        # Store a bunch of revisions of a single object
+        self._oid = oid = self._storage.new_oid()
+        revid1 = self._dostore(oid, data=MinPO(11))
+        revid2 = self._dostore(oid, revid=revid1, data=MinPO(12))
+        revid3 = self._dostore(oid, revid=revid2, data=MinPO(13))
+        revid4 = self._dostore(oid, revid=revid3, data=MinPO(14))
+        # Note that the end points are included
+        # Iterate over all of the transactions with explicit start/stop
+        txniter = self._storage.iterator(revid1, revid4)
+        self.iter_verify(txniter, [revid1, revid2, revid3, revid4], 11)
+        # Iterate over some of the transactions with explicit start
+        txniter = self._storage.iterator(revid3)
+        self.iter_verify(txniter, [revid3, revid4], 13)
+        # Iterate over some of the transactions with explicit stop
+        txniter = self._storage.iterator(None, revid2)
+        self.iter_verify(txniter, [revid1, revid2], 11)
+        # Iterate over some of the transactions with explicit start+stop
+        txniter = self._storage.iterator(revid2, revid3)
+        self.iter_verify(txniter, [revid2, revid3], 12)
+        # Specify an upper bound somewhere in between values
+        revid3a = p64((U64(revid3) + U64(revid4)) / 2)
+        txniter = self._storage.iterator(revid2, revid3a)
+        self.iter_verify(txniter, [revid2, revid3], 12)
+        # Specify a lower bound somewhere in between values.
+        # revid2 == revid1+1 is very likely on Windows.  Adding 1 before
+        # dividing ensures that "the midpoint" we compute is strictly larger
+        # than revid1.
+        revid1a = p64((U64(revid1) + 1 + U64(revid2)) / 2)
+        assert revid1 < revid1a
+        txniter = self._storage.iterator(revid1a, revid3a)
+        self.iter_verify(txniter, [revid2, revid3], 12)
+        # Specify an empty range
+        txniter = self._storage.iterator(revid3, revid2)
+        self.iter_verify(txniter, [], 13)
+        # Specify a singleton range
+        txniter = self._storage.iterator(revid3, revid3)
+        self.iter_verify(txniter, [revid3], 13)
+
+class IteratorDeepCompare:
+    def compare(self, storage1, storage2):
+        eq = self.assertEqual
+        iter1 = storage1.iterator()
+        iter2 = storage2.iterator()
+        for txn1, txn2 in zip(iter1, iter2):
+            eq(txn1.tid,         txn2.tid)
+            eq(txn1.status,      txn2.status)
+            eq(txn1.user,        txn2.user)
+            eq(txn1.description, txn2.description)
+            eq(txn1._extension,  txn2._extension)
+            for rec1, rec2 in zip(txn1, txn2):
+                eq(rec1.oid,     rec2.oid)
+                eq(rec1.serial,  rec2.serial)
+                eq(rec1.version, rec2.version)
+                eq(rec1.data,    rec2.data)
+            # Make sure there are no more records left in rec1 and rec2,
+            # meaning they were the same length.
+            self.assertRaises(IndexError, txn1.next)
+            self.assertRaises(IndexError, txn2.next)
+        # Make sure ther are no more records left in txn1 and txn2, meaning
+        # they were the same length
+        self.assertRaises(IndexError, iter1.next)
+        self.assertRaises(IndexError, iter2.next)


=== Zope3/lib/python/ZODB/tests/StorageTestBase.py 1.9.6.1 => 1.9.6.2 ===
     return inst
 
+def handle_all_serials(oid, *args):
+    """Return dict of oid to serialno from store() and tpc_vote().
+
+    Raises an exception if one of the calls raised an exception.
+
+    The storage interface got complicated when ZEO was introduced.
+    Any individual store() call can return None or a sequence of
+    2-tuples where the 2-tuple is either oid, serialno or an
+    exception to be raised by the client.
+
+    The original interface just returned the serialno for the
+    object.
+    """
+    d = {}
+    for arg in args:
+        if isinstance(arg, types.StringType):
+            d[oid] = arg
+        elif arg is None:
+            pass
+        else:
+            for oid, serial in arg:
+                if not isinstance(serial, types.StringType):
+                    raise serial # error from ZEO server
+                d[oid] = serial
+    return d
+
+def handle_serials(oid, *args):
+    """Return the serialno for oid based on multiple return values.
+
+    A helper for function _handle_all_serials().
+    """
+    args = (oid,) + args
+    return apply(handle_all_serials, args)[oid]
+
 def import_helper(name):
     mod = __import__(name)
-    for part in string.split(name, ".")[1:]:
-        mod = getattr(mod, part)
-    return mod
+    return sys.modules[name]
 
 
 class StorageTestBase(unittest.TestCase):
+
+    # XXX It would be simpler if concrete tests didn't need to extend
+    # setUp() and tearDown().
+    
     def setUp(self):
         # You need to override this with a setUp that creates self._storage
-        self._transaction = Transaction()
+        self._storage = None
 
     def _close(self):
         # You should override this if closing your storage requires additional
         # shutdown operations.
-        self._transaction.abort()
-        self._storage.close()
+        if self._storage is not None:
+            self._storage.close()
 
     def tearDown(self):
         self._close()
 
-    def _handle_all_serials(self, oid, *args):
-        """Return dict of oid to serialno from store() and tpc_vote().
-
-        Raises an exception if one of the calls raised an exception.
-
-        The storage interface got complicated when ZEO was introduced.
-        Any individual store() call can return None or a sequence of
-        2-tuples where the 2-tuple is either oid, serialno or an
-        exception to be raised by the client.
-
-        The original interface just returned the serialno for the
-        object.
-        """
-        d = {}
-        for arg in args:
-            if isinstance(arg, types.StringType):
-                d[oid] = arg
-            elif arg is None:
-                pass
-            else:
-                for oid, serial in arg:
-                    if not isinstance(serial, types.StringType):
-                        raise arg
-                    d[oid] = serial
-        return d
-
-    def _handle_serials(self, oid, *args):
-        """Return the serialno for oid based on multiple return values.
-
-        A helper for function _handle_all_serials().
-        """
-        args = (oid,) + args
-        return apply(self._handle_all_serials, args)[oid]
-
     def _dostore(self, oid=None, revid=None, data=None, version=None,
-                 already_pickled=0):
+                 already_pickled=0, user=None, description=None):
         """Do a complete storage transaction.  The defaults are:
         
          - oid=None, ask the storage for a new oid
@@ -146,14 +148,24 @@
         if version is None:
             version = ''
         # Begin the transaction
-        self._storage.tpc_begin(self._transaction)
-        # Store an object
-        r1 = self._storage.store(oid, revid, data, version,
-                                       self._transaction)
-        # Finish the transaction
-        r2 = self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
-        return self._handle_serials(oid, r1, r2)
+        t = Transaction()
+        if user is not None:
+            t.user = user
+        if description is not None:
+            t.description = description
+        try:
+            self._storage.tpc_begin(t)
+            # Store an object
+            r1 = self._storage.store(oid, revid, data, version, t)
+            # Finish the transaction
+            r2 = self._storage.tpc_vote(t)
+            revid = handle_serials(oid, r1, r2)
+            self._storage.tpc_finish(t)
+        except:
+            self._storage.tpc_abort(t)
+            raise
+        return revid
         
-    def _dostoreNP(self, oid=None, revid=None, data=None, version=None):
+    def _dostoreNP(self, oid=None, revid=None, data=None, version=None,
+                   user=None, description=None):
         return self._dostore(oid, revid, data, version, already_pickled=1)


=== Zope3/lib/python/ZODB/tests/Synchronization.py 1.2.36.1 => 1.2.36.2 ===
 
     def verifyWrongTrans(self, callable, *args):
-        self._storage.tpc_begin(self._transaction)
-        args = (StorageTransactionError, callable) + args
-        apply(self.assertRaises, args)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        self.assertRaises(StorageTransactionError, callable, *args)
+        self._storage.tpc_abort(t)
 
     def checkAbortVersionNotCommitting(self):
         self.verifyNotCommitting(self._storage.abortVersion,
-                                 VERSION, self._transaction)
+                                 VERSION, Transaction())
 
     def checkAbortVersionWrongTrans(self):
         self.verifyWrongTrans(self._storage.abortVersion,
@@ -81,7 +82,7 @@
 
     def checkCommitVersionNotCommitting(self):
         self.verifyNotCommitting(self._storage.commitVersion,
-                                 VERSION, "", self._transaction)
+                                 VERSION, "", Transaction())
 
     def checkCommitVersionWrongTrans(self):
         self.verifyWrongTrans(self._storage.commitVersion,
@@ -90,7 +91,7 @@
 
     def checkStoreNotCommitting(self):
         self.verifyNotCommitting(self._storage.store,
-                                 OID, SERIALNO, "", "", self._transaction)
+                                 OID, SERIALNO, "", "", Transaction())
     
     def checkStoreWrongTrans(self):
         self.verifyWrongTrans(self._storage.store,
@@ -107,18 +108,26 @@
         self._storage.tpc_abort(Transaction())
 
     def checkAbortWrongTrans(self):
-        self._storage.tpc_begin(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
         self._storage.tpc_abort(Transaction())
+        self._storage.tpc_abort(t)
 
     def checkFinishNotCommitting(self):
-        self._storage.tpc_finish(Transaction())
+        t = Transaction()
+        self._storage.tpc_finish(t)
+        self._storage.tpc_abort(t)
 
     def checkFinishWrongTrans(self):
-        self._storage.tpc_begin(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
         self._storage.tpc_finish(Transaction())
+        self._storage.tpc_abort(t)
     
     def checkBeginCommitting(self):
-        self._storage.tpc_begin(self._transaction)
-        self._storage.tpc_begin(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        self._storage.tpc_begin(t)
+        self._storage.tpc_abort(t)
 
     # XXX how to check undo?


=== Zope3/lib/python/ZODB/tests/TransactionalUndoStorage.py 1.13 => 1.13.24.1 ===
 import types
 from ZODB import POSException
+from ZODB.Transaction import Transaction
 
 from ZODB.tests.MinPO import MinPO
 from ZODB.tests.StorageTestBase import zodb_pickle, zodb_unpickle
@@ -37,13 +38,14 @@
 
     def _multi_obj_transaction(self, objs):
         newrevs = {}
-        self._storage.tpc_begin(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
         self._transaction_begin()
         for oid, rev, data in objs:
-            self._transaction_store(oid, rev, data, '', self._transaction)
+            self._transaction_store(oid, rev, data, '', t)
             newrevs[oid] = None
-        self._transaction_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        self._transaction_vote(t)
+        self._storage.tpc_finish(t)
         for oid in newrevs.keys():
             newrevs[oid] = self._transaction_newserial(oid)
         return newrevs
@@ -58,11 +60,12 @@
         info = self._storage.undoInfo()
         tid = info[0]['id']
         # Now start an undo transaction
-        self._transaction.note('undo1')
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.transactionalUndo(tid, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        t.note('undo1')
+        self._storage.tpc_begin(t)
+        oids = self._storage.transactionalUndo(tid, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         eq(len(oids), 1)
         eq(oids[0], oid)
         data, revid = self._storage.load(oid, '')
@@ -70,11 +73,12 @@
         # Do another one
         info = self._storage.undoInfo()
         tid = info[2]['id']
-        self._transaction.note('undo2')
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.transactionalUndo(tid, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        t.note('undo2')
+        self._storage.tpc_begin(t)
+        oids = self._storage.transactionalUndo(tid, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         eq(len(oids), 1)
         eq(oids[0], oid)
         data, revid = self._storage.load(oid, '')
@@ -82,11 +86,12 @@
         # Try to undo the first record
         info = self._storage.undoInfo()
         tid = info[4]['id']
-        self._transaction.note('undo3')
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.transactionalUndo(tid, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        t.note('undo3')
+        self._storage.tpc_begin(t)
+        oids = self._storage.transactionalUndo(tid, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
 
         eq(len(oids), 1)
         eq(oids[0], oid)
@@ -96,10 +101,11 @@
         # And now let's try to redo the object's creation
         info = self._storage.undoInfo()
         tid = info[0]['id']
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.transactionalUndo(tid, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.transactionalUndo(tid, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         eq(len(oids), 1)
         eq(oids[0], oid)
         data, revid = self._storage.load(oid, '')
@@ -113,10 +119,11 @@
         # Undo the last transaction
         info = self._storage.undoInfo()
         tid = info[0]['id']
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.transactionalUndo(tid, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.transactionalUndo(tid, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         eq(len(oids), 1)
         eq(oids[0], oid)
         data, revid = self._storage.load(oid, '')
@@ -125,10 +132,11 @@
         # creation.  Let's undo the object creation.
         info = self._storage.undoInfo()
         tid = info[2]['id']
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.transactionalUndo(tid, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.transactionalUndo(tid, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         eq(len(oids), 1)
         eq(oids[0], oid)
         self.assertRaises(KeyError, self._storage.load, oid, '')
@@ -141,10 +149,11 @@
         # Undo the last transaction
         info = self._storage.undoInfo()
         tid = info[0]['id']
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.transactionalUndo(tid, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.transactionalUndo(tid, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         eq(len(oids), 1)
         eq(oids[0], oid)
         data, revid = self._storage.load(oid, '')
@@ -153,10 +162,11 @@
         # creation.  Let's redo the last undo
         info = self._storage.undoInfo()
         tid = info[0]['id']
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.transactionalUndo(tid, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.transactionalUndo(tid, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         eq(len(oids), 1)
         eq(oids[0], oid)
         data, revid = self._storage.load(oid, '')
@@ -171,26 +181,28 @@
         oid2 = self._storage.new_oid()
         revid1 = revid2 = ZERO
         # Store two objects in the same transaction
-        self._storage.tpc_begin(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
         self._transaction_begin()
-        self._transaction_store(oid1, revid1, p31, '', self._transaction)
-        self._transaction_store(oid2, revid2, p51, '', self._transaction)
+        self._transaction_store(oid1, revid1, p31, '', t)
+        self._transaction_store(oid2, revid2, p51, '', t)
         # Finish the transaction
-        self._transaction_vote(self._transaction)
+        self._transaction_vote(t)
         revid1 = self._transaction_newserial(oid1)
         revid2 = self._transaction_newserial(oid2)
-        self._storage.tpc_finish(self._transaction)
+        self._storage.tpc_finish(t)
         eq(revid1, revid2)
         # Update those same two objects
-        self._storage.tpc_begin(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
         self._transaction_begin()
-        self._transaction_store(oid1, revid1, p32, '', self._transaction)
-        self._transaction_store(oid2, revid2, p52, '', self._transaction)
+        self._transaction_store(oid1, revid1, p32, '', t)
+        self._transaction_store(oid2, revid2, p52, '', t)
         # Finish the transaction
-        self._transaction_vote(self._transaction)
+        self._transaction_vote(t)
         revid1 = self._transaction_newserial(oid1)
         revid2 = self._transaction_newserial(oid2)
-        self._storage.tpc_finish(self._transaction)
+        self._storage.tpc_finish(t)
         eq(revid1, revid2)
         # Make sure the objects have the current value
         data, revid1 = self._storage.load(oid1, '')
@@ -200,10 +212,11 @@
         # Now attempt to undo the transaction containing two objects
         info = self._storage.undoInfo()
         tid = info[0]['id']
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.transactionalUndo(tid, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.transactionalUndo(tid, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         eq(len(oids), 2)
         self.failUnless(oid1 in oids)
         self.failUnless(oid2 in oids)
@@ -249,14 +262,15 @@
         info = self._storage.undoInfo()
         tid = info[0]['id']
         tid1 = info[1]['id']
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.transactionalUndo(tid, self._transaction)
-        oids1 = self._storage.transactionalUndo(tid1, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.transactionalUndo(tid, t)
+        oids1 = self._storage.transactionalUndo(tid1, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         # We get the finalization stuff called an extra time:
-##        self._storage.tpc_vote(self._transaction)
-##        self._storage.tpc_finish(self._transaction)
+##        self._storage.tpc_vote(t)
+##        self._storage.tpc_finish(t)
         eq(len(oids), 2)
         eq(len(oids1), 2)
         unless(oid1 in oids)
@@ -268,10 +282,11 @@
         # Now try to undo the one we just did to undo, whew
         info = self._storage.undoInfo()
         tid = info[0]['id']
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.transactionalUndo(tid, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.transactionalUndo(tid, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         eq(len(oids), 2)
         unless(oid1 in oids)
         unless(oid2 in oids)
@@ -292,23 +307,25 @@
         revid1 = self._dostore(oid1, data=p31, already_pickled=1)
         revid2 = self._dostore(oid2, data=p51, already_pickled=1)
         # Update those same two objects
-        self._storage.tpc_begin(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
         self._transaction_begin()
-        self._transaction_store(oid1, revid1, p32, '', self._transaction)
-        self._transaction_store(oid2, revid2, p52, '', self._transaction)
+        self._transaction_store(oid1, revid1, p32, '', t)
+        self._transaction_store(oid2, revid2, p52, '', t)
         # Finish the transaction
-        self._transaction_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        self._transaction_vote(t)
+        self._storage.tpc_finish(t)
         revid1 = self._transaction_newserial(oid1)
         revid2 = self._transaction_newserial(oid2)
         eq(revid1, revid2)
         # Now attempt to undo the transaction containing two objects
         info = self._storage.undoInfo()
         tid = info[0]['id']
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.transactionalUndo(tid, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.transactionalUndo(tid, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         eq(len(oids), 2)
         self.failUnless(oid1 in oids)
         self.failUnless(oid2 in oids)
@@ -318,13 +335,14 @@
         eq(zodb_unpickle(data), MinPO(51))
         # Like the above, but this time, the second transaction contains only
         # one object.
-        self._storage.tpc_begin(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
         self._transaction_begin()
-        self._transaction_store(oid1, revid1, p33, '', self._transaction)
-        self._transaction_store(oid2, revid2, p53, '', self._transaction)
+        self._transaction_store(oid1, revid1, p33, '', t)
+        self._transaction_store(oid2, revid2, p53, '', t)
         # Finish the transaction
-        self._transaction_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        self._transaction_vote(t)
+        self._storage.tpc_finish(t)
         revid1 = self._transaction_newserial(oid1)
         revid2 = self._transaction_newserial(oid2)
         eq(revid1, revid2)
@@ -334,10 +352,11 @@
         # Now attempt to undo the transaction containing two objects
         info = self._storage.undoInfo()
         tid = info[1]['id']
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.transactionalUndo(tid, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.transactionalUndo(tid, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         eq(len(oids), 1)
         self.failUnless(oid1 in oids)
         self.failUnless(not oid2 in oids)
@@ -357,11 +376,12 @@
         # Start the undo
         info = self._storage.undoInfo()
         tid = info[1]['id']
-        self._storage.tpc_begin(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
         self.assertRaises(POSException.UndoError,
                           self._storage.transactionalUndo,
-                          tid, self._transaction)
-        self._storage.tpc_abort(self._transaction)
+                          tid, t)
+        self._storage.tpc_abort(t)
         # Now have more fun: object1 and object2 are in the same transaction,
         # which we'll try to undo to, but one of them has since modified in
         # different transaction, so the undo should fail.
@@ -372,12 +392,13 @@
         p81, p82, p91, p92 = map(zodb_pickle,
                                  map(MinPO, (81, 82, 91, 92)))
 
-        self._storage.tpc_begin(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
         self._transaction_begin()
-        self._transaction_store(oid1, revid1, p81, '', self._transaction)
-        self._transaction_store(oid2, revid2, p91, '', self._transaction)
-        self._transaction_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        self._transaction_store(oid1, revid1, p81, '', t)
+        self._transaction_store(oid2, revid2, p91, '', t)
+        self._transaction_vote(t)
+        self._storage.tpc_finish(t)
         revid1 = self._transaction_newserial(oid1)
         revid2 = self._transaction_newserial(oid2)
         eq(revid1, revid2)
@@ -394,8 +415,9 @@
         self.assertNotEqual(revid2, revid_22)
         info = self._storage.undoInfo()
         tid = info[1]['id']
-        self._storage.tpc_begin(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
         self.assertRaises(POSException.UndoError,
                           self._storage.transactionalUndo,
-                          tid, self._transaction)
-        self._storage.tpc_abort(self._transaction)
+                          tid, t)
+        self._storage.tpc_abort(t)


=== Zope3/lib/python/ZODB/tests/TransactionalUndoVersionStorage.py 1.4.66.1 => 1.4.66.2 ===
 
 from ZODB import POSException
+from ZODB.Transaction import Transaction
 from ZODB.tests.MinPO import MinPO
 from ZODB.tests.StorageTestBase import zodb_unpickle
 
@@ -17,10 +18,11 @@
                                 version=version)
         info=self._storage.undoInfo()
         tid=info[0]['id']
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.transactionalUndo(tid, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.transactionalUndo(tid, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         assert len(oids) == 1
         assert oids[0] == oid
         data, revid = self._storage.load(oid, '')
@@ -30,10 +32,11 @@
         assert revid > revid_b and revid > revid_c
         assert zodb_unpickle(data) == MinPO(92)
         # Now commit the version...
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.commitVersion(version, '', self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.commitVersion(version, '', t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         assert len(oids) == 1
         assert oids[0] == oid
 
@@ -48,10 +51,11 @@
         # ...and undo the commit
         info=self._storage.undoInfo()
         tid=info[0]['id']
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.transactionalUndo(tid, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.transactionalUndo(tid, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         assert len(oids) == 1
         assert oids[0] == oid
         data, revid = self._storage.load(oid, version)
@@ -59,10 +63,11 @@
         data, revid = self._storage.load(oid, '')
         assert zodb_unpickle(data) == MinPO(91)
         # Now abort the version
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.abortVersion(version, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.abortVersion(version, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         assert len(oids) == 1
         assert oids[0] == oid
         # The object should not exist in the version now, but it should exist
@@ -78,10 +83,11 @@
         # Now undo the abort
         info=self._storage.undoInfo()
         tid=info[0]['id']
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.transactionalUndo(tid, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.transactionalUndo(tid, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         assert len(oids) == 1
         assert oids[0] == oid
         # And the object should be back in versions 'one' and ''


=== Zope3/lib/python/ZODB/tests/VersionStorage.py 1.11 => 1.11.10.1 ===
 
 from ZODB import POSException
+from ZODB.Transaction import Transaction
 from ZODB.tests.MinPO import MinPO
 from ZODB.tests.StorageTestBase import zodb_unpickle
 
@@ -144,10 +145,11 @@
         
 ##        s1 = self._storage.getSerial(oid)
         # Now abort the version -- must be done in a transaction
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.abortVersion(version, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.abortVersion(version, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
 ##        s2 = self._storage.getSerial(oid)
 ##        eq(s1, s2) # or self.assert(s2 > s1) ?
         eq(len(oids), 1)
@@ -159,14 +161,15 @@
         eq = self.assertEqual
         oid, version = self._setup_version()
         # Now abort a bogus version
-        self._storage.tpc_begin(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
 
         #JF# The spec is silent on what happens if you abort or commit
         #JF# a non-existent version. FileStorage consideres this a noop.
         #JF# We can change the spec, but until we do ....
         #JF# self.assertRaises(POSException.VersionError,
         #JF#                   self._storage.abortVersion,
-        #JF#                   'bogus', self._transaction)
+        #JF#                   'bogus', t)
 
         # And try to abort the empty version
         if (hasattr(self._storage, 'supportsTransactionalUndo')
@@ -174,12 +177,12 @@
             # XXX FileStorage used to be broken on this one
             self.assertRaises(POSException.VersionError,
                               self._storage.abortVersion,
-                              '', self._transaction)
+                              '', t)
         
         # But now we really try to abort the version
-        oids = self._storage.abortVersion(version, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        oids = self._storage.abortVersion(version, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         eq(len(oids), 1)
         eq(oids[0], oid)
         data, revid = self._storage.load(oid, '')
@@ -194,19 +197,24 @@
         oid1, version1 = self._setup_version('one')
         data, revid1 = self._storage.load(oid1, version1)
         eq(zodb_unpickle(data), MinPO(54))
-        self._storage.tpc_begin(self._transaction)
-        self.assertRaises(POSException.VersionCommitError,
-                          self._storage.commitVersion,
-                          'one', 'one', self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        try:
+            self.assertRaises(POSException.VersionCommitError,
+                              self._storage.commitVersion,
+                              'one', 'one', t)
+        finally:
+            self._storage.tpc_abort(t)
 
     def checkModifyAfterAbortVersion(self):
         eq = self.assertEqual
         oid, version = self._setup_version()
         # Now abort the version
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.abortVersion(version, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.abortVersion(version, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         # Load the object's current state (which gets us the revid)
         data, revid = self._storage.load(oid, '')
         # And modify it a few times
@@ -225,10 +233,11 @@
         data, revid = self._storage.load(oid, '')
         eq(zodb_unpickle(data), MinPO(51))
         # Try committing this version to the empty version
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.commitVersion(version, '', self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.commitVersion(version, '', t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         data, revid = self._storage.load(oid, '')
         eq(zodb_unpickle(data), MinPO(54))
 
@@ -251,11 +260,12 @@
         eq(zodb_unpickle(data), MinPO(51))
         
         # Okay, now let's commit object1 to version2
-        self._storage.tpc_begin(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
         oids = self._storage.commitVersion(version1, version2,
-                                           self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+                                           t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         eq(len(oids), 1)
         eq(oids[0], oid1)
         data, revid = self._storage.load(oid1, version2)
@@ -286,10 +296,11 @@
         eq(zodb_unpickle(data), MinPO(51))
 
         # First, let's abort version1
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.abortVersion(version1, self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.abortVersion(version1, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         eq(len(oids), 1)
         eq(oids[0], oid1)
         data, revid = self._storage.load(oid1, '')
@@ -310,10 +321,11 @@
         data, revid = self._storage.load(oid2, version2)
         eq(zodb_unpickle(data), MinPO(54))
         # Okay, now let's commit version2 back to the trunk
-        self._storage.tpc_begin(self._transaction)
-        oids = self._storage.commitVersion(version2, '', self._transaction)
-        self._storage.tpc_vote(self._transaction)
-        self._storage.tpc_finish(self._transaction)
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.commitVersion(version2, '', t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
         eq(len(oids), 1)
         eq(oids[0], oid2)
         data, revid = self._storage.load(oid1, '')


=== Zope3/lib/python/ZODB/tests/speed.py 1.1 => 1.1.88.1 ===
-# 
-# Zope Public License (ZPL) Version 1.0
-# -------------------------------------
-# 
-# Copyright (c) Digital Creations.  All rights reserved.
-# 
-# This license has been certified as Open Source(tm).
-# 
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-# 
-# 1. Redistributions in source code must retain the above copyright
-#    notice, this list of conditions, and the following disclaimer.
-# 
-# 2. Redistributions in binary form must reproduce the above copyright
-#    notice, this list of conditions, and the following disclaimer in
-#    the documentation and/or other materials provided with the
-#    distribution.
-# 
-# 3. Digital Creations requests that attribution be given to Zope
-#    in any manner possible. Zope includes a "Powered by Zope"
-#    button that is installed by default. While it is not a license
-#    violation to remove this button, it is requested that the
-#    attribution remain. A significant investment has been put
-#    into Zope, and this effort will continue if the Zope community
-#    continues to grow. This is one way to assure that growth.
-# 
-# 4. All advertising materials and documentation mentioning
-#    features derived from or use of this software must display
-#    the following acknowledgement:
-# 
-#      "This product includes software developed by Digital Creations
-#      for use in the Z Object Publishing Environment
-#      (http://www.zope.org/)."
-# 
-#    In the event that the product being advertised includes an
-#    intact Zope distribution (with copyright and license included)
-#    then this clause is waived.
-# 
-# 5. Names associated with Zope or Digital Creations must not be used to
-#    endorse or promote products derived from this software without
-#    prior written permission from Digital Creations.
-# 
-# 6. Modified redistributions of any form whatsoever must retain
-#    the following acknowledgment:
-# 
-#      "This product includes software developed by Digital Creations
-#      for use in the Z Object Publishing Environment
-#      (http://www.zope.org/)."
-# 
-#    Intact (re-)distributions of any official Zope release do not
-#    require an external acknowledgement.
-# 
-# 7. Modifications are encouraged but must be packaged separately as
-#    patches to official Zope releases.  Distributions that do not
-#    clearly separate the patches from the original work must be clearly
-#    labeled as unofficial distributions.  Modifications which do not
-#    carry the name Zope may be packaged in any form, as long as they
-#    conform to all of the clauses above.
-# 
-# 
-# Disclaimer
-# 
-#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
-#   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-#   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-#   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
-#   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-#   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-#   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
-#   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-#   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-#   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-#   SUCH DAMAGE.
-# 
-# 
-# This software consists of contributions made by Digital Creations and
-# many individuals on behalf of Digital Creations.  Specific
-# attributions are listed in the accompanying credits file.
+#
+# 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
 # 
 ##############################################################################
 usage="""Test speed of a ZODB storage


=== Zope3/lib/python/ZODB/tests/testFileStorage.py 1.13 => 1.13.12.1 ===
+
 import ZODB.FileStorage
 import sys, os, unittest
+import errno
+from Transaction import Transaction
 
 from ZODB.tests import StorageTestBase, BasicStorage, \
      TransactionalUndoStorage, VersionStorage, \
      TransactionalUndoVersionStorage, PackableStorage, \
      Synchronization, ConflictResolution, HistoryStorage, \
-     IteratorStorage, Corruption, RevisionStorage, PersistentStorage
+     IteratorStorage, Corruption, RevisionStorage, PersistentStorage, \
+     MTStorage, ReadOnlyStorage
 
 class FileStorageTests(
     StorageTestBase.StorageTestBase,
@@ -19,7 +24,10 @@
     ConflictResolution.ConflictResolvingStorage,
     HistoryStorage.HistoryStorage,
     IteratorStorage.IteratorStorage,
+    IteratorStorage.ExtendedIteratorStorage,
     PersistentStorage.PersistentStorage,
+    MTStorage.MTStorage,
+    ReadOnlyStorage.ReadOnlyStorage
     ):
 
     def open(self, **kwargs):
@@ -32,19 +40,90 @@
 
     def setUp(self):
         self.open(create=1)
-        StorageTestBase.StorageTestBase.setUp(self)
 
     def tearDown(self):
-        StorageTestBase.StorageTestBase.tearDown(self)
+        self._storage.close()
         for ext in '', '.old', '.tmp', '.lock', '.index':
             path = 'FileStorageTests.fs' + ext
             if os.path.exists(path):
                 os.remove(path)
 
+class FileStorageRecoveryTest(
+    StorageTestBase.StorageTestBase,
+    IteratorStorage.IteratorDeepCompare,
+    ):
+
+    def setUp(self):
+        self._storage = ZODB.FileStorage.FileStorage('Source.fs')
+        self._dst = ZODB.FileStorage.FileStorage('Dest.fs')
+
+    def tearDown(self):
+        self._storage.close()
+        self._dst.close()
+        for ext in '', '.old', '.tmp', '.lock', '.index':
+            for fs in 'Source', 'Dest':
+                path = fs + '.fs' + ext
+                try:
+                    os.remove(path)
+                except OSError, e:
+                    if e.errno <> errno.ENOENT: raise
+
+    def checkSimpleRecovery(self):
+        oid = self._storage.new_oid()
+        revid = self._dostore(oid, data=11)
+        revid = self._dostore(oid, revid=revid, data=12)
+        revid = self._dostore(oid, revid=revid, data=13)
+        self._dst.copyTransactionsFrom(self._storage)
+        self.compare(self._storage, self._dst)
+
+    def checkRecoveryAcrossVersions(self):
+        oid = self._storage.new_oid()
+        revid = self._dostore(oid, data=21)
+        revid = self._dostore(oid, revid=revid, data=22)
+        revid = self._dostore(oid, revid=revid, data=23, version='one')
+        revid = self._dostore(oid, revid=revid, data=34, version='one')
+        # Now commit the version
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        self._storage.commitVersion('one', '', t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
+        self._dst.copyTransactionsFrom(self._storage)
+        self.compare(self._storage, self._dst)
+
+    def checkRecoverAbortVersion(self):
+        oid = self._storage.new_oid()
+        revid = self._dostore(oid, data=21, version="one")
+        revid = self._dostore(oid, revid=revid, data=23, version='one')
+        revid = self._dostore(oid, revid=revid, data=34, version='one')
+        # Now abort the version and the creation
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.abortVersion('one', t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
+        self.assertEqual(oids, [oid])
+        self._dst.copyTransactionsFrom(self._storage)
+        self.compare(self._storage, self._dst)
+        # Also make sure the the last transaction has a data record
+        # with None for its data attribute, because we've undone the
+        # object.
+        for s in self._storage, self._dst:
+            iter = s.iterator()
+            for trans in iter:
+                pass # iterate until we get the last one
+            data = trans[0]
+            self.assertRaises(IndexError, lambda i:trans[i], 1)
+            self.assertEqual(data.oid, oid)
+            self.assertEqual(data.data, None)
+                
+
 def test_suite():
     suite = unittest.makeSuite(FileStorageTests, 'check')
     suite2 = unittest.makeSuite(Corruption.FileStorageCorruptTests, 'check')
+    suite3 = unittest.makeSuite(FileStorageRecoveryTest, 'check')
     suite.addTest(suite2)
+    suite.addTest(suite3)
     return suite
 
 def main():


=== Zope3/lib/python/ZODB/tests/testMappingStorage.py 1.1 => 1.1.14.1 ===
     def setUp(self):
         self._storage = ZODB.MappingStorage.MappingStorage()
-        StorageTestBase.StorageTestBase.setUp(self)
+
+    def tearDown(self):
+        self._storage.close()
 
 def test_suite():
     suite = unittest.makeSuite(MappingStorageTests, 'check')


=== Zope3/lib/python/ZODB/tests/testTransaction.py 1.2 => 1.2.40.1 ===
         meth = getattr(obj, meth_name)
         meth(1)
-        get_transaction().commit()
-
+        get_transaction().commit() 
+                            
     checkSubSingleCommit = lambda self:\
                            self.wrap_test(BasicTests, "checkSingleCommit")