[Zope-Checkins] CVS: Zope/lib/python/ZODB/tests - testZODB.py:1.9.2.1

Shane Hathaway shane@zope.com
Tue, 11 Feb 2003 12:01:10 -0500


Update of /cvs-repository/Zope/lib/python/ZODB/tests
In directory cvs.zope.org:/tmp/cvs-serv18227/tests

Modified Files:
      Tag: shane-conflict-handling-branch
	testZODB.py 
Log Message:
Added a test that demonstrates how ZODB allows bare "except" clauses to
generate inconsistent data.  The next task is to make ZODB raise a conflict
error when this happens.


=== Zope/lib/python/ZODB/tests/testZODB.py 1.9 => 1.9.2.1 ===
--- Zope/lib/python/ZODB/tests/testZODB.py:1.9	Fri Jan 17 12:23:16 2003
+++ Zope/lib/python/ZODB/tests/testZODB.py	Tue Feb 11 12:01:10 2003
@@ -174,6 +174,94 @@
             conn2.close()
 
 
+    def checkConflictingTransactionCommit(self):
+        # Verify Zope doesn't commit a transaction with conflicting data.
+        # In this test, the "real data" doesn't conflict, but the "index"
+        # does.  This simulates what happens in ZCatalog.
+        from ZODB.POSException import ConflictError
+        conn = self._db.open()
+        try:
+            root = conn.root()
+            # real_data contains { key -> value }
+            real_data = PersistentMapping()
+            root['real_data'] = real_data
+            # index contains { value -> [key,] }
+            index = PersistentMapping()
+            root['index'] = index
+
+            # Populate the data with a->false and b->true.
+            real_data['a'] = PersistentMapping({'indexed_value': 'false'})
+            real_data['b'] = PersistentMapping({'indexed_value': 'true'})
+            index['true'] = PersistentMapping({'b': 1})
+            index['false'] = PersistentMapping({'a': 1})
+            get_transaction().commit()
+
+            conn2 = self._db.open()
+            try:
+                # Open another connection.
+                conn2.setLocalTransaction()
+                conn2.getTransaction().begin()
+                root2 = conn2.root()
+                real_data2 = root2['real_data']
+                index2 = root2['index']
+
+                # Change the 'b' value in one transaction.
+                real_data['b']['indexed_value'] = 'false'
+                del index['true']['b']
+                index['false']['b'] = 1
+                get_transaction().commit()
+
+                # now try to change the 'a' value in a simultaneous
+                # transaction.  The data itself will not conflict,
+                # but the index will.
+                real_data2['a']['indexed_value'] = 'true'
+                
+                try:
+                    del index2['false']['a']
+                    index2['true']['a'] = 1
+                except:
+                    # This bare except clause is intentional.
+                    # The purpose of this test is to verify ZODB doesn't
+                    # commit conflicting transactions even though bare
+                    # except clauses are present in application code.
+                    pass
+
+                # Precondition: real_data2['a'] is still ready to commit.
+                self.assertEqual(real_data2['a']['indexed_value'], 'true')
+                self.assertEqual(real_data2['a']._p_changed, 1)
+
+                # Precondition: index2 values are *not* ready to commit.
+                # (In fact, they are probably ghosted.)
+                self.assertNotEqual(index2._p_changed, 1)
+                self.assertNotEqual(index2['false']._p_changed, 1)
+                self.assertNotEqual(index2['true']._p_changed, 1)
+
+                # Now, if ZODB commits this transaction, the index will be
+                # out of sync with the data.  Verify ZODB refuses to
+                # commit it.
+                self.assertRaises(ConflictError,
+                                  conn2.getTransaction().commit)
+                # Verify ZODB continues to refuse it.
+                self.assertRaises(ConflictError,
+                                  conn2.getTransaction().commit)
+
+                # Abort the transaction and verify the state of
+                # the database.  Both 'a' and 'b' have the value 'false'.
+                conn2.getTransaction().begin()
+                self.assertEqual(real_data2['a']['indexed_value'], 'false')
+                self.assertEqual(real_data2['b']['indexed_value'], 'false')
+                self.assertEqual(len(index2['false']), 2)
+                self.assertEqual(index2['false']['a'], 1)
+                self.assertEqual(index2['false']['b'], 1)
+                self.assertEqual(len(index2['true']), 0)
+            finally:
+                conn2.close()
+
+        finally:
+            conn.close()
+
+
+
 def test_suite():
     return unittest.makeSuite(ZODBTests, 'check')