[Zope-Checkins] CVS: ZODB3/ZODB/tests - TransactionalUndoStorage.py:1.28
Jeremy Hylton
jeremy@zope.com
Tue, 22 Apr 2003 14:02:42 -0400
Update of /cvs-repository/ZODB3/ZODB/tests
In directory cvs.zope.org:/tmp/cvs-serv6290/ZODB/tests
Modified Files:
TransactionalUndoStorage.py
Log Message:
Backport to pack tests from ZODB4.
=== ZODB3/ZODB/tests/TransactionalUndoStorage.py 1.27 => 1.28 ===
--- ZODB3/ZODB/tests/TransactionalUndoStorage.py:1.27 Thu Dec 5 19:00:53 2002
+++ ZODB3/ZODB/tests/TransactionalUndoStorage.py Tue Apr 22 14:02:41 2003
@@ -34,6 +34,23 @@
class C(Persistent):
pass
+def snooze():
+ # In Windows, it's possible that two successive time.time() calls return
+ # the same value. Tim guarantees that time never runs backwards. You
+ # usually want to call this before you pack a storage, or must make other
+ # guarantees about increasing timestamps.
+ now = time.time()
+ while now == time.time():
+ time.sleep(0.1)
+
+def listeq(L1, L2):
+ """Return True if L1.sort() == L2.sort()"""
+ c1 = L1[:]
+ c2 = L2[:]
+ c1.sort()
+ c2.sort()
+ return c1 == c2
+
class TransactionalUndoStorage:
def _transaction_begin(self):
@@ -555,6 +572,187 @@
eq(o1.obj, o2)
eq(o1.obj.obj, o3)
self._iterate()
+
+ def checkPackAfterUndoDeletion(self):
+ db = DB(self._storage)
+ cn = db.open()
+ root = cn.root()
+
+ pack_times = []
+ def set_pack_time():
+ snooze()
+ pack_times.append(time.time())
+
+ root["key0"] = MinPO(0)
+ root["key1"] = MinPO(1)
+ root["key2"] = MinPO(2)
+ txn = get_transaction()
+ txn.note("create 3 keys")
+ txn.commit()
+
+ set_pack_time()
+
+ del root["key1"]
+ txn = get_transaction()
+ txn.note("delete 1 key")
+ txn.commit()
+
+ set_pack_time()
+
+ root._p_deactivate()
+ cn.sync()
+ self.assert_(listeq(root.keys(), ["key0", "key2"]))
+
+ L = db.undoInfo()
+ db.undo(L[0]["id"])
+ txn = get_transaction()
+ txn.note("undo deletion")
+ txn.commit()
+
+ set_pack_time()
+
+ root._p_deactivate()
+ cn.sync()
+ self.assert_(listeq(root.keys(), ["key0", "key1", "key2"]))
+
+ for t in pack_times:
+ self._storage.pack(t, referencesf)
+
+ root._p_deactivate()
+ cn.sync()
+ self.assert_(listeq(root.keys(), ["key0", "key1", "key2"]))
+ for i in range(3):
+ obj = root["key%d" % i]
+ self.assertEqual(obj.value, i)
+ root.items()
+
+ def checkPackAfterUndoManyTimes(self):
+ db = DB(self._storage)
+ cn = db.open()
+ rt = cn.root()
+
+ rt["test"] = MinPO(1)
+ get_transaction().commit()
+ rt["test2"] = MinPO(2)
+ get_transaction().commit()
+ rt["test"] = MinPO(3)
+ txn = get_transaction()
+ txn.note("root of undo")
+ txn.commit()
+
+ packtimes = []
+ for i in range(10):
+ L = db.undoInfo()
+ db.undo(L[0]["id"])
+ txn = get_transaction()
+ txn.note("undo %d" % i)
+ txn.commit()
+ rt._p_deactivate()
+ cn.sync()
+
+ self.assertEqual(rt["test"].value, i % 2 and 3 or 1)
+ self.assertEqual(rt["test2"].value, 2)
+
+ packtimes.append(time.time())
+ snooze()
+
+ for t in packtimes:
+ self._storage.pack(t, referencesf)
+ cn.sync()
+ cn._cache.clear()
+ # The last undo set the value to 3 and pack should
+ # never change that.
+ self.assertEqual(rt["test"].value, 3)
+ self.assertEqual(rt["test2"].value, 2)
+
+ def testTransactionalUndoIterator(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.tpcBegin(t, tid)
+ for j in range(OBJECTS):
+ oid = s.newObjectId()
+ obj = MinPO(i * OBJECTS + j)
+ data, refs = zodb_pickle(obj)
+ revid = s.store(oid, None, data, refs, '', t)
+ orig.append((tid, oid, revid))
+ s.tpcVote(t)
+ s.tpcFinish(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.tpcBegin(t)
+ base = i * OBJECTS + i
+ for j in range(OBJECTS):
+ tid = info[base + j]['id']
+ s.undo(tid, t)
+ s.tpcVote(t)
+ s.tpcFinish(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
+
+ fsiter = iter(s.iterator())
+ offset = 0
+
+ eq = self.assertEqual
+
+ for i in range(BATCHES):
+ txn = fsiter.next()
+ 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 = fsiter.next()
+ offset += 1
+ eq(len([rec for rec in txn if rec.data_txn is None]), 1)
+
+ for i in range(BATCHES):
+ txn = fsiter.next()
+ 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(StopIteration, fsiter.next)
def checkTransactionalUndoIterator(self):
# check that data_txn set in iterator makes sense