[Zodb-checkins] CVS: ZODB4/ZODB/tests - PackableStorage.py:1.20 TransactionalUndoStorage.py:1.25

Jeremy Hylton jeremy@zope.com
Tue, 3 Dec 2002 18:04:22 -0500


Update of /cvs-repository/ZODB4/ZODB/tests
In directory cvs.zope.org:/tmp/cvs-serv21476/tests

Modified Files:
	PackableStorage.py TransactionalUndoStorage.py 
Log Message:
Merge in some tests from ZODB3.

XXX These are currently failing for reasons I can't figure out.  Maybe
Barry can help.



=== ZODB4/ZODB/tests/PackableStorage.py 1.19 => 1.20 ===
--- ZODB4/ZODB/tests/PackableStorage.py:1.19	Tue Nov 26 13:40:42 2002
+++ ZODB4/ZODB/tests/PackableStorage.py	Tue Dec  3 18:04:22 2002
@@ -23,7 +23,9 @@
 from cStringIO import StringIO
 
 import time
-
+from ZODB.DB import DB
+from Persistence import Persistent
+from Transaction import get_transaction
 
 ZERO = '\0'*8
 
@@ -50,6 +52,9 @@
     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
@@ -321,3 +326,43 @@
         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 = C()
+        obj.value = 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)


