Commit a7a60ba9 authored by Tim Peters's avatar Tim Peters

BTrees and TreeSets have a new ._check() method. This does a thorough job

of verifying that BTree invariants are satisfied, raising AssertionError
if they're not.  Nothing calls this method by magic; it's for debugging.
parent 02e79f40
......@@ -12,7 +12,132 @@
****************************************************************************/
#define BTREETEMPLATE_C "$Id: BTreeTemplate.c,v 1.63 2002/06/19 20:20:07 jeremy Exp $\n"
#define BTREETEMPLATE_C "$Id: BTreeTemplate.c,v 1.64 2002/06/19 23:44:20 tim_one Exp $\n"
/* Sanity-check a BTree. This is a private helper for BTree_check. Return:
* -1 Error. If it's an internal inconsistency in the BTree,
* AssertionError is set.
* 0 No problem found.
*
* nextbucket is the bucket "one beyond the end" of the BTree; the last bucket
* directly reachable from following right child pointers *should* be linked
* to nextbucket (and this is checked).
*/
static int
BTree_check_inner(BTree *self, Bucket *nextbucket)
{
int i;
Bucket *bucketafter;
Sized *child;
char *errormsg = "internal error"; /* someone should have overriden */
Sized *activated_child = NULL;
int result = -1; /* until proved innocent */
#define CHECK(CONDITION, ERRORMSG) \
if (!(CONDITION)) { \
errormsg = (ERRORMSG); \
goto Error; \
}
PER_USE_OR_RETURN(self, -1);
CHECK(self->len >= 0, "BTree len < 0");
CHECK(self->len <= self->size, "BTree len > size");
if (self->len == 0) {
/* Empty BTree. */
CHECK(self->firstbucket == NULL,
"Empty BTree has non-NULL firstbucket");
result = 0;
goto Done;
}
/* Non-empty BTree. */
CHECK(self->firstbucket != NULL, "Non-empty BTree has NULL firstbucket");
CHECK(self->firstbucket->ob_refcnt >= 2,
"Non-empty BTree firstbucket has refcount < 2");
for (i = 0; i < self->len; ++i) {
CHECK(self->data[i].child != NULL, "BTree has NULL child");
}
if (SameType_Check(self, self->data[0].child)) {
/* Our children are also BTrees. */
child = self->data[0].child;
UNLESS (PER_USE(child)) goto Done;
activated_child = child;
CHECK(self->firstbucket == BTREE(child)->firstbucket,
"BTree has firstbucket different than "
"its first child's firstbucket");
PER_ALLOW_DEACTIVATION(child);
activated_child = NULL;
for (i = 0; i < self->len; ++i) {
child = self->data[i].child;
CHECK(SameType_Check(self, child),
"BTree children have different types");
if (i == self->len - 1)
bucketafter = nextbucket;
else {
BTree *child2 = BTREE(self->data[i+1].child);
UNLESS (PER_USE(child2)) goto Done;
bucketafter = child2->firstbucket;
PER_ALLOW_DEACTIVATION(child2);
}
if (BTree_check_inner(BTREE(child), bucketafter) < 0) goto Done;
}
}
else {
/* Our children are buckets. */
CHECK(self->firstbucket == BUCKET(self->data[0].child),
"Bottom-level BTree node has inconsistent firstbucket belief");
for (i = 0; i < self->len; ++i) {
child = self->data[i].child;
UNLESS (PER_USE(child)) goto Done;
activated_child = child;
CHECK(!SameType_Check(self, child),
"BTree children have different types");
CHECK(child->len >= 1, "Bucket length < 1"); /* no empty buckets! */
CHECK(child->len <= child->size, "Bucket len > size");
CHECK(child->ob_refcnt >= 2, "Bucket has refcount < 2");
if (i == self->len - 1)
bucketafter = nextbucket;
else
bucketafter = BUCKET(self->data[i+1].child);
CHECK(BUCKET(child)->next == bucketafter,
"Bucket next pointer is damaged");
PER_ALLOW_DEACTIVATION(child);
activated_child = NULL;
}
}
result = 0;
goto Done;
Error:
PyErr_SetString(PyExc_AssertionError, errormsg);
result = -1;
Done:
/* No point updating access time -- this isn't a "real" use. */
PER_ALLOW_DEACTIVATION(self);
if (activated_child) {
PER_ALLOW_DEACTIVATION(activated_child);
}
return result;
#undef CHECK
}
/* Sanity-check a BTree. This is the ._check() method. Return:
* NULL Error. If it's an internal inconsistency in the BTree,
* AssertionError is set.
* Py_None No problem found.
*/
static PyObject*
BTree_check(BTree *self, PyObject *args)
{
PyObject *result = NULL;
int i = BTree_check_inner(self, NULL);
if (i >= 0) {
result = Py_None;
Py_INCREF(result);
}
return result;
}
/*
** _BTree_get
......@@ -1460,6 +1585,8 @@ static struct PyMethodDef BTree_methods[] = {
"update(collection) -- Add the items from the given collection"},
{"__init__", (PyCFunction) Mapping_update, METH_VARARGS,
"__init__(collection) -- Initialize with items from the given collection"},
{"_check", (PyCFunction) BTree_check, METH_VARARGS,
"Perform sanity check on BTree, and raise exception if flawed."},
#ifdef PERSISTENT
{"_p_resolveConflict", (PyCFunction) BTree__p_resolveConflict, METH_VARARGS,
"_p_resolveConflict() -- Reinitialize from a newly created copy"},
......
......@@ -12,7 +12,7 @@
****************************************************************************/
#define TREESETTEMPLATE_C "$Id: TreeSetTemplate.c,v 1.13 2002/02/21 21:41:17 jeremy Exp $\n"
#define TREESETTEMPLATE_C "$Id: TreeSetTemplate.c,v 1.14 2002/06/19 23:44:20 tim_one Exp $\n"
static PyObject *
TreeSet_insert(BTree *self, PyObject *args)
......@@ -114,6 +114,8 @@ static struct PyMethodDef TreeSet_methods[] = {
"__init__(seq) -- Initialize with the items from the given sequence"},
{"remove", (PyCFunction)TreeSet_remove, METH_VARARGS,
"remove(id) -- Remove a key from the set"},
{"_check", (PyCFunction) BTree_check, METH_VARARGS,
"Perform sanity check on TreeSet, and raise exception if flawed."},
#ifdef PERSISTENT
{"_p_resolveConflict", (PyCFunction) BTree__p_resolveConflict, METH_VARARGS,
"_p_resolveConflict() -- Reinitialize from a newly created copy"},
......
......@@ -499,6 +499,11 @@ class BucketTests(MappingBase):
class BTreeTests(MappingBase):
""" Tests common to all BTrees """
def tearDown(self):
self.t._check()
MappingBase.tearDown(self)
def testDeleteNoChildrenWorks(self):
self.t[5] = 6
self.t[2] = 10
......@@ -953,6 +958,7 @@ class TestIITreeSets(NormalSetTests, TestCase):
# One more.
t = ts()
t.__setstate__(((tree13, 4, tree5711), bucket1))
t._check()
return t, [1, 3, 5, 7, 11]
def testDegenerateBasicOps(self):
......@@ -1013,6 +1019,7 @@ class TestIITreeSets(NormalSetTests, TestCase):
for key in oneperm:
t.remove(key)
keys.remove(key)
t._check()
self._checkRanges(t, keys)
# We removed all the keys, so the tree should be empty now.
self.assertEqual(t.__getstate__(), None)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment