[Zope3-checkins] CVS: Zope3/src/zodb/storage/tests - __init__.py:1.2 base.py:1.2 basic.py:1.2 bdbmixin.py:1.2 conflict.py:1.2 corruption.py:1.2 history.py:1.2 iterator.py:1.2 local.py:1.2 minpo.py:1.2 mt.py:1.2 packable.py:1.2 persistent.py:1.2 readonly.py:1.2 recovery.py:1.2 revision.py:1.2 speed.py:1.2 synchronization.py:1.2 test_autopack.py:1.2 test_config.py:1.2 test_create.py:1.2 test_file.py:1.2 test_fsindex.py:1.2 test_mapping.py:1.2 test_storage_api.py:1.2 test_virgin.py:1.2 test_whitebox.py:1.2 test_zodb_simple.py:1.2 timeiter.py:1.2 timepickles.py:1.2 undo.py:1.2 undoversion.py:1.2 version.py:1.2

Jim Fulton jim@zope.com
Wed, 25 Dec 2002 09:13:56 -0500


Update of /cvs-repository/Zope3/src/zodb/storage/tests
In directory cvs.zope.org:/tmp/cvs-serv15352/src/zodb/storage/tests

Added Files:
	__init__.py base.py basic.py bdbmixin.py conflict.py 
	corruption.py history.py iterator.py local.py minpo.py mt.py 
	packable.py persistent.py readonly.py recovery.py revision.py 
	speed.py synchronization.py test_autopack.py test_config.py 
	test_create.py test_file.py test_fsindex.py test_mapping.py 
	test_storage_api.py test_virgin.py test_whitebox.py 
	test_zodb_simple.py timeiter.py timepickles.py undo.py 
	undoversion.py version.py 
Log Message:
Grand renaming:

- Renamed most files (especially python modules) to lower case.

- Moved views and interfaces into separate hierarchies within each
  project, where each top-level directory under the zope package
  is a separate project.

- Moved everything to src from lib/python.

  lib/python will eventually go away. I need access to the cvs
  repository to make this happen, however.

There are probably some bits that are broken. All tests pass
and zope runs, but I haven't tried everything. There are a number
of cleanups I'll work on tomorrow.



=== Zope3/src/zodb/storage/tests/__init__.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zodb/storage/tests/__init__.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,14 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+# Having this makes debugging better.


=== Zope3/src/zodb/storage/tests/base.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zodb/storage/tests/base.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,287 @@
+##############################################################################
+#
+# 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
+#
+##############################################################################
+
+"""Provide a mixin base class for storage tests.
+
+The StorageTestBase class provides basic setUp() and tearDown()
+semantics (which you can override), and it also provides a helper
+method _dostore() which performs a complete store transaction for a
+single object revision.
+"""
+
+import errno
+import os
+import string
+import sys
+import types
+import unittest
+from cPickle import Pickler, Unpickler
+from cStringIO import StringIO
+
+from zodb.serialize import ConnectionObjectReader
+from zodb.ztransaction import Transaction
+
+from zodb.storage.tests.minpo import MinPO
+
+ZERO = '\0'*8
+DBHOME = 'test-db'
+
+
+def zodb_pickle(obj):
+    """Create a pickle in the format expected by ZODB."""
+    f = StringIO()
+    p = Pickler(f, 1)
+    klass = obj.__class__
+    mod = getattr(klass, "__module__", None)
+    state = obj.__getstate__()
+    # XXX
+    p.dump((mod, klass.__name__, None))
+    p.dump(state)
+    return f.getvalue(1)
+
+def zodb_unpickle(data):
+    """Unpickle an object stored using the format expected by ZODB."""
+    u = ConnectionObjectReader(None, {})
+    return u.getObject(data)
+
+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().
+    """
+    return handle_all_serials(oid, *args)[oid]
+
+def import_helper(name):
+    __import__(name)
+    return sys.modules[name]
+
+def removefs(base):
+    """Remove all files created by FileStorage with path base."""
+    for ext in '', '.old', '.tmp', '.lock', '.index', '.pack':
+        path = base + ext
+        try:
+            os.remove(path)
+        except os.error, err:
+            if err[0] != errno.ENOENT:
+                raise
+
+
+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._storage = None
+
+    def _close(self):
+        # You should override this if closing your storage requires additional
+        # shutdown operations.
+        if self._storage is not None:
+            self._storage.close()
+
+    def tearDown(self):
+        self._close()
+
+    def _dostore(self, oid=None, revid=None, data=None, version=None,
+                 already_pickled=0, user=None, description=None):
+        """Do a complete storage transaction.  The defaults are:
+
+         - oid=None, ask the storage for a new oid
+         - revid=None, use a revid of ZERO
+         - data=None, pickle up some arbitrary data (the integer 7)
+         - version=None, use the empty string version
+
+        Returns the object's new revision id.
+        """
+        if oid is None:
+            oid = self._storage.new_oid()
+        if revid is None:
+            revid = ZERO
+        if data is None:
+            data = MinPO(7)
+        if type(data) == types.IntType:
+            data = MinPO(data)
+        if not already_pickled:
+            data = zodb_pickle(data)
+        if version is None:
+            version = ''
+        # Begin the transaction
+        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,
+                   user=None, description=None):
+        return self._dostore(oid, revid, data, version, already_pickled=1,
+                             user=user, description=description)
+    # The following methods depend on optional storage features.
+
+    def _undo(self, tid, oid=None):
+        # Undo a tid that affects a single object (oid).
+        # XXX This is very specialized
+        t = Transaction()
+        t.note("undo")
+        self._storage.tpc_begin(t)
+        oids = self._storage.transactionalUndo(tid, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
+        if oid is not None:
+            self.assertEqual(len(oids), 1)
+            self.assertEqual(oids[0], oid)
+        return self._storage.lastTransaction()
+
+    def _commitVersion(self, src, dst):
+        t = Transaction()
+        t.note("commit %r to %r" % (src, dst))
+        self._storage.tpc_begin(t)
+        oids = self._storage.commitVersion(src, dst, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
+        return oids
+
+    def _abortVersion(self, ver):
+        t = Transaction()
+        t.note("abort %r" % ver)
+        self._storage.tpc_begin(t)
+        oids = self._storage.abortVersion(ver, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
+        return oids
+
+
+# Base class for unit tests at the ZODB layer
+
+import os
+import errno
+
+from zodb.db import DB
+from transaction import get_transaction
+
+
+# Basic test framework class for both the Full and Minimal Berkeley storages
+
+import os
+import errno
+
+from zodb.storage.base import BerkeleyConfig
+
+
+
+class BerkeleyTestBase(StorageTestBase):
+    def _zap_dbhome(self, dir):
+        # If the tests exited with any uncommitted objects, they'll blow up
+        # subsequent tests because the next transaction commit will try to
+        # commit those object.  But they're tied to closed databases, so
+        # that's broken.  Aborting the transaction now saves us the headache.
+        try:
+            for file in os.listdir(dir):
+                os.unlink(os.path.join(dir, file))
+            os.removedirs(dir)
+        except OSError, e:
+            if e.errno <> errno.ENOENT:
+                raise
+
+    def _mk_dbhome(self, dir):
+        # Checkpointing just slows the tests down because we have to wait for
+        # the thread to properly shutdown.  This can take up to 10 seconds, so
+        # for the purposes of the test suite we shut off this thread.
+        config = BerkeleyConfig()
+        config.interval = 0
+        os.mkdir(dir)
+        try:
+            return self.ConcreteStorage(dir, config=config)
+        except:
+            self._zap_dbhome(dir)
+            raise
+
+    def setUp(self):
+        StorageTestBase.setUp(self)
+        self._zap_dbhome(DBHOME)
+        self._storage = self._mk_dbhome(DBHOME)
+
+    def tearDown(self):
+        StorageTestBase.tearDown(self)
+        self._zap_dbhome(DBHOME)
+
+
+
+class ZODBTestBase(BerkeleyTestBase):
+    def setUp(self):
+        BerkeleyTestBase.setUp(self)
+        self._db = None
+        try:
+            self._db = DB(self._storage)
+            self._conn = self._db.open()
+            self._root = self._conn.root()
+        except:
+            self.tearDown()
+            raise
+
+    def _close(self):
+        if self._db is not None:
+            self._db.close()
+            self._db = self._storage = self._conn = self._root = None
+
+    def tearDown(self):
+        # If the tests exited with any uncommitted objects, they'll blow up
+        # subsequent tests because the next transaction commit will try to
+        # commit those object.  But they're tied to closed databases, so
+        # that's broken.  Aborting the transaction now saves us the headache.
+        try:
+            get_transaction().abort()
+            self._close()
+        finally:
+            BerkeleyTestBase.tearDown(self)


=== Zope3/src/zodb/storage/tests/basic.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zodb/storage/tests/basic.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,188 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Run the basic tests for a storage as described in the official storage API
+
+The most complete and most out-of-date description of the interface is:
+http://www.zope.org/Documentation/Developer/Models/ZODB/ZODB_Architecture_Storage_Interface_Info.html
+
+All storages should be able to pass these tests.
+
+$Id$
+"""
+
+from zodb.ztransaction import Transaction
+from zodb import interfaces
+
+from zodb.storage.tests.minpo import MinPO
+from zodb.storage.tests.base \
+     import zodb_unpickle, zodb_pickle, handle_serials
+
+ZERO = '\0'*8
+
+
+
+class BasicStorage:
+    def checkBasics(self):
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        # This should simply return
+        self._storage.tpc_begin(t)
+        # Aborting is easy
+        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(t)
+        self.assertRaises(
+            interfaces.StorageTransactionError,
+            self._storage.store,
+            0, 0, 0, 0, Transaction())
+
+        try:
+            self._storage.abortVersion('dummy', Transaction())
+        except (interfaces.StorageTransactionError,
+                interfaces.VersionCommitError):
+            pass # test passed ;)
+        else:
+            assert 0, "Should have failed, invalid transaction."
+
+        try:
+            self._storage.commitVersion('dummy', 'dummer', Transaction())
+        except (interfaces.StorageTransactionError,
+                interfaces.VersionCommitError):
+            pass # test passed ;)
+        else:
+            assert 0, "Should have failed, invalid transaction."
+
+        self.assertRaises(
+            interfaces.StorageTransactionError,
+            self._storage.store,
+            0, 1, 2, 3, Transaction())
+        self._storage.tpc_abort(t)
+
+    def checkSerialIsNoneForInitialRevision(self):
+        eq = self.assertEqual
+        oid = self._storage.new_oid()
+        txn = Transaction()
+        self._storage.tpc_begin(txn)
+        # Use None for serial.  Don't use _dostore() here because that coerces
+        # serial=None to serial=ZERO.
+        r1 = self._storage.store(oid, None, zodb_pickle(MinPO(11)),
+                                       '', txn)
+        r2 = self._storage.tpc_vote(txn)
+        self._storage.tpc_finish(txn)
+        newrevid = handle_serials(oid, r1, r2)
+        data, revid = self._storage.load(oid, '')
+        value = zodb_unpickle(data)
+        eq(value, MinPO(11))
+        eq(revid, newrevid)
+
+    def checkNonVersionStore(self, oid=None, revid=None, version=None):
+        revid = ZERO
+        newrevid = self._dostore(revid=revid)
+        # Finish the transaction.
+        self.assertNotEqual(newrevid, revid)
+
+    def checkNonVersionStoreAndLoad(self):
+        eq = self.assertEqual
+        oid = self._storage.new_oid()
+        self._dostore(oid=oid, data=MinPO(7))
+        data, revid = self._storage.load(oid, '')
+        value = zodb_unpickle(data)
+        eq(value, MinPO(7))
+        # Now do a bunch of updates to an object
+        for i in range(13, 22):
+            revid = self._dostore(oid, revid=revid, data=MinPO(i))
+        # Now get the latest revision of the object
+        data, revid = self._storage.load(oid, '')
+        eq(zodb_unpickle(data), MinPO(21))
+
+    def checkNonVersionModifiedInVersion(self):
+        oid = self._storage.new_oid()
+        self._dostore(oid=oid)
+        self.assertEqual(self._storage.modifiedInVersion(oid), '')
+
+    def checkConflicts(self):
+        oid = self._storage.new_oid()
+        revid1 = self._dostore(oid, data=MinPO(11))
+        revid2 = self._dostore(oid, revid=revid1, data=MinPO(12))
+        self.assertRaises(interfaces.ConflictError,
+                          self._dostore,
+                          oid, revid=revid1, data=MinPO(13))
+
+    def checkWriteAfterAbort(self):
+        oid = self._storage.new_oid()
+        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(t)
+        # Now start all over again
+        oid = self._storage.new_oid()
+        self._dostore(oid=oid, data=MinPO(6))
+
+    def checkAbortAfterVote(self):
+        oid1 = self._storage.new_oid()
+        revid1 = self._dostore(oid=oid1, data=MinPO(-2))
+        oid = self._storage.new_oid()
+        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(t)
+        self._storage.tpc_abort(t)
+        # Now start all over again
+        oid = self._storage.new_oid()
+        revid = self._dostore(oid=oid, data=MinPO(6))
+
+        for oid, revid in [(oid1, revid1), (oid, revid)]:
+            data, _revid = self._storage.load(oid, '')
+            self.assertEqual(revid, _revid)
+
+    def checkStoreTwoObjects(self):
+        noteq = self.assertNotEqual
+        p31, p32, p51, p52 = map(MinPO, (31, 32, 51, 52))
+        oid1 = self._storage.new_oid()
+        oid2 = self._storage.new_oid()
+        noteq(oid1, oid2)
+        revid1 = self._dostore(oid1, data=p31)
+        revid2 = self._dostore(oid2, data=p51)
+        noteq(revid1, revid2)
+        revid3 = self._dostore(oid1, revid=revid1, data=p32)
+        revid4 = self._dostore(oid2, revid=revid2, data=p52)
+        noteq(revid3, revid4)
+
+    def checkGetSerial(self):
+        if not hasattr(self._storage, 'getSerial'):
+            return
+        eq = self.assertEqual
+        p41, p42 = map(MinPO, (41, 42))
+        oid = self._storage.new_oid()
+        self.assertRaises(KeyError, self._storage.getSerial, oid)
+        # Now store a revision
+        revid1 = self._dostore(oid, data=p41)
+        eq(revid1, self._storage.getSerial(oid))
+        # 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/src/zodb/storage/tests/bdbmixin.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zodb/storage/tests/bdbmixin.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,27 @@
+##############################################################################
+#
+# 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
+#
+##############################################################################
+
+"""Provide a mixin base class for Berkeley storage tests.
+"""
+
+from zodb.storage.tests import base
+
+class MinimalTestBase(base.BerkeleyTestBase):
+    from zodb.storage.bdbminimal import BDBMinimalStorage
+    ConcreteStorage = BDBMinimalStorage
+
+
+class FullTestBase(base.BerkeleyTestBase):
+    from zodb.storage.bdbfull import BDBFullStorage
+    ConcreteStorage = BDBFullStorage


