[Zodb-checkins] CVS: Zope3/src/zodb/btrees/tests - test_conflict.py:1.5

Tim Peters tim.one@comcast.net
Fri, 10 Jan 2003 16:34:55 -0500


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

Modified Files:
	test_conflict.py 
Log Message:
Fixed a leftover typo from the last round of general cleanup (one test
class was being run twice, and (consequently) another wasn't being run
at all).

Added an "accidental" test that blew up before the last checkin in
several nasty ways:  conflict resolution wasn't working at all in some
non-trivial cases.

Added what I hope is the bulk of a new test to provoke an insane BTree
out of conflict resolution in the presence of an unlucky bucket split.


=== Zope3/src/zodb/btrees/tests/test_conflict.py 1.4 => 1.5 ===
--- Zope3/src/zodb/btrees/tests/test_conflict.py:1.4	Thu Jan  9 13:16:17 2003
+++ Zope3/src/zodb/btrees/tests/test_conflict.py	Fri Jan 10 16:34:52 2003
@@ -26,7 +26,7 @@
     """ Tests common to all types: sets, buckets, and BTrees """
 
     storage = None
-    
+
     def tearDown(self):
         del self.t
         if self.storage is not None:
@@ -76,7 +76,7 @@
         r1 = self.db.open().root()
         r1["t"] = self.t
         get_transaction().commit()
-        
+
         r2 = self.db.open().root()
         copy = r2["t"]
         copy._p_activate()
@@ -88,7 +88,7 @@
 
         copy.update({3:4})
         get_transaction().commit()
-        
+
 
     def testMergeDelete(self):
         base, b1, b2, bm, e1, e2, items = self._setupConflict()
@@ -313,7 +313,7 @@
         try:
             merged = o1._p_resolveConflict(s1, s2, s3)
         except ConflictError, err:
-            pass 
+            pass
         else:
             assert 0, message
     else:
@@ -396,14 +396,139 @@
     def setUp(self):
         self.t = OIBucket()
 
