]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Add pre-order and post-order rbtree iterators. (#3945)
authorJames Jones <jejones3141@gmail.com>
Fri, 19 Feb 2021 12:32:08 +0000 (06:32 -0600)
committerGitHub <noreply@github.com>
Fri, 19 Feb 2021 12:32:08 +0000 (12:32 +0000)
The test we have is based on static test data with the values
appropriate for pre and post iterators determined by hand. We
can generate additional test cases as desired.

src/lib/util/rbtree.c
src/lib/util/rbtree.h
src/lib/util/rbtree_tests.c

index 3a4458eb2822e88e4236a8b024aa1695bddf1bd8..8932aa0a3f9e697b865efb3981ae253e6035b8a7 100644 (file)
@@ -946,6 +946,182 @@ void *rbtree_iter_next_inorder(fr_rb_tree_iter_t *iter)
        return x->data;
 }
 
+/** Initialise a pre-order iterator
+ *
+ * @note If iteration ends early because of a loop condition #rbtree_iter_done must be called.
+ *
+ * @param[out] iter to initialise.
+ * @param[in] tree to iterate over.
+ * @return
+ *     - The first node.  Mutex will be held.
+ *     - NULL if the tree is empty.
+ */
+void *rbtree_iter_init_preorder(fr_rb_tree_iter_t *iter, rbtree_t *tree)
+{
+       fr_rb_node_t *x = tree->root;
+
+       if (x == NIL) return NULL;
+
+       if (tree->lock) pthread_mutex_lock(&tree->mutex);
+
+       /*
+        *      First, the root.
+        */
+       *iter = (fr_rb_tree_iter_t){
+               .tree = tree,
+               .node = x
+       };
+
+       return x->data;
+}
+
+/** Return the next node
+ *
+ * @note Will unlock the tree if no more elements remain.
+ *
+ * @param[in] iter     previously initialised with #rbtree_iter_init
+ * @return
+ *     - The next node.
+ *     - NULL if no more nodes remain.
+ */
+void *rbtree_iter_next_preorder(fr_rb_tree_iter_t *iter)
+{
+       fr_rb_node_t *x = iter->node, *y;
+
+       /*
+        *      Catch callers repeatedly calling iterator
+        *      at the end.
+        */
+       if (unlikely(iter->node == NIL)) return NULL;
+
+       /*
+        * Next is a child of the just-returned node, if it has one.
+        * (Left child first.)
+        */
+       if (x->left != NIL) {
+               x = x->left;
+               iter->node = x;
+               return x->data;
+       }
+       if (x->right != NIL) {
+               x = x->right;
+               iter->node = x;
+               return x->data;
+       }
+
+       /*
+        * Otherwise, the nearest ancestor's unreturned right
+        * child, if one exists.
+        */
+       for (; (y = x->parent) != NIL; x = y) {
+               if (y->right != NIL && y->right != x) {
+                       x = y->right;
+                       iter->node = x;
+                       return x->data;
+               }
+       }
+
+       /*
+        * None of the above? We're done.
+        */
+       iter->node = NIL;
+       if (iter->tree->lock)
+               pthread_mutex_unlock(&iter->tree->mutex);
+       return NULL;
+}
+
+/** Initialise a pre-order iterator
+ *
+ * @note If iteration ends early because of a loop condition #rbtree_iter_done must be called.
+ *
+ * @param[out] iter to initialise.
+ * @param[in] tree to iterate over.
+ * @return
+ *     - The first node.  Mutex will be held.
+ *     - NULL if the tree is empty.
+ */
+void *rbtree_iter_init_postorder(fr_rb_tree_iter_t *iter, rbtree_t *tree)
+{
+       fr_rb_node_t *x = tree->root;
+
+       if (x == NIL) return NULL;
+
+       if (tree->lock) pthread_mutex_lock(&tree->mutex);
+
+       /*
+        *      First: the deepest leaf to the left (jogging to the
+        *      right if there's a right child but no left).
+        */
+       for (;;) {
+               for (; x->left != NIL; x = x->left) ;
+               if (x->right == NIL) break;
+               x = x->right;
+       }
+
+       *iter = (fr_rb_tree_iter_t){
+               .tree = tree,
+               .node = x
+       };
+
+       return x->data;
+}
+
+/** Return the next node
+ *
+ * @note Will unlock the tree if no more elements remain.
+ *
+ * @param[in] iter     previously initialised with #rbtree_iter_init
+ * @return
+ *     - The next node.
+ *     - NULL if no more nodes remain.
+ */
+void *rbtree_iter_next_postorder(fr_rb_tree_iter_t *iter)
+{
+       fr_rb_node_t *x = iter->node, *y;
+
+       /*
+        *      Catch callers repeatedly calling iterator
+        *      at the end.
+        */
+       if (unlikely(iter->node == NIL)) return NULL;
+
+       /*
+        *      This is postorder, so a just-returned node's
+        *      descendants have all been returned. If there
+        *      is another node, it's an ancestor or one of
+        *      its not-yet-returned descendants...but if
+        *      we're at the root, we're done.
+        */
+       y = x->parent;
+       if (y == NIL) {
+               iter->node = NIL;
+               if (iter->tree->lock) pthread_mutex_unlock(&iter->tree->mutex);
+               return NULL;
+       }
+
+       /*
+        * Return the parent if it has no right child, or it has one but
+        * it's been returned.
+        */
+       if (y->right == NIL || y->right == x) {
+               iter->node = y;
+               return y->data;
+       }
+
+       /*
+        * Otherwise, it's as if we're starting over with the right child.
+        */
+       x = y->right;
+       for (;;) {
+               for (; x->left != NIL; x = x->left) ;
+               if (x->right == NIL) break;
+               x = x->right;
+       }
+
+       iter->node = x;
+       return x->data;
+}
+
 /** Explicitly unlock the tree
  *
  * @note Must be called if iterating over the tree ends early.
index de7d5bde1b8607802a8987532fb293f8b2f31fee..9aa27f5dd7890a2fc3aa7f3bac60bf06e97e7d30 100644 (file)
@@ -177,6 +177,14 @@ void               *rbtree_iter_init_inorder(fr_rb_tree_iter_t *iter, rbtree_t *tree) CC_HINT
 
 void           *rbtree_iter_next_inorder(fr_rb_tree_iter_t *iter) CC_HINT(nonnull);
 
+void           *rbtree_iter_init_preorder(fr_rb_tree_iter_t *iter, rbtree_t *tree) CC_HINT(nonnull);
+
+void           *rbtree_iter_next_preorder(fr_rb_tree_iter_t *iter) CC_HINT(nonnull);
+
+void           *rbtree_iter_init_postorder(fr_rb_tree_iter_t *iter, rbtree_t *tree) CC_HINT(nonnull);
+
+void           *rbtree_iter_next_postorder(fr_rb_tree_iter_t *iter) CC_HINT(nonnull);
+
 void           rbtree_iter_done(fr_rb_tree_iter_t *iter) CC_HINT(nonnull);
 
 #ifdef __cplusplus
index edbd33306ede4238ce1d80f598c6876746e73eac..fd8803f51ff769950057d6dffbe38bd9e0e7f11e 100644 (file)
@@ -40,7 +40,7 @@ static int fr_rb_test_cmp(void const *one, void const *two)
        return CMP(a->num, b->num);
 }
 
-static void test_rbtree_iter(void)
+static void test_rbtree_iter_inorder(void)
 {
        rbtree_t                *t;
        fr_rb_test_node_t       sorted[MAXSIZE];
@@ -48,6 +48,7 @@ static void test_rbtree_iter(void)
        size_t                  n, i;
        fr_rb_tree_iter_t       iter;
 
+       TEST_CASE("in-order iterator");
        t = rbtree_alloc(NULL, fr_rb_test_node_t, node, fr_rb_test_cmp, NULL, RBTREE_FLAG_LOCK);
        TEST_CHECK(t != NULL);
 
@@ -80,8 +81,87 @@ static void test_rbtree_iter(void)
        talloc_free(t);
 }
 
+/*
+ *     There's no natural test for pre- and post-order traversal
+ *     as there is for in-order, so we must content ourselves
+ *     with static test data.
+ */
+static uint32_t        pre_post_input[] = {0, 15, 256, 49, 3, 8192, 144, 4, 4096, 25194};
+static uint32_t        pre_output[] = {15, 3, 0, 4, 256, 49, 144, 8192, 4096, 25194};
+static uint32_t        post_output[] = {0, 4, 3, 144, 49, 4096, 25194, 8192, 256, 15};
+
+static void test_rbtree_iter_preorder(void)
+{
+       rbtree_t                *t;
+       fr_rb_test_node_t       *p;
+       size_t                  i;
+       fr_rb_tree_iter_t       iter;
+
+       TEST_CASE("pre-order iterator");
+       /*
+        *      Build a tree from pre_post_input.
+        */
+       t = rbtree_alloc(NULL, fr_rb_test_node_t, node, fr_rb_test_cmp, NULL, RBTREE_FLAG_LOCK);
+       TEST_CHECK(t != NULL);
+
+       for (i = 0; i < sizeof(pre_post_input) / sizeof(uint32_t); i++) {
+               p = talloc(t, fr_rb_test_node_t);
+               p->num = pre_post_input[i];
+               rbtree_insert(t, p);
+       }
+
+       for (p = rbtree_iter_init_preorder(&iter, t), i = 0;
+            p;
+            p = rbtree_iter_next_preorder(&iter), i++) {
+               TEST_MSG("Checking pre_output[%zu] = %u vs n = %u", i, pre_output[i], p->num);
+               TEST_CHECK(pre_output[i] == p->num);
+               TEST_CHECK(pthread_mutex_trylock(&t->mutex) != 0);      /* Lock still held */
+       }
+
+       TEST_CHECK(pthread_mutex_trylock(&t->mutex) == 0);              /* Lock released */
+       pthread_mutex_unlock(&t->mutex);
+
+       talloc_free(t);
+}
+
+static void test_rbtree_iter_postorder(void)
+{
+       rbtree_t                *t;
+       fr_rb_test_node_t       *p;
+       size_t                  i;
+       fr_rb_tree_iter_t       iter;
+
+       TEST_CASE("post-order iterator");
+       /*
+        *      Build a tree from pre_post_input.
+        */
+       t = rbtree_alloc(NULL, fr_rb_test_node_t, node, fr_rb_test_cmp, NULL, RBTREE_FLAG_LOCK);
+       TEST_CHECK(t != NULL);
+
+       for (i = 0; i < sizeof(pre_post_input) / sizeof(uint32_t); i++) {
+               p = talloc(t, fr_rb_test_node_t);
+               p->num = pre_post_input[i];
+               rbtree_insert(t, p);
+       }
+
+       for (p = rbtree_iter_init_postorder(&iter, t), i = 0;
+            p;
+            p = rbtree_iter_next_postorder(&iter), i++) {
+               TEST_MSG("Checking post_output[%zu] s = %u vs n = %u", i, post_output[i], p->num);
+               TEST_CHECK(post_output[i] == p->num);
+               TEST_CHECK(pthread_mutex_trylock(&t->mutex) != 0);      /* Lock still held */
+       }
+
+       TEST_CHECK(pthread_mutex_trylock(&t->mutex) == 0);              /* Lock released */
+       pthread_mutex_unlock(&t->mutex);
+
+       talloc_free(t);
+}
+
 TEST_LIST = {
-       { "rbtree_iter",            test_rbtree_iter },
+       { "rbtree_iter_inorder",            test_rbtree_iter_inorder },
+       { "rbtree_iter_preorder",           test_rbtree_iter_preorder },
+       { "rbtree_iter_postorder",          test_rbtree_iter_postorder },
 
        { NULL }
 };