=== Zope3/src/zodb/storage/tests/conflict.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zodb/storage/tests/conflict.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,205 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Tests for application-level conflict resolution."""
+
+from zodb.ztransaction import Transaction
+from zodb.interfaces import ConflictError, UndoError
+from zodb.conflict import ResolveObjectReader
+from persistence import Persistent
+
+from zodb.storage.tests.base import zodb_unpickle, zodb_pickle
+
+import sys
+import types
+from cPickle import Pickler, Unpickler
+from cStringIO import StringIO
+
+class PCounter(Persistent):
+
+    _value = 0
+
+    def __repr__(self):
+        return "<PCounter %d>" % self._value
+
+    def inc(self):
+        self._value = self._value + 1
+
+class RPCounter(PCounter):
+    """Version of PCounter that supports conflict resolution."""
+
+    def _p_resolveConflict(self, oldState, savedState, newState):
+        savedDiff = savedState['_value'] - oldState['_value']
+        newDiff = newState['_value'] - oldState['_value']
+
+        oldState['_value'] = oldState['_value'] + savedDiff + newDiff
+
+        return oldState
+
+    # XXX What if _p_resolveConflict _thinks_ it resolved the
+    # conflict, but did something wrong?
+
+class PCounter2(PCounter):
+    def _p_resolveConflict(self, oldState, savedState, newState):
+        raise ConflictError
+
+class PCounter3(PCounter):
+    def _p_resolveConflict(self, oldState, savedState, newState):
+        raise AttributeError, "no attribute (testing conflict resolution)"
+
+class PCounter4(PCounter):
+    def _p_resolveConflict(self, oldState, savedState):
+        raise RuntimeError, "Can't get here; not enough args"
+
+class ConflictResolvingStorage:
+
+    def checkResolve(self):
+        obj = RPCounter()
+        obj.inc()
+
+        oid = self._storage.new_oid()
+
+        revid1 = self._dostoreNP(oid, data=zodb_pickle(obj))
+
+        obj.inc()
+        obj.inc()
+        # The effect of committing two transactions with the same
+        # pickle is to commit two different transactions relative to
+        # revid1 that add two to _value.
+        revid2 = self._dostoreNP(oid, revid=revid1, data=zodb_pickle(obj))
+        revid3 = self._dostoreNP(oid, revid=revid1, data=zodb_pickle(obj))
+
+        data, serialno = self._storage.load(oid, '')
+        inst = zodb_unpickle(data)
+        self.assertEqual(inst._value, 5)
+
+    def unresolvable(self, klass):
+        self.assert_(ResolveObjectReader.unresolvable(PCounter))
+
+    def checkUnresolvable1(self):
+        obj = PCounter()
+        obj.inc()
+
+        oid = self._storage.new_oid()
+
+        revid1 = self._dostoreNP(oid, data=zodb_pickle(obj))
+
+        obj.inc()
+        obj.inc()
+        # The effect of committing two transactions with the same
+        # pickle is to commit two different transactions relative to
+        # revid1 that add two to _value.
+        revid2 = self._dostoreNP(oid, revid=revid1, data=zodb_pickle(obj))
+        self.assertRaises(ConflictError,
+                          self._dostoreNP,
+                          oid, revid=revid1, data=zodb_pickle(obj))
+        self.unresolvable(PCounter)
+
+    def checkUnresolvable2(self):
+        obj = PCounter2()
+        obj.inc()
+
+        oid = self._storage.new_oid()
+
+        revid1 = self._dostoreNP(oid, data=zodb_pickle(obj))
+
+        obj.inc()
+        obj.inc()
+        # The effect of committing two transactions with the same
+        # pickle is to commit two different transactions relative to
+        # revid1 that add two to _value.
+        revid2 = self._dostoreNP(oid, revid=revid1, data=zodb_pickle(obj))
+        self.assertRaises(ConflictError,
+                          self._dostoreNP,
+                          oid, revid=revid1, data=zodb_pickle(obj))
+
+    def checkBuggyResolve1(self):
+        obj = PCounter3()
+        obj.inc()
+
+        oid = self._storage.new_oid()
+
+        revid1 = self._dostoreNP(oid, data=zodb_pickle(obj))
+
+        obj.inc()
+        obj.inc()
+        # The effect of committing two transactions with the same
+        # pickle is to commit two different transactions relative to
+        # revid1 that add two to _value.
+        revid2 = self._dostoreNP(oid, revid=revid1, data=zodb_pickle(obj))
+        self.assertRaises(AttributeError,
+                          self._dostoreNP,
+                          oid, revid=revid1, data=zodb_pickle(obj))
+
+    def checkBuggyResolve2(self):
+        obj = PCounter4()
+        obj.inc()
+
+        oid = self._storage.new_oid()
+
+        revid1 = self._dostoreNP(oid, data=zodb_pickle(obj))
+
+        obj.inc()
+        obj.inc()
+        # The effect of committing two transactions with the same
+        # pickle is to commit two different transactions relative to
+        # revid1 that add two to _value.
+        revid2 = self._dostoreNP(oid, revid=revid1, data=zodb_pickle(obj))
+        self.assertRaises(TypeError,
+                          self._dostoreNP,
+                          oid, revid=revid1, data=zodb_pickle(obj))
+
+class ConflictResolvingTransUndoStorage:
+
+    def checkUndoConflictResolution(self):
+        # This test is based on checkNotUndoable in the
+        # TransactionalUndoStorage test suite.  Except here, conflict
+        # resolution should allow us to undo the transaction anyway.
+
+        obj = RPCounter()
+        obj.inc()
+        oid = self._storage.new_oid()
+        revid_a = self._dostore(oid, data=obj)
+        obj.inc()
+        revid_b = self._dostore(oid, revid=revid_a, data=obj)
+        obj.inc()
+        revid_c = self._dostore(oid, revid=revid_b, data=obj)
+        # Start the undo
+        info = self._storage.undoInfo()
+        tid = info[1]['id']
+        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
+        # TransactionalUndoStorage test suite.  Except here, conflict
+        # resolution should allow us to undo the transaction anyway.
+
+        obj = PCounter2()
+        obj.inc()
+        oid = self._storage.new_oid()
+        revid_a = self._dostore(oid, data=obj)
+        obj.inc()
+        revid_b = self._dostore(oid, revid=revid_a, data=obj)
+        obj.inc()
+        revid_c = self._dostore(oid, revid=revid_b, data=obj)
+        # Start the undo
+        info = self._storage.undoInfo()
+        tid = info[1]['id']
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        self.assertRaises(UndoError, self._storage.transactionalUndo,
+                          tid, t)
+        self._storage.tpc_abort(t)


=== Zope3/src/zodb/storage/tests/corruption.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zodb/storage/tests/corruption.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,83 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Do some minimal tests of data corruption"""
+
+import os
+import random
+import stat
+import tempfile
+import unittest
+
+from zodb.storage.file import FileStorage
+from zodb.storage.tests.base import StorageTestBase
+
+class FileStorageCorruptTests(StorageTestBase):
+
+    def setUp(self):
+        self.path = tempfile.mktemp()
+        self._storage = FileStorage(self.path, create=1)
+
+    def tearDown(self):
+        self._storage.close()
+        for ext in '', '.old', '.tmp', '.lock', '.index':
+            path = self.path + ext
+            if os.path.exists(path):
+                os.remove(path)
+
+    def _do_stores(self):
+        oids = []
+        for i in range(5):
+            oid = self._storage.new_oid()
+            revid = self._dostore(oid)
+            oids.append((oid, revid))
+        return oids
+
+    def _check_stores(self, oids):
+        for oid, revid in oids:
+            data, s_revid = self._storage.load(oid, '')
+            self.assertEqual(s_revid, revid)
+
+    def checkTruncatedIndex(self):
+        oids = self._do_stores()
+        self._close()
+
+        # truncation the index file
+        path = self.path + '.index'
+        self.failUnless(os.path.exists(path))
+        f = open(path, 'r+')
+        f.seek(0, 2)
+        size = f.tell()
+        f.seek(size / 2)
+        f.truncate()
+        f.close()
+
+        self._storage = FileStorage(self.path)
+        self._check_stores(oids)
+
+    def checkCorruptedIndex(self):
+        oids = self._do_stores()
+        self._close()
+
+        # truncation the index file
+        path = self.path + '.index'
+        self.failUnless(os.path.exists(path))
+        size = os.stat(path)[stat.ST_SIZE]
+        f = open(path, 'r+')
+        while f.tell() < size:
+            f.seek(random.randrange(1, size / 10), 1)
+            f.write('\000')
+        f.close()
+
+        self._storage = FileStorage(self.path)
+        self._check_stores(oids)


=== Zope3/src/zodb/storage/tests/history.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zodb/storage/tests/history.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,226 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Run the history() related tests for a storage.
+
+Any storage that supports the history() method should be able to pass
+all these tests.
+"""
+
+from zodb.ztransaction import Transaction
+from zodb.storage.tests.minpo import MinPO
+from zodb.storage.tests.base import zodb_unpickle
+
+
+
+class HistoryStorage:
+    def checkSimpleHistory(self):
+        eq = self.assertEqual
+        # Store a couple of non-version revisions of the 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 get various snapshots of the object's history
+        h = self._storage.history(oid, size=1)
+        eq(len(h), 1)
+        d = h[0]
+        eq(d['serial'], revid3)
+        eq(d['version'], '')
+        # Try to get 2 historical revisions
+        h = self._storage.history(oid, size=2)
+        eq(len(h), 2)
+        d = h[0]
+        eq(d['serial'], revid3)
+        eq(d['version'], '')
+        d = h[1]
+        eq(d['serial'], revid2)
+        eq(d['version'], '')
+        # Try to get all 3 historical revisions
+        h = self._storage.history(oid, size=3)
+        eq(len(h), 3)
+        d = h[0]
+        eq(d['serial'], revid3)
+        eq(d['version'], '')
+        d = h[1]
+        eq(d['serial'], revid2)
+        eq(d['version'], '')
+        d = h[2]
+        eq(d['serial'], revid1)
+        eq(d['version'], '')
+        # There should be no more than 3 revisions
+        h = self._storage.history(oid, size=4)
+        eq(len(h), 3)
+        d = h[0]
+        eq(d['serial'], revid3)
+        eq(d['version'], '')
+        d = h[1]
+        eq(d['serial'], revid2)
+        eq(d['version'], '')
+        d = h[2]
+        eq(d['serial'], revid1)
+        eq(d['version'], '')
+
+    def checkVersionHistory(self):
+        eq = self.assertEqual
+        # Store a couple of non-version revisions
+        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 store some new revisions in a version
+        version = 'test-version'
+        revid4 = self._dostore(oid, revid=revid3, data=MinPO(14),
+                               version=version)
+        revid5 = self._dostore(oid, revid=revid4, data=MinPO(15),
+                               version=version)
+        revid6 = self._dostore(oid, revid=revid5, data=MinPO(16),
+                               version=version)
+        # Now, try to get the six historical revisions (first three are in
+        # 'test-version', followed by the non-version revisions).
+        h = self._storage.history(oid, version, 100)
+        eq(len(h), 6)
+        d = h[0]
+        eq(d['serial'], revid6)
+        eq(d['version'], version)
+        d = h[1]
+        eq(d['serial'], revid5)
+        eq(d['version'], version)
+        d = h[2]
+        eq(d['serial'], revid4)
+        eq(d['version'], version)
+        d = h[3]
+        eq(d['serial'], revid3)
+        eq(d['version'], '')
+        d = h[4]
+        eq(d['serial'], revid2)
+        eq(d['version'], '')
+        d = h[5]
+        eq(d['serial'], revid1)
+        eq(d['version'], '')
+
+    def checkHistoryAfterVersionCommit(self):
+        eq = self.assertEqual
+        # Store a couple of non-version revisions
+        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 store some new revisions in a version
+        version = 'test-version'
+        revid4 = self._dostore(oid, revid=revid3, data=MinPO(14),
+                               version=version)
+        revid5 = self._dostore(oid, revid=revid4, data=MinPO(15),
+                               version=version)
+        revid6 = self._dostore(oid, revid=revid5, data=MinPO(16),
+                               version=version)
+        # Now commit the version
+        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
+        # FileStorage is broken here; the serial number in the post-commit
+        # non-version revision will be the same as the serial number of the
+        # previous in-version revision.
+        #
+        # BAW: Using load() is the only way to get the serial number of the
+        # current revision of the object.  But at least this works for both
+        # broken and working storages.
+        ign, revid7 = self._storage.load(oid, '')
+        # Now, try to get the six historical revisions (first three are in
+        # 'test-version', followed by the non-version revisions).
+        h = self._storage.history(oid, version, 100)
+        eq(len(h), 7)
+        d = h[0]
+        eq(d['serial'], revid7)
+        eq(d['version'], '')
+        d = h[1]
+        eq(d['serial'], revid6)
+        eq(d['version'], version)
+        d = h[2]
+        eq(d['serial'], revid5)
+        eq(d['version'], version)
+        d = h[3]
+        eq(d['serial'], revid4)
+        eq(d['version'], version)
+        d = h[4]
+        eq(d['serial'], revid3)
+        eq(d['version'], '')
+        d = h[5]
+        eq(d['serial'], revid2)
+        eq(d['version'], '')
+        d = h[6]
+        eq(d['serial'], revid1)
+        eq(d['version'], '')
+
+    def checkHistoryAfterVersionAbort(self):
+        eq = self.assertEqual
+        # Store a couple of non-version revisions
+        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 store some new revisions in a version
+        version = 'test-version'
+        revid4 = self._dostore(oid, revid=revid3, data=MinPO(14),
+                               version=version)
+        revid5 = self._dostore(oid, revid=revid4, data=MinPO(15),
+                               version=version)
+        revid6 = self._dostore(oid, revid=revid5, data=MinPO(16),
+                               version=version)
+        # Now commit the version
+        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
+        # FileStorage is broken here; the serial number in the post-commit
+        # non-version revision will be the same as the serial number of the
+        # previous in-version revision.
+        #
+        # BAW: Using load() is the only way to get the serial number of the
+        # current revision of the object.  But at least this works for both
+        # broken and working storages.
+        ign, revid7 = self._storage.load(oid, '')
+        # Now, try to get the six historical revisions (first three are in
+        # 'test-version', followed by the non-version revisions).
+        h = self._storage.history(oid, version, 100)
+        eq(len(h), 7)
+        d = h[0]
+        eq(d['serial'], revid7)
+        eq(d['version'], '')
+        d = h[1]
+        eq(d['serial'], revid6)
+        eq(d['version'], version)
+        d = h[2]
+        eq(d['serial'], revid5)
+        eq(d['version'], version)
+        d = h[3]
+        eq(d['serial'], revid4)
+        eq(d['version'], version)
+        d = h[4]
+        eq(d['serial'], revid3)
+        eq(d['version'], '')
+        d = h[5]
+        eq(d['serial'], revid2)
+        eq(d['version'], '')
+        d = h[6]
+        eq(d['serial'], revid1)
+        eq(d['version'], '')


=== Zope3/src/zodb/storage/tests/iterator.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zodb/storage/tests/iterator.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,193 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Run tests against the iterator() interface for storages.
+
+Any storage that supports the iterator() method should be able to pass
+all these tests.
+"""
+
+from zodb.storage.tests.minpo import MinPO
+from zodb.storage.tests.base import zodb_unpickle
+from zodb.utils import u64, p64
+from zodb.ztransaction import Transaction
+
+
+class IteratorCompare:
+
+    def iter_verify(self, txniter, revids, val0):
+        eq = self.assertEqual
+        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)
+                eq(rec.serial, revid)
+                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)
+
+    def checkTransactionExtensionFromIterator(self):
+        oid = self._storage.new_oid()
+        revid = self._dostore(oid, data=MinPO(1))
+        iter = self._storage.iterator()
+        count = 0
+        for txn in iter:
+            self.assertEqual(txn._extension, {})
+            count +=1
+        self.assertEqual(count, 1)
+
+
+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/src/zodb/storage/tests/local.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zodb/storage/tests/local.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,27 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+class LocalStorage:
+    """A single test that only make sense for local storages.
+
+    A local storage is one that doens't use ZEO. The __len__()
+    implementation for ZEO is inexact.
+    """
+    def checkLen(self):
+        eq = self.assertEqual
+        # The length of the database ought to grow by one each time
+        eq(len(self._storage), 0)
+        self._dostore()
+        eq(len(self._storage), 1)
+        self._dostore()
+        eq(len(self._storage), 2)