=== ZODB4/ZODB/tests/TransactionalUndoStorage.py 1.24 => 1.25 ===
--- ZODB4/ZODB/tests/TransactionalUndoStorage.py:1.24	Thu Jul 25 17:32:11 2002
+++ ZODB4/ZODB/tests/TransactionalUndoStorage.py	Tue Dec  3 18:04:22 2002
@@ -1,31 +1,25 @@
-##############################################################################
-#
-# 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 transactionalUndo().
 
 Any storage that supports transactionalUndo() must pass these tests.
 """
 
+import time
 import types
+
 from ZODB import POSException
 from ZODB.ZTransaction import Transaction
+from ZODB.utils import u64, p64, z64
+from ZODB.DB import DB
 
 from ZODB.tests.MinPO import MinPO
 from ZODB.tests.StorageTestBase import zodb_pickle, zodb_unpickle
 
-ZERO = '\0'*8
+from Persistence import Persistent
+from Transaction import get_transaction
+
+class C(Persistent):
+    pass
 
-
 class TransactionalUndoStorage:
 
     def _transaction_begin(self):
@@ -62,7 +56,18 @@
         for oid in newrevs.keys():
             newrevs[oid] = self._transaction_newserial(oid)
         return newrevs
-    
+
+    def _iterate(self):
+        """Iterate over the storage in its final state."""
+        # This is testing that the iterator() code works correctly.
+        # The hasattr() guards against ZEO, which doesn't support iterator.
+        if not hasattr(self._storage, "iterator"):
+            return
+        iter = self._storage.iterator()
+        for txn in iter:
+            for rec in txn:
+                pass
+
     def checkSimpleTransactionalUndo(self):
         eq = self.assertEqual
         oid = self._storage.new_oid()
@@ -123,6 +128,7 @@
         eq(oids[0], oid)
         data, revid = self._storage.load(oid, '')
         eq(zodb_unpickle(data), MinPO(23))
+        self._iterate()
 
     def checkUndoCreationBranch1(self):
         eq = self.assertEqual
@@ -153,6 +159,7 @@
         eq(len(oids), 1)
         eq(oids[0], oid)
         self.assertRaises(KeyError, self._storage.load, oid, '')
+        self._iterate()
 
     def checkUndoCreationBranch2(self):
         eq = self.assertEqual
@@ -184,6 +191,7 @@
         eq(oids[0], oid)
         data, revid = self._storage.load(oid, '')
         eq(zodb_unpickle(data), MinPO(12))
+        self._iterate()
 
     def checkTwoObjectUndo(self):
         eq = self.assertEqual
@@ -192,7 +200,7 @@
                                  map(MinPO, (31, 32, 51, 52)))
         oid1 = self._storage.new_oid()
         oid2 = self._storage.new_oid()
-        revid1 = revid2 = ZERO
+        revid1 = revid2 = z64
         # Store two objects in the same transaction
         t = Transaction()
         self._storage.tpc_begin(t)
@@ -237,6 +245,7 @@
         eq(zodb_unpickle(data), MinPO(31))
         data, revid2 = self._storage.load(oid2, '')
         eq(zodb_unpickle(data), MinPO(51))
+        self._iterate()
 
     def checkTwoObjectUndoAtOnce(self):
         # Convenience
@@ -247,7 +256,7 @@
                                                (30, 31, 32, 50, 51, 52)))
         oid1 = self._storage.new_oid()
         oid2 = self._storage.new_oid()
-        revid1 = revid2 = ZERO
+        revid1 = revid2 = z64
         # Store two objects in the same transaction
         d = self._multi_obj_transaction([(oid1, revid1, p30),
                                          (oid2, revid2, p50),
@@ -307,6 +316,7 @@
         eq(zodb_unpickle(data), MinPO(32))
         data, revid2 = self._storage.load(oid2, '')
         eq(zodb_unpickle(data), MinPO(52))
+        self._iterate()
 
     def checkTwoObjectUndoAgain(self):
         eq = self.assertEqual
@@ -377,7 +387,8 @@
         eq(zodb_unpickle(data), MinPO(33))
         data, revid2 = self._storage.load(oid2, '')
         eq(zodb_unpickle(data), MinPO(54))
-        
+        self._iterate()
+
 
     def checkNotUndoable(self):
         eq = self.assertEqual
@@ -391,12 +402,9 @@
         tid = info[1]['id']
         t = Transaction()
         self._storage.tpc_begin(t)
-        try:
-            self._storage.transactionalUndo(tid, t)
-        except POSException.UndoError, e:
-            s = str(e)
-        else:
-            self.fail("UndoError expected")
+        self.assertRaises(POSException.UndoError,
+                          self._storage.transactionalUndo,
+                          tid, t)
         self._storage.tpc_abort(t)
         # Now have more fun: object1 and object2 are in the same transaction,
         # which we'll try to undo to, but one of them has since modified in
@@ -404,7 +412,7 @@
         oid1 = oid
         revid1 = revid_c
         oid2 = self._storage.new_oid()
-        revid2 = ZERO
+        revid2 = z64
         p81, p82, p91, p92 = map(zodb_pickle,
                                  map(MinPO, (81, 82, 91, 92)))
 
@@ -433,10 +441,191 @@
         tid = info[1]['id']
         t = Transaction()
         self._storage.tpc_begin(t)
-        try:
-            self._storage.transactionalUndo(tid, t)
-        except POSException.UndoError, e:
-            s = str(e)
-        else:
-            self.fail("UndoError expected")
+        self.assertRaises(POSException.UndoError,
+                          self._storage.transactionalUndo,
+                          tid, t)
         self._storage.tpc_abort(t)
+        self._iterate()
+
+    def checkTransactionalUndoAfterPack(self):
+        eq = self.assertEqual
+        # Add a few object revisions
+        oid = self._storage.new_oid()
+        revid1 = self._dostore(oid, data=MinPO(51))
+        # For the pack(), we need a timestamp greater than revid1's timestamp.
+        # The semantics of pack()'s `t' argument is that all non-current
+        # revisions with an earlier timestamp will be packed away.  If they
+        # were equal (because the Windows clock resolution is too coarse),
+        # then we won't pack away the first revision.
+        now = packtime = time.time()
+        while packtime <= now:
+            packtime = time.time()
+        revid2 = self._dostore(oid, revid=revid1, data=MinPO(52))
+        revid3 = self._dostore(oid, revid=revid2, data=MinPO(53))
+        # Now get the undo log
+        info = self._storage.undoInfo()
+        eq(len(info), 3)
+        tid = info[0]['id']
+        # Now pack just the initial revision of the object.  We need the
+        # second revision otherwise we won't be able to undo the third
+        # revision!
+        self._storage.pack(packtime)
+        # Make some basic assertions about the undo information now
+        info2 = self._storage.undoInfo()
+        eq(len(info2), 2)
+        # And now attempt to undo the last transaction
+        t = Transaction()
+        self._storage.tpc_begin(t)
+        oids = self._storage.transactionalUndo(tid, t)
+        self._storage.tpc_vote(t)
+        self._storage.tpc_finish(t)
+        eq(len(oids), 1)
+        eq(oids[0], oid)
+        data, revid = self._storage.load(oid, '')
+        # The object must now be at the second state
+        eq(zodb_unpickle(data), MinPO(52))
+        self._iterate()
+
+    def checkTransactionalUndoAfterPackWithObjectUnlinkFromRoot(self):
+        eq = self.assertEqual
+        db = DB(self._storage)
+        conn = db.open()
+        root = conn.root()
+
+        o1 = C()
+        o2 = C()
+        root['obj'] = o1
+        o1.obj = o2
+        txn = get_transaction()
+        txn.note('o1 -> o2')
+        txn.commit()
+        now = packtime = time.time()
+        while packtime <= now:
+            packtime = time.time()
+
+        o3 = C()
+        o2.obj = o3
+        txn = get_transaction()
+        txn.note('o1 -> o2 -> o3')
+        txn.commit()
+
+        o1.obj = o3
+        txn = get_transaction()
+        txn.note('o1 -> o3')
+        txn.commit()
+
+        log = self._storage.undoLog()
+        eq(len(log), 4)
+        for entry in zip(log, ('o1 -> o3', 'o1 -> o2 -> o3',
+                               'o1 -> o2', 'initial database creation')):
+            eq(entry[0]['description'], entry[1])
+
+        self._storage.pack(packtime)
+
+        log = self._storage.undoLog()
+        for entry in zip(log, ('o1 -> o3', 'o1 -> o2 -> o3')):
+            eq(entry[0]['description'], entry[1])
+
+        tid = log[0]['id']
+        db.undo(tid)
+        txn = get_transaction()
+        txn.note('undo')
+        txn.commit()
+        # undo does a txn-undo, but doesn't invalidate
+        conn.sync()
+
+        log = self._storage.undoLog()
+        for entry in zip(log, ('undo', 'o1 -> o3', 'o1 -> o2 -> o3')):
+            eq(entry[0]['description'], entry[1])
+
+        eq(o1.obj, o2)
+        eq(o1.obj.obj, o3)
+        self._iterate()
+
+    def checkTransactionalUndoIterator(self):
+        # check that data_txn set in iterator makes sense
+        if not hasattr(self._storage, "iterator"):
+            return
+
+        s = self._storage
+
+        BATCHES = 4
+        OBJECTS = 4
+
+        orig = []
+        for i in range(BATCHES):
+            t = Transaction()
+            tid = p64(i + 1)
+            s.tpc_begin(t, tid)
+            for j in range(OBJECTS):
+                oid = s.new_oid()
+                obj = MinPO(i * OBJECTS + j)
+                revid = s.store(oid, None, zodb_pickle(obj), '', t)
+                orig.append((tid, oid, revid))
+            s.tpc_vote(t)
+            s.tpc_finish(t)
+
+        i = 0
+        for tid, oid, revid in orig:
+            self._dostore(oid, revid=revid, data=MinPO(revid),
+                          description="update %s" % i)
+
+        # Undo the OBJECTS transactions that modified objects created
+        # in the ith original transaction.
+
+        def undo(i):
+            info = s.undoInfo()
+            t = Transaction()
+            s.tpc_begin(t)
+            base = i * OBJECTS + i
+            for j in range(OBJECTS):
+                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)