[Zope-Checkins] CVS: ZODB3/ZODB/tests - VersionStorage.py:1.17.4.2 TransactionalUndoStorage.py:1.22.2.3 StorageTestBase.py:1.17.8.4 PackableStorage.py:1.13.8.1

Jeremy Hylton jeremy@zope.com
Tue, 13 May 2003 14:53:46 -0400


Update of /cvs-repository/ZODB3/ZODB/tests
In directory cvs.zope.org:/tmp/cvs-serv27185/ZODB/tests

Modified Files:
      Tag: ZODB3-3_1-branch
	VersionStorage.py TransactionalUndoStorage.py 
	StorageTestBase.py PackableStorage.py 
Log Message:
Backport new pack tests from head.


=== ZODB3/ZODB/tests/VersionStorage.py 1.17.4.1 => 1.17.4.2 ===
--- ZODB3/ZODB/tests/VersionStorage.py:1.17.4.1	Fri Nov 15 12:18:55 2002
+++ ZODB3/ZODB/tests/VersionStorage.py	Tue May 13 14:53:45 2003
@@ -1,3 +1,16 @@
+##############################################################################
+#
+# 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.
@@ -7,10 +20,14 @@
 # They were introduced when Jim reviewed the original version of the
 # code.  Barry and Jeremy didn't understand versions then.
 
+import time
+
 from ZODB import POSException
+from ZODB.referencesf import referencesf
 from ZODB.Transaction import Transaction
 from ZODB.tests.MinPO import MinPO
-from ZODB.tests.StorageTestBase import zodb_unpickle
+from ZODB.tests.StorageTestBase import zodb_unpickle, snooze
+from ZODB import DB
 
 class VersionStorage:
 
@@ -37,7 +54,7 @@
         # use repr() to avoid getting binary data in a traceback on error
         self.assertEqual(`revid1`, `revid3`)
         self.assertNotEqual(`revid2`, `revid3`)
-    
+
     def checkVersionedStoreAndLoad(self):
         eq = self.assertEqual
         # Store a couple of non-version revisions of the object
@@ -350,3 +367,164 @@
                           self._storage.load, oid, '')
         self.assertRaises(KeyError,
                           self._storage.load, oid, 'two')
+
+    def checkCreateObjectInVersionWithAbort(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])
+
+    def checkPackVersions(self):
+        db = DB(self._storage)
+        cn = db.open(version="testversion")
+        root = cn.root()
+
+        obj = root["obj"] = MinPO("obj")
+        root["obj2"] = MinPO("obj2")
+        txn = get_transaction()
+        txn.note("create 2 objs in version")
+        txn.commit()
+
+        obj.value = "77"
+        txn = get_transaction()
+        txn.note("modify obj in version")
+        txn.commit()
+
+        # undo the modification to generate a mix of backpointers
+        # and versions for pack to chase
+        info = db.undoInfo()
+        db.undo(info[0]["id"])
+        txn = get_transaction()
+        txn.note("undo modification")
+        txn.commit()
+
+        snooze()
+        self._storage.pack(time.time(), referencesf)
+
+        db.commitVersion("testversion")
+        txn = get_transaction()
+        txn.note("commit version")
+        txn.commit()
+
+        cn = db.open()
+        root = cn.root()
+        root["obj"] = "no version"
+
+        txn = get_transaction()
+        txn.note("modify obj")
+        txn.commit()
+
+        self._storage.pack(time.time(), referencesf)
+
+    def checkPackVersionsInPast(self):
+        db = DB(self._storage)
+        cn = db.open(version="testversion")
+        root = cn.root()
+
+        obj = root["obj"] = MinPO("obj")
+        root["obj2"] = MinPO("obj2")
+        txn = get_transaction()
+        txn.note("create 2 objs in version")
+        txn.commit()
+
+        obj.value = "77"
+        txn = get_transaction()
+        txn.note("modify obj in version")
+        txn.commit()
+
+        t0 = time.time()
+        snooze()
+
+        # undo the modification to generate a mix of backpointers
+        # and versions for pack to chase
+        info = db.undoInfo()
+        db.undo(info[0]["id"])
+        txn = get_transaction()
+        txn.note("undo modification")
+        txn.commit()
+
+        self._storage.pack(t0, referencesf)
+
+        db.commitVersion("testversion")
+        txn = get_transaction()
+        txn.note("commit version")
+        txn.commit()
+
+        cn = db.open()
+        root = cn.root()
+        root["obj"] = "no version"
+
+        txn = get_transaction()
+        txn.note("modify obj")
+        txn.commit()
+
+        self._storage.pack(time.time(), referencesf)
+
+    def checkPackVersionReachable(self):
+        db = DB(self._storage)
+        cn = db.open()
+        root = cn.root()
+
+        names = "a", "b", "c"
+
+        for name in names:
+            root[name] = MinPO(name)
+            get_transaction().commit()
+
+        for name in names:
+            cn2 = db.open(version=name)
+            rt2 = cn2.root()
+            obj = rt2[name]
+            obj.value = MinPO("version")
+            get_transaction().commit()
+            cn2.close()
+
+        root["d"] = MinPO("d")
+        get_transaction().commit()
+
+        self._storage.pack(time.time(), referencesf)
+        cn.sync()
+        cn._cache.clear()
+        
+        # make sure all the non-version data is there
+        for name, obj in root.items():
+            self.assertEqual(name, obj.value)
+
+        # make sure all the version-data is there,
+        # and create a new revision in the version
+        for name in names:
+            cn2 = db.open(version=name)
+            rt2 = cn2.root()
+            obj = rt2[name].value
+            self.assertEqual(obj.value, "version")
+            obj.value = "still version"
+            get_transaction().commit()
+            cn2.close()
+
+        db.abortVersion("b")
+        txn = get_transaction()
+        txn.note("abort version b")
+        txn.commit()
+
+        t = time.time()
+        snooze()
+        
+        L = db.undoInfo()
+        db.undo(L[0]["id"])
+        txn = get_transaction()
+        txn.note("undo abort")
+        txn.commit()
+        
+        self._storage.pack(t, referencesf)
+
+        cn2 = db.open(version="b")
+        rt2 = cn2.root()
+        self.assertEqual(rt2["b"].value.value, "still version")
+            