=== Zope3/src/zodb/storage/tests/minpo.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zodb/storage/tests/minpo.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,26 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""A minimal persistent object to use for tests"""
+
+from persistence import Persistent
+
+class MinPO(Persistent):
+    def __init__(self, value=None):
+        self.value = value
+
+    def __cmp__(self, aMinPO):
+        return cmp(self.value, aMinPO.value)
+
+    def __repr__(self):
+        return "MinPO(%s)" % self.value


=== Zope3/src/zodb/storage/tests/mt.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:53 2002
+++ Zope3/src/zodb/storage/tests/mt.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,206 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+import random
+import threading
+import time
+
+import zodb.db
+from persistence.dict import PersistentDict
+from transaction import get_transaction
+from zodb.ztransaction import Transaction
+
+from zodb.storage.tests.base \
+     import StorageTestBase, zodb_pickle, zodb_unpickle, handle_serials
+from zodb.storage.tests.minpo import MinPO
+from zodb.interfaces 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.setDaemon(1)
+        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 = PersistentDict()
+                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(60)
+        for t in threads:
+            self.failIf(t.isAlive(), "thread failed to finish in 60 seconds")
+
+    def check2ZODBThreads(self):
+        db = zodb.db.DB(self._storage)
+        self._checkNThreads(2, ZODBClientThread, db, self)
+
+    def check7ZODBThreads(self):
+        db = zodb.db.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)


=== Zope3/src/zodb/storage/tests/packable.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:53 2002
+++ Zope3/src/zodb/storage/tests/packable.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,368 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Run some tests relevant for storages that support pack()."""
+
+try:
+    import cPickle
+    pickle = cPickle
+    #import cPickle as pickle
+except ImportError:
+    import pickle
+
+from cStringIO import StringIO
+
+import time
+from zodb.db import DB
+from zodb.storage.tests.minpo import MinPO
+from persistence import Persistent
+from transaction import get_transaction
+
+ZERO = '\0'*8
+
+
+# This class is for the root object.  It must not contain a getoid() method
+# (really, attribute).  The persistent pickling machinery -- in the dumps()
+# function below -- will pickle Root objects as normal, but any attributes
+# which reference persistent Object instances will get pickled as persistent
+# ids, not as the object's state.  This makes the referencesf stuff work,
+# because it pickle sniffs for persistent ids (so we have to get those
+# persistent ids into the root object's pickle).
+class Root:
+    pass
+
+
+# This is the persistent Object class.  Because it has a getoid() method, the
+# persistent pickling machinery -- in the dumps() function below -- will
+# pickle the oid string instead of the object's actual state.  Yee haw, this
+# stuff is deep. ;)
+class Object:
+    def __init__(self, oid):
+        self._oid = oid
+
+    def getoid(self):
+        return self._oid
+
+class C(Persistent):
+    pass
+
+
+# Here's where all the magic occurs.  Sadly, the pickle module is a bit
+# underdocumented, but here's what happens: by setting the persistent_id
+# attribute to getpersid() on the pickler, that function gets called for every
+# object being pickled.  By returning None when the object has no getoid
+# attribute, it signals pickle to serialize the object as normal.  That's how
+# the Root instance gets pickled correctly.  But, if the object has a getoid
+# attribute, then by returning that method's value, we tell pickle to
+# serialize the persistent id of the object instead of the object's state.
+# That sets the pickle up for proper sniffing by the referencesf machinery.
+# Fun, huh?
+def dumps(obj):
+    def getpersid(obj):
+        if hasattr(obj, 'getoid'):
+            return obj.getoid()
+        return None
+    s = StringIO()
+    p = pickle.Pickler(s)
+    p.persistent_id = getpersid
+    p.dump(obj)
+    return s.getvalue()
+
+
+
+class PackableStorageBase:
+    # We keep a cache of object ids to instances so that the unpickler can
+    # easily return any persistent object.
+    _cache = {}
+
+    def _newobj(self):
+        # This is a convenience method to create a new persistent Object
+        # instance.  It asks the storage for a new object id, creates the
+        # instance with the given oid, populates the cache and returns the
+        # object.
+        oid = self._storage.new_oid()
+        obj = Object(oid)
+        self._cache[obj.getoid()] = obj
+        return obj
+
+    def _makeloader(self):
+        # This is the other side of the persistent pickling magic.  We need a
+        # custom unpickler to mirror our custom pickler above.  By setting the
+        # persistent_load function of the unpickler to self._cache.get(),
+        # whenever a persistent id is unpickled, it will actually return the
+        # Object instance out of the cache.  As far as returning a function
+        # with an argument bound to an instance attribute method, we do it
+        # this way because it makes the code in the tests more succinct.
+        #
+        # BUT!  Be careful in your use of loads() vs. pickle.loads().  loads()
+        # should only be used on the Root object's pickle since it's the only
+        # special one.  All the Object instances should use pickle.loads().
+        def loads(str, persfunc=self._cache.get):
+            fp = StringIO(str)
+            u = pickle.Unpickler(fp)
+            u.persistent_load = persfunc
+            return u.load()
+        return loads
+
+
+
+class PackableStorage(PackableStorageBase):
+    def _initroot(self):
+        try:
+            self._storage.load(ZERO, '')
+        except KeyError:
+            from persistence.dict import PersistentDict
+            from zodb.ztransaction import Transaction
+            file = StringIO()
+            p = cPickle.Pickler(file, 1)
+            p.dump((PersistentDict, None))
+            p.dump(PersistentDict().__getstate__())
+            t = Transaction()
+            t.note("initial database creation")
+            self._storage.tpc_begin(t)
+            self._storage.store(ZERO, None, file.getvalue(), '', t)
+            self._storage.tpc_vote(t)
+            self._storage.tpc_finish(t)
+
+    def checkPackEmptyStorage(self):
+        self._storage.pack(time.time())
+
+    def checkPackTomorrow(self):
+        self._initroot()
+        self._storage.pack(time.time() + 100000)
+
+    def checkPackYesterday(self):
+        self._initroot()
+        self._storage.pack(time.time() - 100000)
+
+    def checkPackAllRevisions(self):
+        self._initroot()
+        eq = self.assertEqual
+        raises = self.assertRaises
+        # Create a `persistent' object
+        obj = self._newobj()
+        oid = obj.getoid()
+        obj.value = 1
+        # Commit three different revisions
+        revid1 = self._dostoreNP(oid, data=pickle.dumps(obj))
+        obj.value = 2
+        revid2 = self._dostoreNP(oid, revid=revid1, data=pickle.dumps(obj))
+        obj.value = 3
+        revid3 = self._dostoreNP(oid, revid=revid2, data=pickle.dumps(obj))
+        # Now make sure all three revisions can be extracted
+        data = self._storage.loadSerial(oid, revid1)
+        pobj = pickle.loads(data)
+        eq(pobj.getoid(), oid)
+        eq(pobj.value, 1)
+        data = self._storage.loadSerial(oid, revid2)
+        pobj = pickle.loads(data)
+        eq(pobj.getoid(), oid)
+        eq(pobj.value, 2)
+        data = self._storage.loadSerial(oid, revid3)
+        pobj = pickle.loads(data)
+        eq(pobj.getoid(), oid)
+        eq(pobj.value, 3)
+        # Now pack all transactions; need to sleep a second to make
+        # sure that the pack time is greater than the last commit time.
+        time.sleep(1)
+        self._storage.pack(time.time())
+        # All revisions of the object should be gone, since there is no
+        # reference from the root object to this object.
+        raises(KeyError, self._storage.loadSerial, oid, revid1)
+        raises(KeyError, self._storage.loadSerial, oid, revid2)
+        raises(KeyError, self._storage.loadSerial, oid, revid3)
+
+    def checkPackJustOldRevisions(self):
+        eq = self.assertEqual
+        raises = self.assertRaises
+        loads = self._makeloader()
+        # Create a root object.  This can't be an instance of Object,
+        # otherwise the pickling machinery will serialize it as a persistent
+        # id and not as an object that contains references (persistent ids) to
+        # other objects.
+        root = Root()
+        # Create a persistent object, with some initial state
+        obj = self._newobj()
+        oid = obj.getoid()
+        # Link the root object to the persistent object, in order to keep the
+        # persistent object alive.  Store the root object.
+        root.obj = obj
+        root.value = 0
+        revid0 = self._dostoreNP(ZERO, data=dumps(root))
+        # Make sure the root can be retrieved
+        data, revid = self._storage.load(ZERO, '')
+        eq(revid, revid0)
+        eq(loads(data).value, 0)
+        # Commit three different revisions of the other object
+        obj.value = 1
+        revid1 = self._dostoreNP(oid, data=pickle.dumps(obj))
+        obj.value = 2
+        revid2 = self._dostoreNP(oid, revid=revid1, data=pickle.dumps(obj))
+        obj.value = 3
+        revid3 = self._dostoreNP(oid, revid=revid2, data=pickle.dumps(obj))
+        # Now make sure all three revisions can be extracted
+        data = self._storage.loadSerial(oid, revid1)
+        pobj = pickle.loads(data)
+        eq(pobj.getoid(), oid)
+        eq(pobj.value, 1)
+        data = self._storage.loadSerial(oid, revid2)
+        pobj = pickle.loads(data)
+        eq(pobj.getoid(), oid)
+        eq(pobj.value, 2)
+        data = self._storage.loadSerial(oid, revid3)
+        pobj = pickle.loads(data)
+        eq(pobj.getoid(), oid)
+        eq(pobj.value, 3)
+        # Now pack just revisions 1 and 2.  The object's current revision
+        # should stay alive because it's pointed to by the root.
+        time.sleep(1)
+        self._storage.pack(time.time())
+        # Make sure the revisions are gone, but that object zero and revision
+        # 3 are still there and correct
+        data, revid = self._storage.load(ZERO, '')
+        eq(revid, revid0)
+        eq(loads(data).value, 0)
+        raises(KeyError, self._storage.loadSerial, oid, revid1)
+        raises(KeyError, self._storage.loadSerial, oid, revid2)
+        data = self._storage.loadSerial(oid, revid3)
+        pobj = pickle.loads(data)
+        eq(pobj.getoid(), oid)
+        eq(pobj.value, 3)
+        data, revid = self._storage.load(oid, '')
+        eq(revid, revid3)
+        pobj = pickle.loads(data)
+        eq(pobj.getoid(), oid)
+        eq(pobj.value, 3)
+
+    def checkPackOnlyOneObject(self):
+        eq = self.assertEqual
+        raises = self.assertRaises
+        loads = self._makeloader()
+        # Create a root object.  This can't be an instance of Object,
+        # otherwise the pickling machinery will serialize it as a persistent
+        # id and not as an object that contains references (persistent ids) to
+        # other objects.
+        root = Root()
+        # Create a persistent object, with some initial state
+        obj1 = self._newobj()
+        oid1 = obj1.getoid()
+        # Create another persistent object, with some initial state.  Make
+        # sure its oid is greater than the first object's oid.
+        obj2 = self._newobj()
+        oid2 = obj2.getoid()
+        self.failUnless(oid2 > oid1)
+        # Link the root object to the persistent objects, in order to keep
+        # them alive.  Store the root object.
+        root.obj1 = obj1
+        root.obj2 = obj2
+        root.value = 0
+        revid0 = self._dostoreNP(ZERO, data=dumps(root))
+        # Make sure the root can be retrieved
+        data, revid = self._storage.load(ZERO, '')
+        eq(revid, revid0)
+        eq(loads(data).value, 0)
+        # Commit three different revisions of the first object
+        obj1.value = 1
+        revid1 = self._dostoreNP(oid1, data=pickle.dumps(obj1))
+        obj1.value = 2
+        revid2 = self._dostoreNP(oid1, revid=revid1, data=pickle.dumps(obj1))
+        obj1.value = 3
+        revid3 = self._dostoreNP(oid1, revid=revid2, data=pickle.dumps(obj1))
+        # Now make sure all three revisions can be extracted
+        data = self._storage.loadSerial(oid1, revid1)
+        pobj = pickle.loads(data)
+        eq(pobj.getoid(), oid1)
+        eq(pobj.value, 1)
+        data = self._storage.loadSerial(oid1, revid2)
+        pobj = pickle.loads(data)
+        eq(pobj.getoid(), oid1)
+        eq(pobj.value, 2)
+        data = self._storage.loadSerial(oid1, revid3)
+        pobj = pickle.loads(data)
+        eq(pobj.getoid(), oid1)
+        eq(pobj.value, 3)
+        # Now commit a revision of the second object
+        obj2.value = 11
+        revid4 = self._dostoreNP(oid2, data=pickle.dumps(obj2))
+        # And make sure the revision can be extracted
+        data = self._storage.loadSerial(oid2, revid4)
+        pobj = pickle.loads(data)
+        eq(pobj.getoid(), oid2)
+        eq(pobj.value, 11)
+        # Now pack just revisions 1 and 2 of object1.  Object1's current
+        # revision should stay alive because it's pointed to by the root, as
+        # should Object2's current revision.
+        time.sleep(1)
+        self._storage.pack(time.time())
+        # Make sure the revisions are gone, but that object zero, object2, and
+        # revision 3 of object1 are still there and correct.
+        data, revid = self._storage.load(ZERO, '')
+        eq(revid, revid0)
+        eq(loads(data).value, 0)
+        raises(KeyError, self._storage.loadSerial, oid1, revid1)
+        raises(KeyError, self._storage.loadSerial, oid1, revid2)
+        data = self._storage.loadSerial(oid1, revid3)
+        pobj = pickle.loads(data)
+        eq(pobj.getoid(), oid1)
+        eq(pobj.value, 3)
+        data, revid = self._storage.load(oid1, '')
+        eq(revid, revid3)
+        pobj = pickle.loads(data)
+        eq(pobj.getoid(), oid1)
+        eq(pobj.value, 3)
+        data, revid = self._storage.load(oid2, '')
+        eq(revid, revid4)
+        eq(loads(data).value, 11)
+        data = self._storage.loadSerial(oid2, revid4)
+        pobj = pickle.loads(data)
+        eq(pobj.getoid(), oid2)
+        eq(pobj.value, 11)
+
+    def checkPackUnlinkedFromRoot(self):
+        eq = self.assertEqual
+        db = DB(self._storage)
+        conn = db.open()
+        root = conn.root()
+
+        txn = get_transaction()
+        txn.note('root')
+        txn.commit()
+
+        now = packtime = time.time()
+        while packtime <= now:
+            packtime = time.time()
+
+        obj = MinPO(7)
+
+        root['obj'] = obj
+        txn = get_transaction()
+        txn.note('root -> o1')
+        txn.commit()
+
+        del root['obj']
+        txn = get_transaction()
+        txn.note('root -x-> o1')
+        txn.commit()
+
+        self._storage.pack(packtime)
+
+        log = self._storage.undoLog()
+        tid = log[0]['id']
+        db.undo(tid)
+        txn = get_transaction()
+        txn.note('undo root -x-> o1')
+        txn.commit()
+
+        conn.sync()
+
+        eq(root['obj'].value, 7)


=== Zope3/src/zodb/storage/tests/persistent.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:53 2002
+++ Zope3/src/zodb/storage/tests/persistent.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,54 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Test that a storage's values persist across open and close."""
+
+class PersistentStorage:
+
+    def checkUpdatesPersist(self):
+        oids = []
+
+        def new_oid_wrapper(l=oids, new_oid=self._storage.new_oid):
+            oid = new_oid()
+            l.append(oid)
+            return oid
+
+        self._storage.new_oid = new_oid_wrapper
+
+        self._dostore()
+        oid = self._storage.new_oid()
+        revid = self._dostore(oid)
+        self._dostore(oid, revid, data=8, version='b')
+        oid = self._storage.new_oid()
+        revid = self._dostore(oid, data=1)
+        revid = self._dostore(oid, revid, data=2)
+        self._dostore(oid, revid, data=3)
+
+        # keep copies of all the objects
+        objects = []
+        for oid in oids:
+            p, s = self._storage.load(oid, '')
+            objects.append((oid, '', p, s))
+            ver = self._storage.modifiedInVersion(oid)
+            if ver:
+                p, s = self._storage.load(oid, ver)
+                objects.append((oid, ver, p, s))
+
+        self._storage.close()
+        self.open()
+
+        # keep copies of all the objects
+        for oid, ver, p, s in objects:
+            _p, _s = self._storage.load(oid, ver)
+            self.assertEquals(p, _p)
+            self.assertEquals(s, _s)


=== Zope3/src/zodb/storage/tests/readonly.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:53 2002
+++ Zope3/src/zodb/storage/tests/readonly.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,58 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+from zodb.interfaces import ReadOnlyError
+from zodb.ztransaction 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()
+        t = Transaction()
+        self.assertRaises(ReadOnlyError, self._storage.new_oid)
+        self.assertRaises(ReadOnlyError, self._storage.tpc_begin, t)
+
+        self.assertRaises(ReadOnlyError, self._storage.abortVersion,
+                          '', t)
+        self.assertRaises(ReadOnlyError, self._storage.commitVersion,
+                          '', '', t)
+        self.assertRaises(ReadOnlyError, self._storage.store,
+                          '\000' * 8, None, '', '', t)
+
+        if self._storage.supportsTransactionalUndo():
+            self.assertRaises(ReadOnlyError, self._storage.transactionalUndo,
+                              '\000' * 8, t)