+class NastyConfict(Base, TestCase):
+    def setUp(self):
+        self.t = OOBTree()
+
+    # This tests a problem that cropped up while trying to write
+    # testBucketSplitConflict (below):  conflict resolution wasn't
+    # working at all in non-trivial cases.  Symptoms varied from
+    # strange complaints about pickling (despite that the test isn't
+    # doing any *directly*), thru SystemErrors from Python and
+    # AssertionErrors inside the BTree code.
+    def testResolutionBlowsUp(self):
+        b = self.t
+        for i in range(0, 200, 4):
+            b[i] = i
+        # bucket 0 has 15 values: 0, 4 .. 56
+        # bucket 1 has 15 values: 60, 64 .. 116
+        # bucket 2 has 20 values: 120, 124 .. 196
+        state = b.__getstate__()
+        # Looks like:  ((bucket0, 60, bucket1, 120, bucket2), firstbucket)
+        # If these fail, the *preconditions* for running the test aren't
+        # satisfied -- the test itself hasn't been run yet.
+        self.assertEqual(len(state), 2)
+        self.assertEqual(len(state[0]), 5)
+        self.assertEqual(state[0][1], 60)
+        self.assertEqual(state[0][3], 120)
+
+        # Invoke conflict resolution by committing a transaction.
+        self.openDB()
+
+        r1 = self.db.open().root()
+        r1["t"] = self.t
+        get_transaction().commit()
+
+        r2 = self.db.open().root()
+        copy = r2["t"]
+        # Make sure all of copy is loaded.
+        list(copy.values())
+
+        self.assertEqual(self.t._p_serial, copy._p_serial)
+
+        self.t.update({1:2, 2:3})
+        get_transaction().commit()
+
+        copy.update({3:4})
+        get_transaction().commit()  # if this doesn't blow up
+        list(copy.values())         # and this doesn't either, then fine
+
+
+    # This tests a rare bug in bucket conflict resolution that went
+    # undiagnosed for years.  It's (almost necessarily) a white-box test,
+    # and sensutive to implementation details.
+    # XXX More needed here.
+    def testBucketSplitConflict(self):
+        b = self.t
+        for i in range(0, 200, 4):
+            b[i] = i
+        # bucket 0 has 15 values: 0, 4 .. 56
+        # bucket 1 has 15 values: 60, 64 .. 116
+        # bucket 2 has 20 values: 120, 124 .. 196
+        state = b.__getstate__()
+        # Looks like:  ((bucket0, 60, bucket1, 120, bucket2), firstbucket)
+        # If these fail, the *preconditions* for running the test aren't
+        # satisfied -- the test itself hasn't been run yet.
+        self.assertEqual(len(state), 2)
+        self.assertEqual(len(state[0]), 5)
+        self.assertEqual(state[0][1], 60)
+        self.assertEqual(state[0][3], 120)
+
+        # Invoke conflict resolution by committing a transaction.
+        self.openDB()
+
+        r1 = self.db.open().root()
+        r1["t"] = self.t
+        get_transaction().commit()
+
+        r2 = self.db.open().root()
+        copy = r2["t"]
+        # Make sure all of copy is loaded.
+        list(copy.values())
+
+        self.assertEqual(self.t._p_serial, copy._p_serial)
+
+        # In one transaction, add 16 new keys to bucket1, to force a bucket
+        # split.
+        b = self.t
+        numtoadd = 16
+        candidate = 60
+        while numtoadd:
+            if not b.has_key(candidate):
+                b[candidate] = candidate
+                numtoadd -= 1
+            candidate += 1
+        # bucket 0 has 15 values: 0, 4 .. 56
+        # bucket 1 has 15 values: 60, 61 .. 74
+        # bucket 2 has 16 values: [75, 76 .. 81] + [84, 88 ..116]
+        # bucket 3 has 20 values: 120, 124 .. 196
+        state = b.__getstate__()
+        # Looks like:  ((b0, 60, b1, 75, b2, 120, b3), firstbucket)
+        # The next block is still verifying preconditions.
+        self.assertEqual(len(state) , 2)
+        self.assertEqual(len(state[0]), 7)
+        self.assertEqual(state[0][1], 60)
+        self.assertEqual(state[0][3], 75)
+        self.assertEqual(state[0][5], 120)
+
+        get_transaction().commit()
+
+        # In the other transaction, add 3 values near the tail end of bucket1.
+        # This doesn't cause a split.
+        b = copy
+        for i in range(112, 116):
+            b[i] = i
+        # bucket 0 has 15 values: 0, 4 .. 56
+        # bucket 1 has 18 values: 60, 64 .. 112, 113, 114, 115, 116
+        # bucket 2 has 20 values: 120, 124 .. 196
+        state = b.__getstate__()
+        # Looks like:  ((bucket0, 60, bucket1, 120, bucket2), firstbucket)
+        # The next block is still verifying preconditions.
+        self.assertEqual(len(state), 2)
+        self.assertEqual(len(state[0]), 5)
+        self.assertEqual(state[0][1], 60)
+        self.assertEqual(state[0][3], 120)
+
+        get_transaction().commit()
+        # XXX The BTree "copy" is insane at this point (I hope).
+
+
 def test_suite():
     suite = TestSuite()
-    for klass in (TestIOBTrees, TestOOBTrees, TestOIBTrees, TestIIBTrees,
-                  TestIOSets, TestOOSets, TestIOSets, TestIISets,
-                  TestIOTreeSets, TestOOTreeSets, TestOITreeSets,
-                  TestIITreeSets,
-                  TestIOBuckets, TestOOBuckets, TestOIBuckets, TestIIBuckets):
-        s = makeSuite(klass)
-        suite.addTest(makeSuite(klass))
+    for k in (TestIOBTrees,   TestOOBTrees,   TestOIBTrees,   TestIIBTrees,
+              TestIOSets,     TestOOSets,     TestOISets,     TestIISets,
+              TestIOTreeSets, TestOOTreeSets, TestOITreeSets, TestIITreeSets,
+              TestIOBuckets,  TestOOBuckets,  TestOIBuckets,  TestIIBuckets,
+              NastyConfict):
+        suite.addTest(makeSuite(k))
     return suite
-