diff --git a/.gitignore b/.gitignore index c424dc3..e80d19d 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ coverage.xml *.egg dist .eggs/ +.dir-locals.el diff --git a/BTrees/_base.py b/BTrees/_base.py index 032ea62..1a6b838 100644 --- a/BTrees/_base.py +++ b/BTrees/_base.py @@ -883,11 +883,16 @@ def _set(self, key, value=None, ifunset=False): max_size = self.max_leaf_size if child.size > max_size: self._grow(child, index) - elif (grew is not None and - child.__class__ is self._bucket_type and - len(data) == 1 and - child._p_oid is None - ): + + # If a BTree contains only a single bucket, BTree.__getstate__() + # includes the bucket's entire state, and the bucket doesn't get + # an oid of its own. So if we have a single oid-less bucket that + # changed, it's *our* oid that should be marked as changed -- the + # bucket doesn't have one. + if (grew is not None and + child.__class__ is self._bucket_type and + len(data) == 1 and + child._p_oid is None): self._p_changed = 1 return result diff --git a/BTrees/tests/common.py b/BTrees/tests/common.py index b689901..28eb33a 100644 --- a/BTrees/tests/common.py +++ b/BTrees/tests/common.py @@ -768,7 +768,6 @@ def testPop(self): # Too many arguments. self.assertRaises(TypeError, t.pop, 1, 2, 3) - class BTreeTests(MappingBase): # Tests common to all BTrees @@ -1091,6 +1090,56 @@ def testDamagedIterator(self): self.assertEqual(str(v), str(k[0])) self._checkIt(t) + def testAddTwoSetsChanged(self): + # A bug in the BTree Python implementation once + # caused adding a second item to a tree to fail + # to set _p_changed (adding the first item sets it because + # the _firstbucket gets set, but the second item only grew the + # existing bucket) + t = self._makeOne() + # Note that for the property to actually hold, we have to fake a + # _p_jar and _p_oid + t._p_oid = b'\0\0\0\0\0' + class Jar(object): + def __init__(self): + self._cache = self + self.registered = None + + def mru(self, arg): + pass + def readCurrent(self, arg): + pass + def register(self, arg): + self.registered = arg + + t._p_jar = Jar() + t[1] = 3 + # reset these, setting _firstbucket triggered a change + t._p_changed = False + t._p_jar.registered = None + t[2] = 4 + self.assertTrue(t._p_changed) + self.assertEqual(t, t._p_jar.registered) + + # Setting the same key to a different value also triggers a change + t._p_changed = False + t._p_jar.registered = None + t[2] = 5 + self.assertTrue(t._p_changed) + self.assertEqual(t, t._p_jar.registered) + + # Likewise with only a single value + t = self._makeOne() + t._p_oid = b'\0\0\0\0\0' + t._p_jar = Jar() + t[1] = 3 + # reset these, setting _firstbucket triggered a change + t._p_changed = False + t._p_jar.registered = None + + t[1] = 6 + self.assertTrue(t._p_changed) + self.assertEqual(t, t._p_jar.registered) class NormalSetTests(Base): # Test common to all set types diff --git a/CHANGES.rst b/CHANGES.rst index a9cc90e..454632a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,9 @@ - Suppress testing 64-bit values in OLBTrees on 32 bit machines. See: https://github.com/zopefoundation/BTrees/issues/9 +- Fix _p_changed for small pure-Python BTrees. + See https://github.com/zopefoundation/BTrees/issues/11 + 4.1.1 (2014-12-27) ------------------