=== Zope3/src/zodb/storage/tests/recovery.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:53 2002
+++ Zope3/src/zodb/storage/tests/recovery.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,158 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""More recovery and iterator tests.
+
+$Id$
+"""
+
+from zodb.ztransaction import Transaction
+from zodb.storage.tests.iterator import IteratorDeepCompare
+from zodb.storage.tests.base import MinPO, zodb_unpickle
+from zodb.db import DB
+
+from transaction import get_transaction
+
+import time
+
+class RecoveryStorage(IteratorDeepCompare):
+    # Requires a setUp() that creates a self._dst destination storage
+    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, t=trans: t[i], 1)
+            self.assertEqual(data.oid, oid)
+            self.assertEqual(data.data, None)
+
+    def checkRecoverUndoInVersion(self):
+        oid = self._storage.new_oid()
+        version = "aVersion"
+        revid_a = self._dostore(oid, data=MinPO(91))
+        revid_b = self._dostore(oid, revid=revid_a, version=version,
+                                data=MinPO(92))
+        revid_c = self._dostore(oid, revid=revid_b, version=version,
+                                data=MinPO(93))
+        self._undo(self._storage.undoInfo()[0]['id'], oid)
+        self._commitVersion(version, '')
+        self._undo(self._storage.undoInfo()[0]['id'], oid)
+
+        # now copy the records to a new storage
+        self._dst.copyTransactionsFrom(self._storage)
+        self.compare(self._storage, self._dst)
+
+        # The last two transactions were applied directly rather than
+        # copied.  So we can't use compare() to verify that they new
+        # transactions are applied correctly.  (The new transactions
+        # will have different timestamps for each storage.)
+
+        self._abortVersion(version)
+        self.assert_(self._storage.versionEmpty(version))
+        self._undo(self._storage.undoInfo()[0]['id'], oid)
+        self.assert_(not self._storage.versionEmpty(version))
+
+        # check the data is what we expect it to be
+        data, revid = self._storage.load(oid, version)
+        self.assertEqual(zodb_unpickle(data), MinPO(92))
+        data, revid = self._storage.load(oid, '')
+        self.assertEqual(zodb_unpickle(data), MinPO(91))
+
+        # and swap the storages
+        tmp = self._storage
+        self._storage = self._dst
+        self._abortVersion(version)
+        self.assert_(self._storage.versionEmpty(version))
+        self._undo(self._storage.undoInfo()[0]['id'], oid)
+        self.assert_(not self._storage.versionEmpty(version))
+
+        # check the data is what we expect it to be
+        data, revid = self._storage.load(oid, version)
+        self.assertEqual(zodb_unpickle(data), MinPO(92))
+        data, revid = self._storage.load(oid, '')
+        self.assertEqual(zodb_unpickle(data), MinPO(91))
+
+        # swap them back
+        self._storage = tmp
+
+        # Now remove _dst and copy all the transactions a second time.
+        # This time we will be able to confirm via compare().
+        self._dst.close()
+        self._dst.cleanup()
+        self._dst = self.new_dest()
+        self._dst.copyTransactionsFrom(self._storage)
+        self.compare(self._storage, self._dst)
+
+    def checkRestoreAcrossPack(self):
+        db = DB(self._storage)
+        c = db.open()
+        r = c.root()
+        obj = r["obj1"] = MinPO(1)
+        get_transaction().commit()
+        obj = r["obj2"] = MinPO(1)
+        get_transaction().commit()
+
+        self._dst.copyTransactionsFrom(self._storage)
+        self._dst.pack(time.time())
+
+        self._undo(self._storage.undoInfo()[0]['id'])
+
+        # copy the final transaction manually.  even though there
+        # was a pack, the restore() ought to succeed.
+        final = list(self._storage.iterator())[-1]
+        self._dst.tpc_begin(final, final.tid, final.status)
+        for r in final:
+            self._dst.restore(r.oid, r.serial, r.data, r.version, r.data_txn,
+                              final)
+        self._dst.tpc_vote(final)
+        self._dst.tpc_finish(final)


=== Zope3/src/zodb/storage/tests/revision.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:53 2002
+++ Zope3/src/zodb/storage/tests/revision.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,33 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Check loadSerial() on storages that support historical revisions."""
+
+from zodb.storage.tests.minpo import MinPO
+from zodb.storage.tests.base import zodb_unpickle, zodb_pickle
+
+ZERO = '\0'*8
+
+class RevisionStorage:
+
+    def checkLoadSerial(self):
+        oid = self._storage.new_oid()
+        revid = ZERO
+        revisions = {}
+        for i in range(31, 38):
+            revid = self._dostore(oid, revid=revid, data=MinPO(i))
+            revisions[revid] = MinPO(i)
+        # Now make sure all the revisions have the correct value
+        for revid, value in revisions.items():
+            data = self._storage.loadSerial(oid, revid)
+            self.assertEqual(zodb_unpickle(data), value)


=== Zope3/src/zodb/storage/tests/speed.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:54 2002
+++ Zope3/src/zodb/storage/tests/speed.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,122 @@
+##############################################################################
+#
+# 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
+
+Options:
+
+    -d file    The data file to use as input.
+               The default is this script.
+
+    -n n       The number of repititions
+
+    -s module  A module that defines a 'Storage'
+               attribute, which is an open storage.
+               If not specified, a FileStorage will ne
+               used.
+
+    -z         Test compressing data
+
+    -D         Run in debug mode
+
+    -L         Test loads as well as stores by minimizing
+               the cache after eachrun
+
+    -M         Output means only
+"""
+
+import sys, os, getopt, string, time
+sys.path.insert(0, os.getcwd())
+
+import zodb.db
+from zodb.storage.file import FileStorage
+import persistence
+from transaction import get_transaction
+
+class P(Persistence.Persistent): pass
+
+def main(args):
+
+    opts, args = getopt.getopt(args, 'zd:n:Ds:LM')
+    z=s=None
+    data=sys.argv[0]
+    nrep=5
+    minimize=0
+    detailed=1
+    for o, v in opts:
+        if o=='-n': nrep=string.atoi(v)
+        elif o=='-d': data=v
+        elif o=='-s': s=v
+        elif o=='-z':
+            global zlib
+            import zlib
+            z=compress
+        elif o=='-L':
+            minimize=1
+        elif o=='-M':
+            detailed=0
+        elif o=='-D':
+            global debug
+            os.environ['STUPID_LOG_FILE']=''
+            os.environ['STUPID_LOG_SEVERITY']='-999'
+
+    if s:
+        s=__import__(s, globals(), globals(), ('__doc__',))
+        s=s.Storage
+    else:
+        s=FileStorage('zeo_speed.fs', create=1)
+
+    data=open(data).read()
+    db=zodb.db.DB(s, cache_size=4000)
+    results={1:0, 10:0, 100:0, 1000:0}
+    for j in range(nrep):
+        for r in 1, 10, 100, 1000:
+            t=time.time()
+            jar=db.open()
+            get_transaction().begin()
+            rt=jar.root()
+            key='s%s' % r
+            if rt.has_key(key): p=rt[key]
+            else: rt[key]=p=P()
+            for i in range(r):
+                if z is not None: d=z(data)
+                else: d=data
+                v=getattr(p, str(i), P())
+                v.d=d
+                setattr(p,str(i),v)
+            get_transaction().commit()
+            jar.close()
+            t=time.time()-t
+            if detailed:
+                sys.stderr.write("%s\t%s\t%.4f\n" % (j, r, t))
+                sys.stdout.flush()
+            results[r]=results[r]+t
+            rt=d=p=v=None # release all references
+            if minimize:
+                time.sleep(3)
+                jar.cacheMinimize(3)
+
+    if detailed: print '-'*24
+    for r in 1, 10, 100, 1000:
+        t=results[r]/nrep
+        sys.stderr.write("mean:\t%s\t%.4f\t%.4f (s/o)\n" % (r, t, t/r))
+
+    db.close()
+
+
+def compress(s):
+    c=zlib.compressobj()
+    o=c.compress(s)
+    return o+c.flush()
+
+if __name__=='__main__': main(sys.argv[1:])


=== Zope3/src/zodb/storage/tests/synchronization.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:54 2002
+++ Zope3/src/zodb/storage/tests/synchronization.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,146 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Test the storage's implemenetation of the storage synchronization spec.
+
+The Synchronization spec
+    http://www.zope.org/Documentation/Developer/Models/ZODB/
+    ZODB_Architecture_Storage_Interface_State_Synchronization_Diag.html
+
+It specifies two states committing and non-committing.  A storage
+starts in the non-committing state.  tpc_begin() transfers to the
+committting state; tpc_abort() and tpc_finish() transfer back to
+non-committing.
+
+Several other methods are only allowed in one state or another.  Many
+methods allowed only in the committing state require that they apply
+to the currently committing transaction.
+
+The spec is silent on a variety of methods that don't appear to modify
+the state, e.g. load(), undoLog(), pack().  It's unclear whether there
+is a separate set of synchronization rules that apply to these methods
+or if the synchronization is implementation dependent, i.e. only what
+is need to guarantee a corrected implementation.
+
+The synchronization spec is also silent on whether there is any
+contract implied with the caller.  If the storage can assume that a
+single client is single-threaded and that it will not call, e.g., store()
+until after it calls tpc_begin(), the implementation can be
+substantially simplified.
+
+New and/or unspecified methods:
+
+tpc_vote(): handled like tpc_abort
+transactionalUndo(): handled like undo()  (which is how?)
+
+Methods that have nothing to do with committing/non-committing:
+load(), loadSerial(), getName(), getSize(), __len__(), history(),
+undoLog(), modifiedInVersion(), versionEmpty(), versions(), pack().
+
+Specific questions:
+
+The spec & docs say that undo() takes three arguments, the second
+being a transaction.  If the specified arg isn't the current
+transaction, the undo() should raise StorageTransactionError.  This
+isn't implemented anywhere.  It looks like undo can be called at
+anytime.
+
+FileStorage does not allow undo() during a pack.  How should this be
+tested?  Is it a general restriction?
+
+
+
+"""
+
+from zodb.ztransaction import Transaction
+from zodb.interfaces import StorageTransactionError
+
+VERSION = "testversion"
+OID = "\000" * 8
+SERIALNO = "\000" * 8
+TID = "\000" * 8
+
+class SynchronizedStorage:
+
+##    def verifyCommitting(self, callable, *args):
+##        self.assertRaises(StorageTransactionError, callable *args)
+
+    def verifyNotCommitting(self, callable, *args):
+        args = (StorageTransactionError, callable) + args
+        apply(self.assertRaises, args)
+
+    def verifyWrongTrans(self, callable, *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, Transaction())
+
+    def checkAbortVersionWrongTrans(self):
+        self.verifyWrongTrans(self._storage.abortVersion,
+                              VERSION, Transaction())
+
+    def checkCommitVersionNotCommitting(self):
+        self.verifyNotCommitting(self._storage.commitVersion,
+                                 VERSION, "", Transaction())
+
+    def checkCommitVersionWrongTrans(self):
+        self.verifyWrongTrans(self._storage.commitVersion,
+                              VERSION, "", Transaction())
+
+
+    def checkStoreNotCommitting(self):
+        self.verifyNotCommitting(self._storage.store,
+                                 OID, SERIALNO, "", "", Transaction())
+
+    def checkStoreWrongTrans(self):
+        self.verifyWrongTrans(self._storage.store,
+                              OID, SERIALNO, "", "", Transaction())
+
+##    def checkNewOidNotCommitting(self):
+##        self.verifyNotCommitting(self._storage.new_oid)
+
+##    def checkNewOidWrongTrans(self):
+##        self.verifyWrongTrans(self._storage.new_oid)
+
+
+    def checkAbortNotCommitting(self):
+        self._storage.tpc_abort(Transaction())
+
+    def checkAbortWrongTrans(self):
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        self._storage.tpc_abort(Transaction())
+        self._storage.tpc_abort(t)
+
+    def checkFinishNotCommitting(self):
+        t = Transaction()
+        self._storage.tpc_finish(t)
+        self._storage.tpc_abort(t)
+
+    def checkFinishWrongTrans(self):
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        self._storage.tpc_finish(Transaction())
+        self._storage.tpc_abort(t)
+
+    def checkBeginCommitting(self):
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        self._storage.tpc_begin(t)
+        self._storage.tpc_abort(t)
+
+    # XXX how to check undo?