=== ZODB3/ZODB/tests/TransactionalUndoStorage.py 1.22.2.2 => 1.22.2.3 ===
--- ZODB3/ZODB/tests/TransactionalUndoStorage.py:1.22.2.2	Thu Oct 24 12:28:08 2002
+++ ZODB3/ZODB/tests/TransactionalUndoStorage.py	Tue May 13 14:53:45 2003
@@ -1,3 +1,16 @@
+##############################################################################
+#
+# 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.
@@ -14,13 +27,21 @@
 
 from Persistence import Persistent
 from ZODB.tests.MinPO import MinPO
-from ZODB.tests.StorageTestBase import zodb_pickle, zodb_unpickle
+from ZODB.tests.StorageTestBase import zodb_pickle, zodb_unpickle, snooze
 
 ZERO = '\0'*8
 
 class C(Persistent):
     pass
 
+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):
@@ -453,14 +474,8 @@
         # 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()
+        packtime = time.time()
+        snooze()                # time.time() now distinct from packtime
         revid2 = self._dostore(oid, revid=revid1, data=MinPO(52))
         revid3 = self._dostore(oid, revid=revid2, data=MinPO(53))
         # Now get the undo log
@@ -542,6 +557,98 @@
         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 checkTransactionalUndoIterator(self):
         # check that data_txn set in iterator makes sense


=== ZODB3/ZODB/tests/StorageTestBase.py 1.17.8.3 => 1.17.8.4 ===
--- ZODB3/ZODB/tests/StorageTestBase.py:1.17.8.3	Wed Jan 22 11:58:15 2003
+++ ZODB3/ZODB/tests/StorageTestBase.py	Tue May 13 14:53:45 2003
@@ -11,6 +11,7 @@
 import pickle
 import string
 import sys
+import time
 import types
 import unittest
 from cPickle import Pickler, Unpickler
@@ -70,6 +71,15 @@
     inst.__setstate__(state)
     return inst
 
+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 handle_all_serials(oid, *args):
     """Return dict of oid to serialno from store() and tpc_vote().
 


=== ZODB3/ZODB/tests/PackableStorage.py 1.13 => 1.13.8.1 ===
--- ZODB3/ZODB/tests/PackableStorage.py:1.13	Wed Aug 14 18:07:09 2002
+++ ZODB3/ZODB/tests/PackableStorage.py	Tue May 13 14:53:45 2003
@@ -1,3 +1,16 @@
+##############################################################################
+#
+# 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:
@@ -13,6 +26,8 @@
     from StringIO import StringIO
 
 import time
+from ZODB import DB
+from Persistence import Persistent
 from ZODB.referencesf import referencesf
 
 
@@ -42,6 +57,9 @@
         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
@@ -159,8 +177,10 @@
         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(), referencesf)
+        now = packtime = time.time()
+        while packtime <= now:
+            packtime = time.time()
+        self._storage.pack(packtime, referencesf)
         # 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)
@@ -210,8 +230,10 @@
         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(), referencesf)
+        now = packtime = time.time()
+        while packtime <= now:
+            packtime = time.time()
+        self._storage.pack(packtime, referencesf)
         # Make sure the revisions are gone, but that object zero and revision
         # 3 are still there and correct
         data, revid = self._storage.load(ZERO, '')
@@ -287,8 +309,10 @@
         # 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(), referencesf)
+        now = packtime = time.time()
+        while packtime <= now:
+            packtime = time.time()
+        self._storage.pack(packtime, referencesf)
         # 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, '')
@@ -312,3 +336,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, referencesf)
+
+        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)