[Zodb-checkins]
SVN: ZODB/branches/3.3/src/ZODB/tests/RecoveryStorage.py
Merge 25911 from trunk.
Tim Peters
tim.one at comcast.net
Fri Jun 18 16:08:15 EDT 2004
Log message for revision 25912:
Merge 25911 from trunk.
New test checkRestoreWithMultipleObjectsInUndoRedo from ZODB 3.2.2.
3.3 didn't have the bug, and this confirms it.
-=-
Modified: ZODB/branches/3.3/src/ZODB/tests/RecoveryStorage.py
===================================================================
--- ZODB/branches/3.3/src/ZODB/tests/RecoveryStorage.py 2004-06-18 20:04:14 UTC (rev 25911)
+++ ZODB/branches/3.3/src/ZODB/tests/RecoveryStorage.py 2004-06-18 20:06:56 UTC (rev 25912)
@@ -183,3 +183,106 @@
data, serial = self._dst.load(root._p_oid, '')
raises(KeyError, self._dst.load, obj1._p_oid, '')
raises(KeyError, self._dst.load, obj2._p_oid, '')
+
+ def checkRestoreWithMultipleObjectsInUndoRedo(self):
+ from ZODB.FileStorage import FileStorage
+
+ # Undo creates backpointers in (at least) FileStorage. ZODB 3.2.1
+ # FileStorage._data_find() had an off-by-8 error, neglecting to
+ # account for the size of the backpointer when searching a
+ # transaction with multiple data records. The results were
+ # unpredictable. For example, it could raise a Python exception
+ # due to passing a negative offset to file.seek(), or could
+ # claim that a transaction didn't have data for an oid despite
+ # that it actually did.
+ #
+ # The former failure mode was seen in real life, in a ZRS secondary
+ # doing recovery. On my box today, the second failure mode is
+ # what happens in this test (with an unpatched _data_find, of
+ # course). Note that the error can only "bite" if more than one
+ # data record is in a transaction, and the oid we're looking for
+ # follows at least one data record with a backpointer.
+ #
+ # Unfortunately, _data_find() is a low-level implementation detail,
+ # and this test does some horrid white-box abuse to test it.
+
+ is_filestorage = isinstance(self._storage, FileStorage)
+
+ db = DB(self._storage)
+ c = db.open()
+ r = c.root()
+
+ # Create some objects.
+ r["obj1"] = MinPO(1)
+ r["obj2"] = MinPO(1)
+ transaction.commit()
+
+ # Add x attributes to them.
+ r["obj1"].x = 'x1'
+ r["obj2"].x = 'x2'
+ transaction.commit()
+
+ r = db.open().root()
+ self.assertEquals(r["obj1"].x, 'x1')
+ self.assertEquals(r["obj2"].x, 'x2')
+
+ # Dirty tricks.
+ if is_filestorage:
+ obj1_oid = r["obj1"]._p_oid
+ obj2_oid = r["obj2"]._p_oid
+ # This will be the offset of the next transaction, which
+ # will contain two backpointers.
+ pos = self._storage.getSize()
+
+ # Undo the attribute creation.
+ info = self._storage.undoInfo()
+ tid = info[0]['id']
+ t = Transaction()
+ self._storage.tpc_begin(t)
+ oids = self._storage.undo(tid, t)
+ self._storage.tpc_vote(t)
+ self._storage.tpc_finish(t)
+
+ r = db.open().root()
+ self.assertRaises(AttributeError, getattr, r["obj1"], 'x')
+ self.assertRaises(AttributeError, getattr, r["obj2"], 'x')
+
+ if is_filestorage:
+ # _data_find should find data records for both objects in that
+ # transaction. Without the patch, the second assert failed
+ # (it claimed it couldn't find a data record for obj2) on my
+ # box, but other failure modes were possible.
+ self.assert_(self._storage._data_find(pos, obj1_oid, '') > 0)
+ self.assert_(self._storage._data_find(pos, obj2_oid, '') > 0)
+
+ # The offset of the next ("redo") transaction.
+ pos = self._storage.getSize()
+
+ # Undo the undo (restore the attributes).
+ info = self._storage.undoInfo()
+ tid = info[0]['id']
+ t = Transaction()
+ self._storage.tpc_begin(t)
+ oids = self._storage.undo(tid, t)
+ self._storage.tpc_vote(t)
+ self._storage.tpc_finish(t)
+
+ r = db.open().root()
+ self.assertEquals(r["obj1"].x, 'x1')
+ self.assertEquals(r["obj2"].x, 'x2')
+
+ if is_filestorage:
+ # Again _data_find should find both objects in this txn, and
+ # again the second assert failed on my box.
+ self.assert_(self._storage._data_find(pos, obj1_oid, '') > 0)
+ self.assert_(self._storage._data_find(pos, obj2_oid, '') > 0)
+
+ # Indirectly provoke .restore(). .restore in turn indirectly
+ # provokes _data_find too, but not usefully for the purposes of
+ # the specific bug this test aims at: copyTransactionsFrom() uses
+ # storage iterators that chase backpointers themselves, and
+ # return the data they point at instead. The result is that
+ # _data_find didn't actually see anything dangerous in this
+ # part of the test.
+ self._dst.copyTransactionsFrom(self._storage)
+ self.compare(self._storage, self._dst)
More information about the Zodb-checkins
mailing list