=== Zope3/src/zodb/storage/tests/test_autopack.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:54 2002
+++ Zope3/src/zodb/storage/tests/test_autopack.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,270 @@
+##############################################################################
+#
+# Copyright (c) 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
+#
+##############################################################################
+
+try:
+    import bsddb3
+except ImportError:
+    raise RuntimeError, 'BerkeleyDB not available'
+
+import os
+import time
+import unittest
+
+from zodb.db import DB
+from zodb.storage.tests.minpo import MinPO
+from persistence import Persistent
+from transaction import get_transaction
+
+from zodb.storage.bdbfull import BDBFullStorage
+from zodb.storage.bdbminimal import BDBMinimalStorage
+from zodb.storage.base import BerkeleyConfig
+from zodb.storage.tests.base import BerkeleyTestBase
+
+ZERO = '\0'*8
+
+class C(Persistent):
+    pass
+
+
+
+class TestAutopackBase(BerkeleyTestBase):
+    def _config(self):
+        config = BerkeleyConfig()
+        # Autopack every 3 seconds, 6 seconds into the past, no classic packs
+        config.frequency = 3
+        config.packtime = 6
+        config.classicpack = 0
+        return config
+
+    def _wait_for_next_autopack(self):
+        storage = self._storage
+        # BAW: this uses a non-public interface
+        packtime = storage._autopacker._nextcheck
+        while packtime == storage._autopacker._nextcheck:
+            time.sleep(1)
+
+    def _mk_dbhome(self, dir):
+        # Create the storage
+        os.mkdir(dir)
+        try:
+            return self.ConcreteStorage(dir, config=self._config())
+        except:
+            self._zap_dbhome(dir)
+            raise
+
+
+class TestAutopack(TestAutopackBase):
+    ConcreteStorage = BDBFullStorage
+
+    def checkAutopack(self):
+        unless = self.failUnless
+        raises = self.assertRaises
+        storage = self._storage
+        # Wait for an autopack operation to occur, then make three revisions
+        # to an object.  Wait for the next autopack operation and make sure
+        # all three revisions still exist.  Then sleep 10 seconds and wait for
+        # another autopack operation.  Then verify that the first two
+        # revisions have been packed away.
+        oid = storage.new_oid()
+        self._wait_for_next_autopack()
+        revid1 = self._dostore(oid, data=MinPO(2112))
+        revid2 = self._dostore(oid, revid=revid1, data=MinPO(2113))
+        revid3 = self._dostore(oid, revid=revid2, data=MinPO(2114))
+        self._wait_for_next_autopack()
+        unless(storage.loadSerial(oid, revid1))
+        unless(storage.loadSerial(oid, revid2))
+        unless(storage.loadSerial(oid, revid3))
+        # Should be enough time for the revisions to get packed away
+        time.sleep(10)
+        self._wait_for_next_autopack()
+        # The first two revisions should now be gone, but the third should
+        # still exist because it's the current revision, and we haven't done a
+        # classic pack.
+        raises(KeyError, self._storage.loadSerial, oid, revid1)
+        raises(KeyError, self._storage.loadSerial, oid, revid2)
+        unless(storage.loadSerial(oid, revid3))
+
+
+
+class TestAutomaticClassicPack(TestAutopackBase):
+    ConcreteStorage = BDBFullStorage
+
+    def _config(self):
+        config = BerkeleyConfig()
+        # Autopack every 3 seconds, 6 seconds into the past, no classic packs
+        config.frequency = 3
+        config.packtime = 6
+        config.classicpack = 1
+        return config
+
+    def checkAutomaticClassicPack(self):
+        unless = self.failUnless
+        raises = self.assertRaises
+        storage = self._storage
+        # Wait for an autopack operation to occur, then make three revisions
+        # to an object.  Wait for the next autopack operation and make sure
+        # all three revisions still exist.  Then sleep 10 seconds and wait for
+        # another autopack operation.  Then verify that the first two
+        # revisions have been packed away.
+        oid = storage.new_oid()
+        self._wait_for_next_autopack()
+        revid1 = self._dostore(oid, data=MinPO(2112))
+        revid2 = self._dostore(oid, revid=revid1, data=MinPO(2113))
+        revid3 = self._dostore(oid, revid=revid2, data=MinPO(2114))
+        self._wait_for_next_autopack()
+        unless(storage.loadSerial(oid, revid1))
+        unless(storage.loadSerial(oid, revid2))
+        unless(storage.loadSerial(oid, revid3))
+        # Should be enough time for the revisions to get packed away
+        time.sleep(10)
+        self._wait_for_next_autopack()
+        # The first two revisions should now be gone, but the third should
+        # still exist because it's the current revision, and we haven't done a
+        # classic pack.
+        raises(KeyError, storage.loadSerial, oid, revid1)
+        raises(KeyError, storage.loadSerial, oid, revid2)
+        raises(KeyError, storage.loadSerial, oid, revid3)
+
+    def checkCycleUnreachable(self):
+        unless = self.failUnless
+        raises = self.assertRaises
+        storage = self._storage
+        db = DB(storage)
+        conn = db.open()
+        root = conn.root()
+        self._wait_for_next_autopack()
+        # Store an object that's reachable from the root
+        obj1 = C()
+        obj2 = C()
+        obj1.obj = obj2
+        obj2.obj = obj1
+        root.obj = obj1
+        txn = get_transaction()
+        txn.note('root -> obj1 <-> obj2')
+        txn.commit()
+        oid1 = obj1._p_oid
+        oid2 = obj2._p_oid
+        assert oid1 and oid2 and oid1 <> oid2
+        self._wait_for_next_autopack()
+        unless(storage.load(ZERO, ''))
+        unless(storage.load(oid1, ''))
+        unless(storage.load(oid2, ''))
+        # Now unlink it, which should still leave obj1 and obj2 alive
+        del root.obj
+        txn = get_transaction()
+        txn.note('root -X-> obj1 <-> obj2')
+        txn.commit()
+        unless(storage.load(ZERO, ''))
+        unless(storage.load(oid1, ''))
+        unless(storage.load(oid2, ''))
+        # Do an explicit full pack to right now to collect all the old
+        # revisions and the cycle.
+        storage.pack(time.time())
+        # And it should be packed away
+        unless(storage.load(ZERO, ''))
+        raises(KeyError, storage.load, oid1, '')
+        raises(KeyError, storage.load, oid2, '')
+
+
+
+class TestMinimalPack(TestAutopackBase):
+    ConcreteStorage = BDBMinimalStorage
+
+    def _config(self):
+        config = BerkeleyConfig()
+        # Autopack every 3 seconds
+        config.frequency = 3
+        return config
+
+    def checkRootUnreachable(self):
+        unless = self.failUnless
+        raises = self.assertRaises
+        storage = self._storage
+        db = DB(storage)
+        conn = db.open()
+        root = conn.root()
+        self._wait_for_next_autopack()
+        # Store an object that's reachable from the root
+        obj = C()
+        obj.value = 999
+        root.obj = obj
+        txn = get_transaction()
+        txn.note('root -> obj')
+        txn.commit()
+        oid = obj._p_oid
+        assert oid
+        self._wait_for_next_autopack()
+        unless(storage.load(ZERO, ''))
+        unless(storage.load(oid, ''))
+        # Now unlink it
+        del root.obj
+        txn = get_transaction()
+        txn.note('root -X-> obj')
+        txn.commit()
+        # The object should be gone due to reference counting
+        unless(storage.load(ZERO, ''))
+        raises(KeyError, storage.load, oid, '')
+
+    def checkCycleUnreachable(self):
+        unless = self.failUnless
+        raises = self.assertRaises
+        storage = self._storage
+        db = DB(storage)
+        conn = db.open()
+        root = conn.root()
+        self._wait_for_next_autopack()
+        # Store an object that's reachable from the root
+        obj1 = C()
+        obj2 = C()
+        obj1.obj = obj2
+        obj2.obj = obj1
+        root.obj = obj1
+        txn = get_transaction()
+        txn.note('root -> obj1 <-> obj2')
+        txn.commit()
+        oid1 = obj1._p_oid
+        oid2 = obj2._p_oid
+        assert oid1 and oid2 and oid1 <> oid2
+        self._wait_for_next_autopack()
+        unless(storage.load(ZERO, ''))
+        unless(storage.load(oid1, ''))
+        unless(storage.load(oid2, ''))
+        # Now unlink it, which should still leave obj1 and obj2 alive
+        del root.obj
+        txn = get_transaction()
+        txn.note('root -X-> obj1 <-> obj2')
+        txn.commit()
+        unless(storage.load(ZERO, ''))
+        unless(storage.load(oid1, ''))
+        unless(storage.load(oid2, ''))
+        # But the next autopack should collect both obj1 and obj2
+        self._wait_for_next_autopack()
+        # And it should be packed away
+        unless(storage.load(ZERO, ''))
+        raises(KeyError, storage.load, oid1, '')
+        raises(KeyError, storage.load, oid2, '')
+
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(TestAutopack, 'check'))
+    suite.addTest(unittest.makeSuite(TestAutomaticClassicPack, 'check'))
+    suite.addTest(unittest.makeSuite(TestMinimalPack, 'check'))
+    return suite
+
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')


=== Zope3/src/zodb/storage/tests/test_config.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:54 2002
+++ Zope3/src/zodb/storage/tests/test_config.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,170 @@
+import os
+import shutil
+import tempfile
+import unittest
+from StringIO import StringIO
+
+import ZConfig
+
+from zodb import config
+
+class StorageTestCase(unittest.TestCase):
+
+    def setUp(self):
+        unittest.TestCase.setUp(self)
+        self.tmpfn = tempfile.mktemp()
+        self.storage = None
+
+    def tearDown(self):
+        unittest.TestCase.tearDown(self)
+        storage = self.storage
+        self.storage = None
+        try:
+            if storage is not None:
+                storage.close()
+        except:
+            pass
+        try:
+            # Full storage creates a directory
+            if os.path.isdir(self.tmpfn):
+                shutil.rmtree(self.tmpfn)
+            else:
+                os.remove(self.tmpfn)
+        except os.error:
+            pass
+
+    def testFileStorage(self):
+        from zodb.storage.file import FileStorage
+        sample = """
+        <Storage>
+        type       FileStorage
+        file_name  %s
+        create     yes
+        </Storage>
+        """ % self.tmpfn
+        io = StringIO(sample)
+        rootconf = ZConfig.loadfile(io)
+        storageconf = rootconf.getSection("Storage")
+        cls, args = config.getStorageInfo(storageconf)
+        self.assertEqual(cls, FileStorage)
+        self.assertEqual(args, {"file_name": self.tmpfn, "create": 1})
+        self.storage = config.createStorage(storageconf)
+        self.assert_(isinstance(self.storage, FileStorage))
+
+    def testZEOStorage(self):
+        try:
+            from zodb.zeo.client import ClientStorage
+        except ImportError:
+            return
+        sample = """
+        <Storage>
+        type       ClientStorage
+        addr       zeo://www.python.org:9001
+        wait       no
+        </Storage>
+        """
+        io = StringIO(sample)
+        rootconf = ZConfig.loadfile(io)
+        storageconf = rootconf.getSection("Storage")
+        cls, args = config.getStorageInfo(storageconf)
+        self.assertEqual(cls, ClientStorage)
+        self.assertEqual(args, {"addr": [("www.python.org", 9001)], "wait": 0})
+        self.storage = config.createStorage(storageconf)
+        self.assert_(isinstance(self.storage, ClientStorage))
+
+    def testMappingStorage(self):
+        from zodb.storage.mapping import MappingStorage
+        sample = """
+        <Storage>
+        type       MappingStorage
+        </Storage>
+        """
+        io = StringIO(sample)
+        rootconf = ZConfig.loadfile(io)
+        storageconf = rootconf.getSection("Storage")
+        cls, args = config.getStorageInfo(storageconf)
+        self.assertEqual(cls, MappingStorage)
+        self.assertEqual(args, {})
+        self.storage = config.createStorage(storageconf)
+        self.assert_(isinstance(self.storage, MappingStorage))
+
+    def testModuleStorage(self):
+        # Test explicit module+class
+        from zodb.storage.mapping import MappingStorage
+        sample = """
+        <Storage>
+        type       zodb.storage.mapping.MappingStorage
+        </Storage>
+        """
+        io = StringIO(sample)
+        rootconf = ZConfig.loadfile(io)
+        storageconf = rootconf.getSection("Storage")
+        cls, args = config.getStorageInfo(storageconf)
+        self.assertEqual(cls, MappingStorage)
+        self.assertEqual(args, {})
+        self.storage = config.createStorage(storageconf)
+        self.assert_(isinstance(self.storage, MappingStorage))
+
+    def testFullStorage(self):
+        try:
+            from zodb.storage.bdbfull import BDBFullStorage
+        except ImportError:
+##            import warnings
+##            warnings.warn('No BDBStorage available', RuntimeWarning)
+            return
+        sample = """
+        <Storage>
+        type       BDBFullStorage
+        name       %s
+        cachesize  1000
+        </Storage>
+        """ % self.tmpfn
+        os.mkdir(self.tmpfn)
+        io = StringIO(sample)
+        rootconf = ZConfig.loadfile(io)
+        storageconf = rootconf.getSection("Storage")
+        cls, args = config.getStorageInfo(storageconf)
+        self.assertEqual(cls, BDBFullStorage)
+        # It's too hard to test the config instance equality
+        args = args.copy()
+        del args['config']
+        self.assertEqual(args, {"name": self.tmpfn})
+        self.storage = config.createStorage(storageconf)
+        self.assert_(isinstance(self.storage, BDBFullStorage))
+        # XXX _config isn't public
+        self.assert_(self.storage._config.cachesize, 1000)
+
+    def testMinimalStorage(self):
+        try:
+            from zodb.storage.bdbminimal import BDBMinimalStorage
+        except ImportError:
+##            import warnings
+##            warnings.warn('No BDBStorage available', RuntimeWarning)
+            return
+        sample = """
+        <Storage>
+        type       BDBMinimalStorage
+        name       %s
+        cachesize  1000
+        </Storage>
+        """ % self.tmpfn
+        os.mkdir(self.tmpfn)
+        io = StringIO(sample)
+        rootconf = ZConfig.loadfile(io)
+        storageconf = rootconf.getSection("Storage")
+        cls, args = config.getStorageInfo(storageconf)
+        self.assertEqual(cls, BDBMinimalStorage)
+        # It's too hard to test the config instance equality
+        args = args.copy()
+        del args['config']
+        self.assertEqual(args, {"name": self.tmpfn})
+        self.storage = config.createStorage(storageconf)
+        self.assert_(isinstance(self.storage, BDBMinimalStorage))
+        # XXX _config isn't public
+        self.assert_(self.storage._config.cachesize, 1000)
+
+def test_suite():
+    return unittest.makeSuite(StorageTestCase)
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')


=== Zope3/src/zodb/storage/tests/test_create.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:54 2002
+++ Zope3/src/zodb/storage/tests/test_create.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,125 @@
+##############################################################################
+#
+# 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
+#
+##############################################################################
+
+try:
+    import bsddb3
+except ImportError:
+    raise RuntimeError, 'BerkeleyDB not available'
+
+# Unit test for database creation
+
+import os
+import time
+import unittest
+
+from zodb.storage.base import BerkeleyConfig
+from zodb.storage.tests import bdbmixin
+from zodb.storage.tests.base import DBHOME
+from zodb.storage.bdbfull import BDBFullStorage
+
+
+
+class TestMixin:
+    def checkDBHomeExists(self):
+        self.failUnless(os.path.isdir(DBHOME))
+
+
+class MinimalCreateTest(bdbmixin.MinimalTestBase, TestMixin):
+    pass
+
+
+class FullCreateTest(bdbmixin.FullTestBase, TestMixin):
+    pass
+
+
+
+class FullOpenExistingTest(bdbmixin.FullTestBase):
+    def checkOpenWithExistingVersions(self):
+        version = 'test-version'
+        oid = self._storage.new_oid()
+        revid = self._dostore(oid, data=7, version=version)
+        # Now close the current storage and re-open it
+        self._storage.close()
+        self._storage = self.ConcreteStorage(DBHOME)
+        self.assertEqual(self._storage.modifiedInVersion(oid), version)
+
+    def checkOpenAddVersion(self):
+        eq = self.assertEqual
+        version1 = 'test-version'
+        oid1 = self._storage.new_oid()
+        revid = self._dostore(oid1, data=7, version=version1)
+        # Now close the current storage and re-open it
+        self._storage.close()
+        self._storage = self.ConcreteStorage(DBHOME)
+        eq(self._storage.modifiedInVersion(oid1), version1)
+        # Now create a 2nd version string, then close/reopen
+        version2 = 'new-version'
+        oid2 = self._storage.new_oid()
+        revid = self._dostore(oid2, data=8, version=version2)
+        # Now close the current storage and re-open it
+        self._storage.close()
+        self._storage = self.ConcreteStorage(DBHOME)
+        eq(self._storage.modifiedInVersion(oid1), version1)
+        # Now create a 2nd version string, then close/reopen
+        eq(self._storage.modifiedInVersion(oid2), version2)
+
+
+
+class FullOpenCloseTest(bdbmixin.FullTestBase):
+    def _mk_dbhome(self, dir):
+        config = BerkeleyConfig
+        config.interval = 10
+        os.mkdir(dir)
+        try:
+            return self.ConcreteStorage(dir, config=config)
+        except:
+            self._zap_dbhome(dir)
+            raise
+
+    def checkCloseWithCheckpointingThread(self):
+        # All the interesting stuff happens in the setUp and tearDown
+        time.sleep(20)
+
+
+
+class OpenRecoveryTest(bdbmixin.FullTestBase):
+    def _mk_dbhome(self, dir):
+        self._dir = dir
+
+    def checkOpenWithBogusConfig(self):
+        class C: pass
+        c = C()
+        # This instance won't have the necessary attributes, so the creation
+        # will fail.  We want to be sure that everything gets cleaned up
+        # enough to fix that and create a proper storage.
+        self.assertRaises(AttributeError, BDBFullStorage, self._dir, config=c)
+        c = BerkeleyConfig()
+        s = BDBFullStorage(self._dir, config=c)
+        s.close()
+
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(MinimalCreateTest, 'check'))
+    suite.addTest(unittest.makeSuite(FullCreateTest, 'check'))
+    suite.addTest(unittest.makeSuite(FullOpenExistingTest, 'check'))
+    suite.addTest(unittest.makeSuite(FullOpenCloseTest, 'check'))
+    suite.addTest(unittest.makeSuite(OpenRecoveryTest, 'check'))
+    return suite
+
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')


=== Zope3/src/zodb/storage/tests/test_file.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:54 2002
+++ Zope3/src/zodb/storage/tests/test_file.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,106 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+import zodb.storage.file
+import sys, os, unittest
+import errno
+from zodb.ztransaction import Transaction
+from zodb import interfaces
+
+from zodb.storage.tests import base, basic, \
+     undo, version, \
+     undoversion, packable, \
+     synchronization, conflict, history, \
+     iterator, corruption, revision, persistent, \
+     mt, readonly, recovery
+from zodb.storage.tests.base import MinPO, zodb_unpickle
+
+class FileStorageTests(
+    base.StorageTestBase,
+    basic.BasicStorage,
+    undo.TransactionalUndoStorage,
+    revision.RevisionStorage,
+    version.VersionStorage,
+    undoversion.TransactionalUndoVersionStorage,
+    packable.PackableStorage,
+    synchronization.SynchronizedStorage,
+    conflict.ConflictResolvingStorage,
+    conflict.ConflictResolvingTransUndoStorage,
+    history.HistoryStorage,
+    iterator.IteratorStorage,
+    iterator.ExtendedIteratorStorage,
+    persistent.PersistentStorage,
+    mt.MTStorage,
+    readonly.ReadOnlyStorage
+    ):
+
+    def open(self, **kwargs):
+        self._storage = zodb.storage.file.FileStorage('FileStorageTests.fs',
+                                                      **kwargs)
+
+    def setUp(self):
+        base.removefs("FileStorageTests.fs")
+        self.open(create=1)
+
+    def tearDown(self):
+        self._storage.close()
+        base.removefs("FileStorageTests.fs")
+
+    def checkLongMetadata(self):
+        s = "X" * 75000
+        try:
+            self._dostore(user=s)
+        except interfaces.StorageError:
+            pass
+        else:
+            self.fail("expect long user field to raise error")
+        try:
+            self._dostore(description=s)
+        except interfaces.StorageError:
+            pass
+        else:
+            self.fail("expect long user field to raise error")
+
+class FileStorageRecoveryTest(
+    base.StorageTestBase,
+    recovery.RecoveryStorage,
+    ):
+
+    def setUp(self):
+        base.removefs("Source.fs")
+        base.removefs("Dest.fs")
+        self._storage = zodb.storage.file.FileStorage('Source.fs')
+        self._dst = zodb.storage.file.FileStorage('Dest.fs')
+
+    def tearDown(self):
+        self._storage.close()
+        self._dst.close()
+        base.removefs("Source.fs")
+        base.removefs("Dest.fs")
+
+    def new_dest(self):
+        base.removefs('Dest.fs')
+        return zodb.storage.file.FileStorage('Dest.fs')
+
+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():
+    alltests=test_suite()
+    runner = unittest.TextTestRunner()
+    runner.run(alltests)


=== Zope3/src/zodb/storage/tests/test_fsindex.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:54 2002
+++ Zope3/src/zodb/storage/tests/test_fsindex.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,73 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+
+import unittest, sys
+from zodb.storage.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/src/zodb/storage/tests/test_mapping.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:55 2002
+++ Zope3/src/zodb/storage/tests/test_mapping.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,37 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+import zodb.storage.mapping
+import os, unittest
+
+from zodb.storage.tests import base, basic, synchronization
+
+class MappingStorageTests(base.StorageTestBase,
+                          basic.BasicStorage,
+                          synchronization.SynchronizedStorage,
+                          ):
+
+    def setUp(self):
+        self._storage = zodb.storage.mapping.MappingStorage()
+
+    def tearDown(self):
+        self._storage.close()
+
+def test_suite():
+    suite = unittest.makeSuite(MappingStorageTests, 'check')
+    return suite
+
+if __name__ == "__main__":
+    loader = unittest.TestLoader()
+    loader.testMethodPrefix = "check"
+    unittest.main(testLoader=loader)


=== Zope3/src/zodb/storage/tests/test_storage_api.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:55 2002
+++ Zope3/src/zodb/storage/tests/test_storage_api.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,92 @@
+##############################################################################
+#
+# 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
+#
+##############################################################################
+
+try:
+    import bsddb3
+except ImportError:
+    raise RuntimeError, 'BerkeleyDB not available'
+
+# Unit tests for basic storage functionality
+
+import unittest
+from zodb import interfaces
+
+from zodb.storage.tests import bdbmixin
+from zodb.storage.tests.basic import BasicStorage
+from zodb.storage.tests.revision import RevisionStorage
+from zodb.storage.tests.version import VersionStorage
+from zodb.storage.tests.undo import TransactionalUndoStorage
+from zodb.storage.tests.undoversion import \
+     TransactionalUndoVersionStorage
+from zodb.storage.tests.packable import PackableStorage
+from zodb.storage.tests.history import HistoryStorage
+from zodb.storage.tests.iterator import \
+     IteratorStorage, ExtendedIteratorStorage
+from zodb.storage.tests.recovery import RecoveryStorage
+from zodb.storage.tests import conflict
+
+
+
+class MinimalTest(bdbmixin.MinimalTestBase, BasicStorage):
+    def checkVersionedStoreAndLoad(self):
+        # This storage doesn't support versions, so we should get an exception
+        oid = self._storage.new_oid()
+        self.assertRaises(interfaces.Unsupported,
+                          self._dostore,
+                          oid, data=11, version='a version')
+
+
+class FullTest(bdbmixin.FullTestBase, BasicStorage,
+               RevisionStorage, VersionStorage,
+               TransactionalUndoStorage,
+               TransactionalUndoVersionStorage,
+               PackableStorage,
+               HistoryStorage,
+               IteratorStorage, ExtendedIteratorStorage,
+               conflict.ConflictResolvingStorage,
+               conflict.ConflictResolvingTransUndoStorage):
+    pass
+
+
+
+DST_DBHOME = 'test-dst'
+
+class FullRecoveryTest(bdbmixin.FullTestBase,
+                       RecoveryStorage):
+    def setUp(self):
+        bdbmixin.FullTestBase.setUp(self)
+        self._zap_dbhome(DST_DBHOME)
+        self._dst = self._mk_dbhome(DST_DBHOME)
+
+    def tearDown(self):
+        bdbmixin.FullTestBase.tearDown(self)
+        self._zap_dbhome(DST_DBHOME)
+
+    def new_dest(self):
+        self._zap_dbhome(DST_DBHOME)
+        return self._mk_dbhome(DST_DBHOME)
+
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(FullTest, 'check'))
+    suite.addTest(unittest.makeSuite(FullRecoveryTest, 'check'))
+    suite.addTest(unittest.makeSuite(MinimalTest, 'check'))
+    return suite
+
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')


=== Zope3/src/zodb/storage/tests/test_virgin.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:55 2002
+++ Zope3/src/zodb/storage/tests/test_virgin.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,62 @@
+##############################################################################
+#
+# 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
+#
+##############################################################################
+
+# Test creation of a brand new database, and insertion of root objects.
+
+try:
+    import bsddb3
+except ImportError:
+    raise RuntimeError, 'BerkeleyDB not available'
+
+import unittest
+
+from zodb.storage.tests.base import ZODBTestBase
+from transaction import get_transaction
+from persistence.dict import PersistentDict
+
+
+
+class InsertMixin:
+    def checkIsEmpty(self):
+        self.failUnless(not self._root.has_key('names'))
+
+    def checkNewInserts(self):
+        self._root['names'] = names = PersistentDict()
+        names['Warsaw'] = 'Barry'
+        names['Hylton'] = 'Jeremy'
+        get_transaction().commit()
+
+
+
+class FullNewInsertsTest(ZODBTestBase, InsertMixin):
+    from zodb.storage.bdbfull import BDBFullStorage
+    ConcreteStorage = BDBFullStorage
+
+
+class MinimalNewInsertsTest(ZODBTestBase, InsertMixin):
+    from zodb.storage.bdbminimal import BDBMinimalStorage
+    ConcreteStorage = BDBMinimalStorage
+
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(MinimalNewInsertsTest, 'check'))
+    suite.addTest(unittest.makeSuite(FullNewInsertsTest, 'check'))
+    return suite
+
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')


=== Zope3/src/zodb/storage/tests/test_whitebox.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:55 2002
+++ Zope3/src/zodb/storage/tests/test_whitebox.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,234 @@
+##############################################################################
+#
+# 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
+#
+##############################################################################
+
+# Whitebox testing of storage implementation details.
+
+try:
+    import bsddb3
+except ImportError:
+    raise RuntimeError, 'BerkeleyDB not available'
+
+import unittest
+
+from zodb.storage.tests.minpo import MinPO
+from zodb.storage.tests.base import zodb_unpickle
+from zodb.storage.bdbfull import BDBFullStorage
+from zodb.storage.bdbminimal import BDBMinimalStorage
+from zodb.storage.tests.base import BerkeleyTestBase
+from zodb.storage.tests.base import ZODBTestBase
+
+from persistence import Persistent
+from transaction import get_transaction
+
+ZERO = '\0'*8
+
+
+
+class Object(Persistent):
+    pass
+
+
+
+class WhiteboxLowLevelMinimal(BerkeleyTestBase):
+    ConcreteStorage = BDBMinimalStorage
+
+    def checkTableConsistencyAfterCommit(self):
+        unless = self.failIf
+        eq = self.assertEqual
+        oid = self._storage.new_oid()
+        revid1 = self._dostore(oid, data=11)
+        revid2 = self._dostore(oid, revid=revid1, data=12)
+        revid3 = self._dostore(oid, revid=revid2, data=13)
+        # First off, there should be no entries in the pending table
+        unless(self._storage._pending.keys())
+        # Also, there should be no entries in the oids table
+        unless(self._storage._oids.keys())
+        # Now, there should be exactly one oid in the serials table, and
+        # exactly one record for that oid in the table too.
+        oids = {}
+        c = self._storage._serials.cursor()
+        try:
+            rec = c.first()
+            while rec:
+                oid, serial = rec
+                oids.setdefault(oid, []).append(serial)
+                rec = c.next()
+        finally:
+            c.close()
+        eq(len(oids), 1)
+        eq(len(oids[oids.keys()[0]]), 1)
+        # There should now be exactly one entry in the pickles table.
+        pickles = self._storage._pickles.items()
+        eq(len(pickles), 1)
+        key, data = pickles[0]
+        poid = key[:8]
+        pserial = key[8:]
+        eq(oid, poid)
+        eq(revid3, pserial)
+        obj = zodb_unpickle(data)
+        eq(obj.value, 13)
+        # Now verify the refcounts table, which should be empty because the
+        # stored object isn't referenced by any other objects.
+        eq(len(self._storage._refcounts.keys()), 0)
+
+
+
+class WhiteboxHighLevelMinimal(ZODBTestBase):
+    ConcreteStorage = BDBMinimalStorage
+
+    def checkReferenceCounting(self):
+        eq = self.assertEqual
+        obj = MinPO(11)
+        self._root.obj = obj
+        get_transaction().commit()
+        obj.value = 12
+        get_transaction().commit()
+        obj.value = 13
+        get_transaction().commit()
+        # Make sure the databases have what we expect
+        eq(len(self._storage._serials.items()), 2)
+        eq(len(self._storage._pickles.items()), 2)
+        # And now refcount out the object
+        del self._root.obj
+        get_transaction().commit()
+        # Verification stage.  Our serials table should have exactly one
+        # entry, oid == 0
+        keys = self._storage._serials.keys()
+        eq(len(keys), 1)
+        eq(len(self._storage._serials.items()), 1)
+        eq(keys[0], ZERO)
+        # The pickles table now should have exactly one revision of the root
+        # object, and no revisions of the MinPO object, which should have been
+        # collected away.
+        pickles = self._storage._pickles.items()
+        eq(len(pickles), 1)
+        rec = pickles[0]
+        key = rec[0]
+        data = rec[1]
+        eq(key[:8], ZERO)
+        # And that pickle should have no 'obj' attribute.
+        unobj = zodb_unpickle(data)
+        self.failIf(hasattr(unobj, 'obj'))
+        # Our refcounts table should have no entries in it, because the root
+        # object is an island.
+        eq(len(self._storage._refcounts.keys()), 0)
+        # And of course, oids and pendings should be empty too
+        eq(len(self._storage._oids.keys()), 0)
+        eq(len(self._storage._pending.keys()), 0)
+
+    def checkRecursiveReferenceCounting(self):
+        eq = self.assertEqual
+        obj1 = Object()
+        obj2 = Object()
+        obj3 = Object()
+        obj4 = Object()
+        self._root.obj = obj1
+        obj1.obj = obj2
+        obj2.obj = obj3
+        obj3.obj = obj4
+        get_transaction().commit()
+        # Make sure the databases have what we expect
+        eq(len(self._storage._serials.items()), 5)
+        eq(len(self._storage._pickles.items()), 5)
+        # And now refcount out the object
+        del self._root.obj
+        get_transaction().commit()
+        # Verification stage.  Our serials table should have exactly one
+        # entry, oid == 0
+        keys = self._storage._serials.keys()
+        eq(len(keys), 1)
+        eq(len(self._storage._serials.items()), 1)
+        eq(keys[0], ZERO)
+        # The pickles table now should have exactly one revision of the root
+        # object, and no revisions of any other objects, which should have
+        # been collected away.
+        pickles = self._storage._pickles.items()
+        eq(len(pickles), 1)
+        rec = pickles[0]
+        key = rec[0]
+        data = rec[1]
+        eq(key[:8], ZERO)
+        # And that pickle should have no 'obj' attribute.
+        unobj = zodb_unpickle(data)
+        self.failIf(hasattr(unobj, 'obj'))
+        # Our refcounts table should have no entries in it, because the root
+        # object is an island.
+        eq(len(self._storage._refcounts.keys()), 0)
+        # And of course, oids and pendings should be empty too
+        eq(len(self._storage._oids.keys()), 0)
+        eq(len(self._storage._pending.keys()), 0)
+
+
+
+class WhiteboxHighLevelFull(ZODBTestBase):
+    ConcreteStorage = BDBFullStorage
+
+    def checkReferenceCounting(self):
+        eq = self.assertEqual
+        # Make sure the databases have what we expect
+        eq(len(self._storage._serials.items()), 1)
+        eq(len(self._storage._pickles.items()), 1)
+        # Now store an object
+        obj = MinPO(11)
+        self._root.obj = obj
+        get_transaction().commit()
+        # Make sure the databases have what we expect
+        eq(len(self._storage._serials.items()), 2)
+        eq(len(self._storage._pickles.items()), 3)
+        obj.value = 12
+        get_transaction().commit()
+        # Make sure the databases have what we expect
+        eq(len(self._storage._serials.items()), 2)
+        eq(len(self._storage._pickles.items()), 4)
+        obj.value = 13
+        get_transaction().commit()
+        # Make sure the databases have what we expect
+        eq(len(self._storage._serials.items()), 2)
+        eq(len(self._storage._pickles.items()), 5)
+        # And now refcount out the object
+        del self._root.obj
+        get_transaction().commit()
+        # Verification stage.  Our serials tabl should still have 2 entries,
+        # one for the root object and one for the now unlinked MinPO obj.
+        keys = self._storage._serials.keys()
+        eq(len(keys), 2)
+        eq(len(self._storage._serials.items()), 2)
+        eq(keys[0], ZERO)
+        # The pickles table should now have 6 entries, broken down like so:
+        # - 3 revisions of the root object: the initial database-open
+        #   revision, the revision that got its obj attribute set, and the
+        #   revision that got its obj attribute deleted.
+        # - 3 Three revisions of obj, corresponding to values 11, 12, and 13
+        pickles = self._storage._pickles.items()
+        eq(len(pickles), 6)
+        # Our refcounts table should have one entry in it for the MinPO that's
+        # referenced in an earlier revision of the root object
+        eq(len(self._storage._refcounts.keys()), 1)
+        # And of course, oids and pendings should be empty too
+        eq(len(self._storage._oids.keys()), 0)
+        eq(len(self._storage._pending.keys()), 0)
+
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(WhiteboxLowLevelMinimal, 'check'))
+    suite.addTest(unittest.makeSuite(WhiteboxHighLevelMinimal, 'check'))
+    suite.addTest(unittest.makeSuite(WhiteboxHighLevelFull, 'check'))
+    return suite
+
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')


=== Zope3/src/zodb/storage/tests/test_zodb_simple.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:55 2002
+++ Zope3/src/zodb/storage/tests/test_zodb_simple.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,97 @@
+##############################################################################
+#
+# 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
+#
+##############################################################################
+
+try:
+    import bsddb3
+except ImportError:
+    raise RuntimeError, 'BerkeleyDB not available'
+
+# Test some simple ZODB level stuff common to both the Minimal and Full
+# storages, like transaction aborts and commits, changing objects, etc.
+# Doesn't test undo, versions, or packing.
+
+import time
+import unittest
+# Import this here and now so that import failures properly cause the test
+# suite to ignore these tests.
+import bsddb3
+
+from zodb.storage.tests.base import ZODBTestBase
+from transaction import get_transaction
+from persistence.dict import PersistentDict
+
+
+
+class CommitAndRead:
+    def checkCommit(self):
+        self.failUnless(not self._root)
+        names = self._root['names'] = PersistentDict()
+        names['Warsaw'] = 'Barry'
+        names['Hylton'] = 'Jeremy'
+        get_transaction().commit()
+
+    def checkReadAfterCommit(self):
+        eq = self.assertEqual
+        self.checkCommit()
+        names = self._root['names']
+        eq(names['Warsaw'], 'Barry')
+        eq(names['Hylton'], 'Jeremy')
+        self.failUnless(names.get('Drake') is None)
+
+    def checkAbortAfterRead(self):
+        self.checkReadAfterCommit()
+        names = self._root['names']
+        names['Drake'] = 'Fred'
+        get_transaction().abort()
+
+    def checkReadAfterAbort(self):
+        self.checkAbortAfterRead()
+        names = self._root['names']
+        self.failUnless(names.get('Drake') is None)
+
+    def checkChangingCommits(self):
+        self.checkReadAfterAbort()
+        now = time.time()
+        # Make sure the last timestamp was more than 3 seconds ago
+        timestamp = self._root.get('timestamp')
+        if timestamp is None:
+            timestamp = self._root['timestamp'] = 0
+            get_transaction().commit()
+        self.failUnless(now > timestamp + 3)
+        self._root['timestamp'] = now
+        time.sleep(3)
+
+
+
+class MinimalCommitAndRead(ZODBTestBase, CommitAndRead):
+    from zodb.storage.bdbminimal import BDBMinimalStorage
+    ConcreteStorage = BDBMinimalStorage
+
+
+class FullCommitAndRead(ZODBTestBase, CommitAndRead):
+    from zodb.storage.bdbfull import BDBFullStorage
+    ConcreteStorage = BDBFullStorage
+
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(MinimalCommitAndRead, 'check'))
+    suite.addTest(unittest.makeSuite(FullCommitAndRead, 'check'))
+    return suite
+
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')


=== Zope3/src/zodb/storage/tests/timeiter.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:55 2002
+++ Zope3/src/zodb/storage/tests/timeiter.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,286 @@
+#! /usr/bin/env python
+
+##############################################################################
+#
+# 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
+#
+##############################################################################
+
+"""Time transaction commits and normalize vs. pickle size and #objects.
+
+Actually just counts the size of pickles in the transaction via the iterator
+protocol, so storage overheads aren't counted.
+
+Usage: %(PROGRAM)s [options]
+Options:
+    -h/--help
+        Print this message and exit.
+
+    -s filename
+    --source=filename
+        Use database in filename as the source (must be a FileStorage)
+
+    -d filename
+    --dest=filename
+        Use database in filename as the destination (must be a BDB storage)
+
+    -o filename
+    --output=filename
+        Print results in filename, otherwise stdout.
+
+    -m txncount
+    --max=txncount
+        Stop after committing txncount transactions.
+
+    -k txncount
+    --skip=txncount
+        Skip the first txncount transactions.
+
+    -p/--profile
+        Turn on specialized profiling.
+
+    -q/--quiet
+        Be quite.
+"""
+
+import sys
+import os
+import getopt
+import time
+import errno
+import profile
+import traceback
+import marshal
+
+from bsddb3 import db
+
+from zodb import utils
+from zodb.timestamp import TimeStamp
+from zodb.storage.file import FileStorage
+from zodb.storage.bdbfull import BDBFullStorage
+
+PROGRAM = sys.argv[0]
+ZERO = '\0'*8
+
+
+
+def usage(code, msg=''):
+    print >> sys.stderr, __doc__ % globals()
+    if msg:
+        print >> sys.stderr, msg
+    sys.exit(code)
+
+
+
+def main():
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], 'hs:d:qo:l:pm:k:',
+                                   ['help', 'source=', 'dest=', 'quiet',
+                                    'output=', 'logfile=', 'profile',
+                                    'max=', 'skip='])
+    except getopt.error, msg:
+        usage(1, msg)
+
+    class Options:
+        source = None
+        dest = None
+        verbose = 1
+        outfile = None
+        logfile = None
+        profilep = 0
+        maxtxn = -1
+        skiptxn = -1
+
+    options = Options()
+
+    for opt, arg in opts:
+        if opt in ('-h', '--help'):
+            usage(0)
+        elif opt in ('-s', '--source'):
+            options.source = arg
+        elif opt in ('-d', '--dest'):
+            options.dest = arg
+        elif opt in ('-q', '--quiet'):
+            options.verbose = 0
+        elif opt in ('-o', '--output'):
+            options.outfile = arg
+        elif opt in ('-l', '--logfile'):
+            options.logfile = arg
+        elif opt in ('-p', '--profile'):
+            options.profilep = 1
+        elif opt in ('-m', '--max'):
+            options.maxtxn = int(arg)
+        elif opt in ('-k', '--skip'):
+            options.skiptxn = int(arg)
+
+    if args:
+        usage(1)
+
+    if not options.source or not options.dest:
+        usage(1, 'Source and destination databases must be provided')
+
+    # Open the output file
+    if options.outfile is None:
+        options.outfp = sys.stdout
+        options.outclosep = 0
+    else:
+        options.outfp = open(options.outfile, 'w')
+        options.outclosep = 1
+
+    # Open the logfile
+    if options.logfile is None:
+        options.logfp = sys.stdout
+        options.logclosep = 0
+    else:
+        options.logfp = open(options.logfile, 'w')
+        options.logclosep = 1
+
+    # Print a comment, this is a hack
+    print >> options.outfp, '# FS->BDB 3.3.11'
+    print >> options.outfp, '#', time.ctime()
+
+    print >>sys.stderr, 'Opening source FileStorage...'
+    t0 = time.time()
+    srcdb = FileStorage(options.source, read_only=1)
+    t1 = time.time()
+    print >>sys.stderr, 'Opening source FileStorage done. %s seconds' % (t1-t0)
+#
+# Uncomment this section to do a FS->BDB migration
+#
+    print >>sys.stderr, 'Opening destination BDB...'
+    t0 = time.time()
+    dstdb = BDBFullStorage(options.dest)
+    t1 = time.time()
+    print >>sys.stderr, 'Opening destination BDB done. %s seconds' % (t1-t0)
+
+#
+# Uncomment this section to do a FS->FS migration
+#
+##    print >>sys.stderr, 'Opening destination FileStorage...'
+##    t0 = time.time()
+##    dstdb = FileStorage(dest)
+##    t1 = time.time()
+##    print >>sys.stderr, 'Opening destination FileStorage done. %s seconds' % (
+##        t1-t0)
+
+    try:
+        t0 = time.time()
+        doit(srcdb, dstdb, options)
+        t1 = time.time()
+        print 'Total time:', t1-t0
+    finally:
+        # Done
+        srcdb.close()
+        dstdb.close()
+        if options.outclosep:
+            options.outfp.close()
+        if options.logclosep:
+            options.logfp.close()
+
+
+
+def doit(srcdb, dstdb, options):
+    outfp = options.outfp
+    logfp = options.logfp
+    profilep = options.profilep
+    verbose = options.verbose
+    # some global information
+    largest_pickle = 0
+    largest_txn_in_size = 0
+    largest_txn_in_objects = 0
+    # Ripped from BaseStorage.copyTransactionsFrom()
+    ts = None
+    ok = 1
+    prevrevids = {}
+    counter = 0
+    skipper = 0
+    for txn in srcdb.iterator():
+        skipper += 1
+        if skipper <= options.skiptxn:
+            continue
+        counter += 1
+        if counter > options.maxtxn > 0:
+            break
+        tid = txn.tid
+        if ts is None:
+            ts = TimeStamp(tid)
+        else:
+            t = TimeStamp(tid)
+            if t <= ts:
+                if ok:
+                    print 'Time stamps are out of order %s, %s' % (ts, t)
+                    ok = 0
+                    ts = t.laterThan(ts)
+                    tid = `ts`
+                else:
+                    ts = t
+                    if not ok:
+                        print 'Time stamps are back in order %s' % t
+                        ok = 1
+        if verbose:
+            print ts
+
+        prof = None
+        if profilep and (counter % 100) == 0:
+            prof = profile.Profile()
+        objects = 0
+        size = 0
+        t0 = time.time()
+        dstdb.tpc_begin(txn, tid, txn.status)
+        t1 = time.time()
+        try:
+            for r in txn:
+                oid = r.oid
+                objects += 1
+                thissize = len(r.data)
+                size += thissize
+                if thissize > largest_pickle:
+                    largest_pickle = thissize
+                if verbose:
+                    if not r.version:
+                        vstr = 'norev'
+                    else:
+                        vstr = r.version
+                    print utils.U64(oid), vstr, len(r.data)
+                oldrevid = prevrevids.get(oid, ZERO)
+                newrevid = dstdb.store(oid, oldrevid, r.data, r.version, txn)
+                prevrevids[oid] = newrevid
+            t2 = time.time()
+            dstdb.tpc_vote(txn)
+            t3 = time.time()
+            # Profile every 100 transactions
+            if prof:
+                prof.runcall(dstdb.tpc_finish, txn)
+            else:
+                dstdb.tpc_finish(txn)
+            t4 = time.time()
+        except KeyError, e:
+            traceback.print_exc(file=logfp)
+
+        # record the results
+        if objects > largest_txn_in_objects:
+            largest_txn_in_objects = objects
+        if size > largest_txn_in_size:
+            largest_txn_in_size = size
+        print >> outfp, utils.U64(tid), objects, size, t4-t0, \
+              t1-t0, t2-t1, t3-t2, t4-t3
+
+        if prof:
+            prof.create_stats()
+            fp = open('profile-%02d.txt' % (counter / 100), 'wb')
+            marshal.dump(prof.stats, fp)
+            fp.close()
+    print >> outfp, largest_pickle, largest_txn_in_size, largest_txn_in_objects
+
+
+
+if __name__ == '__main__':
+    main()


=== Zope3/src/zodb/storage/tests/timepickles.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:55 2002
+++ Zope3/src/zodb/storage/tests/timepickles.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,297 @@
+#! /usr/bin/env python
+
+##############################################################################
+#
+# 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
+#
+##############################################################################
+
+"""Time transaction commits and normalize vs. pickle size and #objects.
+
+Actually just counts the size of pickles in the transaction via the iterator
+protocol, so storage overheads aren't counted.
+
+Usage: %(PROGRAM)s [options]
+Options:
+    -h/--help
+        Print this message and exit.
+
+    -s filename
+    --source=filename
+        Use database in filename as the source (must be a FileStorage)
+
+    -d filename
+    --dest=filename
+        Use database in filename as the destination (must be a BDB storage)
+
+    -o filename
+    --output=filename
+        Print results in filename, otherwise stdout.
+
+    -m txncount
+    --max=txncount
+        Stop after committing txncount transactions.
+
+    -k txncount
+    --skip=txncount
+        Skip the first txncount transactions.
+
+    -p/--profile
+        Turn on specialized profiling.
+
+    -q/--quiet
+        Be quite.
+"""
+
+import sys
+import os
+import getopt
+import time
+import errno
+import profile
+import traceback
+import marshal
+
+from bsddb3 import db
+
+from zodb import utils
+from zodb.timestamp import TimeStamp
+from zodb.storage.file import FileStorage
+from zodb.storage.bdbfull import BDBFullStorage
+
+PROGRAM = sys.argv[0]
+ZERO = '\0'*8
+
+
+
+def usage(code, msg=''):
+    print >> sys.stderr, __doc__ % globals()
+    if msg:
+        print >> sys.stderr, msg
+    sys.exit(code)
+
+
+
+def main():
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], 'hs:d:qo:l:pm:k:',
+                                   ['help', 'source=', 'dest=', 'quiet',
+                                    'output=', 'logfile=', 'profile',
+                                    'max=', 'skip='])
+    except getopt.error, msg:
+        usage(1, msg)
+
+    class Options:
+        source = None
+        dest = None
+        verbose = 1
+        outfile = None
+        logfile = None
+        profilep = 0
+        maxtxn = -1
+        skiptxn = -1
+
+    options = Options()
+
+    for opt, arg in opts:
+        if opt in ('-h', '--help'):
+            usage(0)
+        elif opt in ('-s', '--source'):
+            options.source = arg
+        elif opt in ('-d', '--dest'):
+            options.dest = arg
+        elif opt in ('-q', '--quiet'):
+            options.verbose = 0
+        elif opt in ('-o', '--output'):
+            options.outfile = arg
+        elif opt in ('-l', '--logfile'):
+            options.logfile = arg
+        elif opt in ('-p', '--profile'):
+            options.profilep = 1
+        elif opt in ('-m', '--max'):
+            options.maxtxn = int(arg)
+        elif opt in ('-k', '--skip'):
+            options.skiptxn = int(arg)
+
+    if args:
+        usage(1)
+
+    if not options.source or not options.dest:
+        usage(1, 'Source and destination databases must be provided')
+
+    # Open the output file
+    if options.outfile is None:
+        options.outfp = sys.stdout
+        options.outclosep = 0
+    else:
+        options.outfp = open(options.outfile, 'w')
+        options.outclosep = 1
+
+    # Open the logfile
+    if options.logfile is None:
+        options.logfp = sys.stdout
+        options.logclosep = 0
+    else:
+        options.logfp = open(options.logfile, 'w')
+        options.logclosep = 1
+
+    # Print a comment, this is a hack
+    print >> options.outfp, '# FS->BDB 3.3.11'
+    print >> options.outfp, '#', time.ctime()
+
+    print >>sys.stderr, 'Opening source FileStorage...'
+    t0 = time.time()
+    srcdb = FileStorage(options.source, read_only=1)
+    t1 = time.time()
+    print >>sys.stderr, 'Opening source FileStorage done. %s seconds' % (t1-t0)
+#
+# Uncomment this section to do a FS->BDB migration
+#
+    print >>sys.stderr, 'Opening destination BDB...'
+    t0 = time.time()
+##    dstdb = BDBFullStorage(options.dest)
+    dstdb = None
+    t1 = time.time()
+    print >>sys.stderr, 'Opening destination BDB done. %s seconds' % (t1-t0)
+
+#
+# Uncomment this section to do a FS->FS migration
+#
+##    print >>sys.stderr, 'Opening destination FileStorage...'
+##    t0 = time.time()
+##    dstdb = FileStorage(dest)
+##    t1 = time.time()
+##    print >>sys.stderr, 'Opening destination FileStorage done. %s seconds' % (
+##        t1-t0)
+
+    try:
+        t0 = time.time()
+        doit(srcdb, dstdb, options)
+        t1 = time.time()
+        print 'Total time:', t1-t0
+    finally:
+        # Done
+        srcdb.close()
+##        dstdb.close()
+        if options.outclosep:
+            options.outfp.close()
+        if options.logclosep:
+            options.logfp.close()
+
+
+
+def doit(srcdb, dstdb, options):
+    outfp = options.outfp
+    logfp = options.logfp
+    profilep = options.profilep
+    verbose = options.verbose
+    # some global information
+    largest_pickle = 0
+    largest_txn_in_size = 0
+    largest_txn_in_objects = 0
+    # Ripped from BaseStorage.copyTransactionsFrom()
+    ts = None
+    ok = 1
+    prevrevids = {}
+    counter = 0
+    skipper = 0
+
+    from bsddb3 import db
+    env = db.DBEnv()
+    env.open('BDB',
+        db.DB_CREATE       # create underlying files as necessary
+        | db.DB_RECOVER    # run normal recovery before opening
+        | db.DB_INIT_MPOOL # initialize shared memory buffer pool
+        | db.DB_INIT_LOCK  # initialize locking subsystem
+        | db.DB_INIT_TXN   # initialize transaction subsystem
+        | db.DB_THREAD     # we use the environment from other threads
+        )
+    d = db.DB(env)
+    d.open('zodb_picklesauce', db.DB_BTREE, db.DB_CREATE)
+
+    for txn in srcdb.iterator():
+        skipper += 1
+        if skipper <= options.skiptxn:
+            continue
+        counter += 1
+        if counter > options.maxtxn > 0:
+            break
+        tid = txn.tid
+        if ts is None:
+            ts = TimeStamp(tid)
+        else:
+            t = TimeStamp(tid)
+            if t <= ts:
+                if ok:
+                    print 'Time stamps are out of order %s, %s' % (ts, t)
+                    ok = 0
+                    ts = t.laterThan(ts)
+                    tid = `ts`
+                else:
+                    ts = t
+                    if not ok:
+                        print 'Time stamps are back in order %s' % t
+                        ok = 1
+        if verbose:
+            print ts
+
+        prof = None
+        if profilep and (counter % 100) == 0:
+            prof = profile.Profile()
+        objects = 0
+        size = 0
+        t0 = time.time()
+
+        t1 = time.time()
+        try:
+            dbtxn = env.txn_begin()
+            for r in txn:
+                oid = r.oid
+                objects += 1
+                thissize = len(r.data)
+                size += thissize
+                if thissize > largest_pickle:
+                    largest_pickle = thissize
+                if verbose:
+                    if not r.version:
+                        vstr = 'norev'
+                    else:
+                        vstr = r.version
+                    print utils.U64(oid), vstr, len(r.data)
+                key = oid + tid
+                d.put(key, r.data, txn=dbtxn)
+            t2 = time.time()
+            t3 = time.time()
+            dbtxn.commit()
+            t4 = time.time()
+        except KeyError, e:
+            traceback.print_exc(file=logfp)
+
+        # record the results
+        if objects > largest_txn_in_objects:
+            largest_txn_in_objects = objects
+        if size > largest_txn_in_size:
+            largest_txn_in_size = size
+        print >> outfp, utils.U64(tid), objects, size, t4-t0, \
+              t1-t0, t2-t1, t3-t2, t4-t3
+
+        if prof:
+            prof.create_stats()
+            fp = open('profile-%02d.txt' % (counter / 100), 'wb')
+            marshal.dump(prof.stats, fp)
+            fp.close()
+    d.close()
+    print >> outfp, largest_pickle, largest_txn_in_size, largest_txn_in_objects
+
+
+
+if __name__ == '__main__':
+    main()


=== Zope3/src/zodb/storage/tests/undo.py 1.1 => 1.2 === (534/634 lines abridged)
--- /dev/null	Wed Dec 25 09:13:55 2002
+++ Zope3/src/zodb/storage/tests/undo.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,631 @@
+"""Check transactionalUndo().
+
+Any storage that supports transactionalUndo() must pass these tests.
+"""
+
+import time
+import types
+
+from zodb import interfaces
+from zodb.ztransaction import Transaction
+from zodb.utils import u64, p64, z64
+from zodb.db import DB
+
+from zodb.storage.tests.minpo import MinPO
+from zodb.storage.tests.base import zodb_pickle, zodb_unpickle
+
+from persistence import Persistent
+from transaction import get_transaction
+
+class C(Persistent):
+    pass
+
+class TransactionalUndoStorage:
+
+    def _transaction_begin(self):
+        self.__serials = {}
+
+    def _transaction_store(self, oid, rev, data, vers, trans):
+        r = self._storage.store(oid, rev, data, vers, trans)
+        if r:
+            if type(r) == types.StringType:
+                self.__serials[oid] = r
+            else:
+                for oid, serial in r:
+                    self.__serials[oid] = serial
+
+    def _transaction_vote(self, trans):
+        r = self._storage.tpc_vote(trans)
+        if r:
+            for oid, serial in r:
+                self.__serials[oid] = serial
+
+    def _transaction_newserial(self, oid):
+        return self.__serials[oid]
+
+    def _multi_obj_transaction(self, objs):
+        newrevs = {}

[-=- -=- -=- 534 lines omitted -=- -=- -=-]

+                tid = info[base + j]['id']
+                s.transactionalUndo(tid, t)
+            s.tpc_vote(t)
+            s.tpc_finish(t)
+
+        for i in range(BATCHES):
+            undo(i)
+
+        # There are now (2 + OBJECTS) * BATCHES transactions:
+        #     BATCHES original transactions, followed by
+        #     OBJECTS * BATCHES modifications, followed by
+        #     BATCHES undos
+
+        iter = s.iterator()
+        offset = 0
+
+        eq = self.assertEqual
+
+        for i in range(BATCHES):
+            txn = iter[offset]
+            offset += 1
+
+            tid = p64(i + 1)
+            eq(txn.tid, tid)
+
+            L1 = [(rec.oid, rec.serial, rec.data_txn) for rec in txn]
+            L2 = [(oid, revid, None) for _tid, oid, revid in orig
+                  if _tid == tid]
+
+            eq(L1, L2)
+
+        for i in range(BATCHES * OBJECTS):
+            txn = iter[offset]
+            offset += 1
+            eq(len([rec for rec in txn if rec.data_txn is None]), 1)
+
+        for i in range(BATCHES):
+            txn = iter[offset]
+            offset += 1
+
+            # The undos are performed in reverse order.
+            otid = p64(BATCHES - i)
+            L1 = [(rec.oid, rec.data_txn) for rec in txn]
+            L2 = [(oid, otid) for _tid, oid, revid in orig
+                  if _tid == otid]
+            L1.sort()
+            L2.sort()
+            eq(L1, L2)
+
+        self.assertRaises(IndexError, iter.__getitem__, offset)


=== Zope3/src/zodb/storage/tests/undoversion.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:56 2002
+++ Zope3/src/zodb/storage/tests/undoversion.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,100 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+# Check interactions between transactionalUndo() and versions.  Any storage
+# that supports both transactionalUndo() and versions must pass these tests.
+
+from zodb.ztransaction import Transaction
+from zodb.storage.tests.minpo import MinPO
+from zodb.storage.tests.base import zodb_unpickle
+
+
+class TransactionalUndoVersionStorage:
+    def checkUndoInVersion(self):
+        oid = self._storage.new_oid()
+        version = 'one'
+        revid_a = self._dostore(oid, data=MinPO(91))
+        revid_b = self._dostore(oid, revid=revid_a, data=MinPO(92),
+                                version=version)
+        revid_c = self._dostore(oid, revid=revid_b, data=MinPO(93),
+                                version=version)
+        info=self._storage.undoInfo()
+        tid=info[0]['id']
+        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, '')
+        assert revid == revid_a
+        assert zodb_unpickle(data) == MinPO(91)
+        data, revid = self._storage.load(oid, version)
+        assert revid > revid_b and revid > revid_c
+        assert zodb_unpickle(data) == MinPO(92)
+        # Now commit the version...
+        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
+
+        data, revid = self._storage.load(oid, version)
+        assert zodb_unpickle(data) == MinPO(92)
+        data, revid = self._storage.load(oid, '')
+        assert zodb_unpickle(data) == MinPO(92)
+        # ...and undo the commit
+        info=self._storage.undoInfo()
+        tid=info[0]['id']
+        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)
+        assert zodb_unpickle(data) == MinPO(92)
+        data, revid = self._storage.load(oid, '')
+        assert zodb_unpickle(data) == MinPO(91)
+        # Now abort the version
+        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
+
+        data, revid = self._storage.load(oid, version)
+        assert zodb_unpickle(data) == MinPO(91)
+        data, revid = self._storage.load(oid, '')
+        assert zodb_unpickle(data) == MinPO(91)
+        # Now undo the abort
+        info=self._storage.undoInfo()
+        tid=info[0]['id']
+        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 ''
+        data, revid = self._storage.load(oid, version)
+        assert zodb_unpickle(data) == MinPO(92)
+        data, revid = self._storage.load(oid, '')
+        assert zodb_unpickle(data) == MinPO(91)


=== Zope3/src/zodb/storage/tests/version.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:56 2002
+++ Zope3/src/zodb/storage/tests/version.py	Wed Dec 25 09:12:20 2002
@@ -0,0 +1,331 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Run the version related tests for a storage.
+
+Any storage that supports versions should be able to pass all these tests.
+"""
+
+from zodb import interfaces
+from zodb.ztransaction import Transaction
+from zodb.storage.tests.minpo import MinPO
+from zodb.storage.tests.base import zodb_unpickle
+
+
+class VersionStorage:
+    def checkVersionedStoreAndLoad(self):
+        eq = self.assertEqual
+        # Store a couple of non-version revisions of the object
+        oid = self._storage.new_oid()
+        revid = self._dostore(oid, data=MinPO(11))
+        revid = self._dostore(oid, revid=revid, data=MinPO(12))
+        # And now store some new revisions in a version
+        version = 'test-version'
+        revid = self._dostore(oid, revid=revid, data=MinPO(13),
+                              version=version)
+        revid = self._dostore(oid, revid=revid, data=MinPO(14),
+                              version=version)
+        revid = self._dostore(oid, revid=revid, data=MinPO(15),
+                              version=version)
+        # Now read back the object in both the non-version and version and
+        # make sure the values jive.
+        data, revid = self._storage.load(oid, '')
+        eq(zodb_unpickle(data), MinPO(12))
+        data, vrevid = self._storage.load(oid, version)
+        eq(zodb_unpickle(data), MinPO(15))
+        if hasattr(self._storage, 'getSerial'):
+            s = self._storage.getSerial(oid)
+            eq(s, max(revid, vrevid))
+
+    def checkVersionedLoadErrors(self):
+        oid = self._storage.new_oid()
+        version = 'test-version'
+        revid = self._dostore(oid, data=MinPO(11))
+        revid = self._dostore(oid, revid=revid, data=MinPO(12),
+                              version=version)
+        # Try to load a bogus oid
+        self.assertRaises(KeyError,
+                          self._storage.load,
+                          self._storage.new_oid(), '')
+        data, revid = self._storage.load(oid, 'bogus')
+        self.assertEqual(zodb_unpickle(data), MinPO(11))
+
+
+    def checkVersionLock(self):
+        oid = self._storage.new_oid()
+        revid = self._dostore(oid, data=MinPO(11))
+        version = 'test-version'
+        revid = self._dostore(oid, revid=revid, data=MinPO(12),
+                              version=version)
+        self.assertRaises(interfaces.VersionLockError,
+                          self._dostore,
+                          oid, revid=revid, data=MinPO(14),
+                          version='another-version')
+
+    def checkVersionEmpty(self):
+        # Before we store anything, these versions ought to be empty
+        version = 'test-version'
+        self.failUnless(self._storage.versionEmpty(version))
+        # Now store some objects
+        oid = self._storage.new_oid()
+        revid = self._dostore(oid, data=MinPO(11))
+        revid = self._dostore(oid, revid=revid, data=MinPO(12))
+        revid = self._dostore(oid, revid=revid, data=MinPO(13),
+                              version=version)
+        revid = self._dostore(oid, revid=revid, data=MinPO(14),
+                              version=version)
+        # The blank version should not be empty
+
+        # Neither should 'test-version'
+        self.failUnless(not self._storage.versionEmpty(version))
+        # But this non-existant version should be empty
+        self.failUnless(self._storage.versionEmpty('bogus'))
+
+    def checkVersions(self):
+        unless = self.failUnless
+        # Store some objects in the non-version
+        oid1 = self._storage.new_oid()
+        oid2 = self._storage.new_oid()
+        oid3 = self._storage.new_oid()
+        revid1 = self._dostore(oid1, data=MinPO(11))
+        revid2 = self._dostore(oid2, data=MinPO(12))
+        revid3 = self._dostore(oid3, data=MinPO(13))
+        # Now create some new versions
+        revid1 = self._dostore(oid1, revid=revid1, data=MinPO(14),
+                               version='one')
+        revid2 = self._dostore(oid2, revid=revid2, data=MinPO(15),
+                               version='two')
+        revid3 = self._dostore(oid3, revid=revid3, data=MinPO(16),
+                               version='three')
+        # Ask for the versions
+        versions = self._storage.versions()
+        unless('one' in versions)
+        unless('two' in versions)
+        unless('three' in versions)
+        # Now flex the `max' argument
+        versions = self._storage.versions(1)
+        self.assertEqual(len(versions), 1)
+        unless('one' in versions or 'two' in versions or 'three' in versions)
+
+    def _setup_version(self, version='test-version'):
+        # Store some revisions in the non-version
+        oid = self._storage.new_oid()
+        revid = self._dostore(oid, data=MinPO(49))
+        revid = self._dostore(oid, revid=revid, data=MinPO(50))
+        nvrevid = revid = self._dostore(oid, revid=revid, data=MinPO(51))
+        # Now do some stores in a version
+        revid = self._dostore(oid, revid=revid, data=MinPO(52),
+                              version=version)
+        revid = self._dostore(oid, revid=revid, data=MinPO(53),
+                              version=version)
+        revid = self._dostore(oid, revid=revid, data=MinPO(54),
+                              version=version)
+        return oid, version
+
+    def checkAbortVersion(self):
+        eq = self.assertEqual
+        oid, version = self._setup_version()
+
+        # XXX Not sure I can write a test for getSerial() in the
+        # presence of aborted versions, because FileStorage and
+        # Berkeley storage give a different answer. I think Berkeley
+        # is right and FS is wrong.
+
+##        s1 = self._storage.getSerial(oid)
+        # Now abort the version -- must be done in a 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)
+        eq(oids[0], oid)
+        data, revid = self._storage.load(oid, '')
+        eq(zodb_unpickle(data), MinPO(51))
+
+    def checkAbortVersionErrors(self):
+        eq = self.assertEqual
+        oid, version = self._setup_version()
+        # Now abort a bogus version
+        t = Transaction()
+        self._storage.tpc_begin(t)
+
+        # And try to abort the empty version
+        if (hasattr(self._storage, 'supportsTransactionalUndo')
+            and self._storage.supportsTransactionalUndo()):
+            # XXX FileStorage used to be broken on this one
+            self.assertRaises(interfaces.VersionError,
+                              self._storage.abortVersion,
+                              '', t)
+
+        # But now we really try to abort the version
+        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, '')
+        eq(zodb_unpickle(data), MinPO(51))
+
+    def checkCommitVersionErrors(self):
+        if not (hasattr(self._storage, 'supportsTransactionalUndo')
+            and self._storage.supportsTransactionalUndo()):
+            # XXX FileStorage used to be broken on this one
+            return
+        eq = self.assertEqual
+        oid1, version1 = self._setup_version('one')
+        data, revid1 = self._storage.load(oid1, version1)
+        eq(zodb_unpickle(data), MinPO(54))
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        try:
+            self.assertRaises(interfaces.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
+        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
+        revid = self._dostore(oid, revid=revid, data=MinPO(52))
+        revid = self._dostore(oid, revid=revid, data=MinPO(53))
+        revid = self._dostore(oid, revid=revid, data=MinPO(54))
+        data, newrevid = self._storage.load(oid, '')
+        eq(newrevid, revid)
+        eq(zodb_unpickle(data), MinPO(54))
+
+    def checkCommitToNonVersion(self):
+        eq = self.assertEqual
+        oid, version = self._setup_version()
+        data, revid = self._storage.load(oid, version)
+        eq(zodb_unpickle(data), MinPO(54))
+        data, revid = self._storage.load(oid, '')
+        eq(zodb_unpickle(data), MinPO(51))
+        # Try committing this version to the empty version
+        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))
+
+    def checkCommitToOtherVersion(self):
+        eq = self.assertEqual
+        oid1, version1 = self._setup_version('one')
+
+        data, revid1 = self._storage.load(oid1, version1)
+        eq(zodb_unpickle(data), MinPO(54))
+        oid2, version2 = self._setup_version('two')
+        data, revid2 = self._storage.load(oid2, version2)
+        eq(zodb_unpickle(data), MinPO(54))
+
+        # make sure we see the non-version data when appropriate
+        data, revid2 = self._storage.load(oid1, version2)
+        eq(zodb_unpickle(data), MinPO(51))
+        data, revid2 = self._storage.load(oid2, version1)
+        eq(zodb_unpickle(data), MinPO(51))
+        data, revid2 = self._storage.load(oid1, '')
+        eq(zodb_unpickle(data), MinPO(51))
+
+        # Okay, now let's commit object1 to version2
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.commitVersion(version1, version2,
+                                           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)
+        eq(zodb_unpickle(data), MinPO(54))
+        data, revid = self._storage.load(oid2, version2)
+        eq(zodb_unpickle(data), MinPO(54))
+
+        # an object can only exist in one version, so a load from
+        # version1 should now give the non-version data
+        data, revid2 = self._storage.load(oid1, version1)
+        eq(zodb_unpickle(data), MinPO(51))
+
+        # as should a version that has never been used
+        data, revid2 = self._storage.load(oid1, 'bela lugosi')
+        eq(zodb_unpickle(data), MinPO(51))
+
+    def checkAbortOneVersionCommitTheOther(self):
+        eq = self.assertEqual
+        oid1, version1 = self._setup_version('one')
+        data, revid1 = self._storage.load(oid1, version1)
+        eq(zodb_unpickle(data), MinPO(54))
+        oid2, version2 = self._setup_version('two')
+        data, revid2 = self._storage.load(oid2, version2)
+        eq(zodb_unpickle(data), MinPO(54))
+
+        # Let's make sure we can't get object1 in version2
+        data, revid2 = self._storage.load(oid1, version2)
+        eq(zodb_unpickle(data), MinPO(51))
+
+        # First, let's abort version1
+        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, '')
+        eq(zodb_unpickle(data), MinPO(51))
+
+        data, revid = self._storage.load(oid1, '')
+        eq(zodb_unpickle(data), MinPO(51))
+        data, revid = self._storage.load(oid1, '')
+        eq(zodb_unpickle(data), MinPO(51))
+
+        data, revid = self._storage.load(oid2, '')
+        eq(zodb_unpickle(data), MinPO(51))
+        data, revid = self._storage.load(oid2, version2)
+        eq(zodb_unpickle(data), MinPO(54))
+        # Okay, now let's commit version2 back to the trunk
+        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, '')
+        eq(zodb_unpickle(data), MinPO(51))
+
+        # But the trunk should be up to date now
+        data, revid = self._storage.load(oid2, '')
+        eq(zodb_unpickle(data), MinPO(54))
+        data, revid = self._storage.load(oid2, version2)
+        eq(zodb_unpickle(data), MinPO(54))
+
+        oid = self._storage.new_oid()
+        revid = self._dostore(oid, revid=revid, data=MinPO(54), version='one')
+        self.assertRaises(KeyError,
+                          self._storage.load, oid, '')
+        self.assertRaises(KeyError,
+                          self._storage.load, oid, 'two')