Skip to content

Fix rbtree foreach traversal#9

Merged
Jiu-xiao merged 1 commit into
masterfrom
fix/rbtree-foreach
Apr 20, 2026
Merged

Fix rbtree foreach traversal#9
Jiu-xiao merged 1 commit into
masterfrom
fix/rbtree-foreach

Conversation

@Jiu-xiao

@Jiu-xiao Jiu-xiao commented Apr 20, 2026

Copy link
Copy Markdown
Owner

Summary by Sourcery

Fix red-black tree foreach traversal behavior and add a regression test for single-node iteration.

Bug Fixes:

  • Correct rbtree foreach recursion to traverse left subtree, current node, and right subtree while respecting early termination from the callback.

Tests:

  • Add a unit test to verify that rbtree foreach over a single node invokes the callback exactly once.

Fixes #8

@sourcery-ai

sourcery-ai Bot commented Apr 20, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds targeted test coverage for om_rbtree_foreach traversal and fixes the internal recursive implementation to correctly traverse all nodes and honor early-termination semantics.

Flow diagram for updated _om_rbtree_foreach traversal and early termination

flowchart TD
    A_start["_om_rbtree_foreach(node, fun, arg)"] --> B_isNull{"node == NULL?"}
    B_isNull -- "yes" --> B_retTrue["return true"]
    B_isNull -- "no" --> C_callLeft["call _om_rbtree_foreach(node.left, fun, arg)"]
    C_callLeft --> C_leftResult{"left returned true?"}
    C_leftResult -- "no" --> C_retFalse["return false"]
    C_leftResult -- "yes" --> D_callFun["call fun(node, arg)"]
    D_callFun --> D_funResult{"fun returned true?"}
    D_funResult -- "no" --> D_retFalse["return false"]
    D_funResult -- "yes" --> E_callRight["call _om_rbtree_foreach(node.right, fun, arg)"]
    E_callRight --> E_retRight["return value from right subtree"]
Loading

File-Level Changes

Change Details Files
Add a focused unit test to validate single-node rbtree foreach traversal behavior and register it in the test suite.
  • Introduce a helper callback that asserts the visited node key and increments an invocation counter when used with rbtree foreach.
  • Add a new _RBT_FOREACH test that builds a single-node rbtree, runs om_rbtree_foreach, and asserts the callback runs exactly once.
  • Register the new _RBT_FOREACH test case in the existing red-black tree test group so it runs with the suite.
test/om_test_base.c
Correct om_rbtree_foreach recursive traversal logic to handle null nodes and properly propagate early termination from the callback.
  • Ensure the internal foreach helper returns true immediately when invoked with a null node, making it a no-op base case instead of a failure.
  • Change the recursion order to always traverse the left subtree first, then invoke the user callback, then traverse the right subtree.
  • Propagate false returns from both the left subtree traversal and the callback to stop traversal early, while continuing to traverse right only when both succeed.
src/comp/om_rbt.c

Possibly linked issues

  • #_om_rbtree_foreach 错误: The PR fixes _om_rbtree_foreach to return true on NULL and use proper in-order traversal, matching the issue report.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@Jiu-xiao Jiu-xiao changed the title Fix rbtree foreach traversal Fix rbtree foreach traversal #8 Apr 20, 2026
@Jiu-xiao Jiu-xiao changed the title Fix rbtree foreach traversal #8 Fix rbtree foreach traversal Apr 20, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="test/om_test_base.c" line_range="138-136" />
<code_context>
 }
 END_TEST

+START_TEST(_RBT_FOREACH) {
+  RBT_ROOT(rbt);
+  om_rbt_node_t node = {.key = "B"};
+  int count = 0;
+
+  om_rbtree_insert(&rbt, &node);
+  om_rbtree_foreach(&rbt, _rbt_foreach_once, &count);
+
+  ck_assert_msg(count == 1, "单节点遍历应执行1次回调,实际执行%d次", count);
+}
+END_TEST
+
 Suite* make_om_base_suite(void) {
</code_context>
<issue_to_address>
**suggestion (testing):** Add tests to cover multi-node traversals and verify full in-order behavior, not just the single-node case.

Given the traversal bug fixed in this PR, this test should exercise more than the single-node case. Please build a small multi-node tree (e.g., three nodes that force visiting both left and right branches) and assert that all nodes are visited and that the visit sequence is strictly in-order by key (for example, inserting keys "A", "B", "C" and collecting the visited keys in the callback to check the exact order).
</issue_to_address>

### Comment 2
<location path="test/om_test_base.c" line_range="144" />
<code_context>
+  int count = 0;
+
+  om_rbtree_insert(&rbt, &node);
+  om_rbtree_foreach(&rbt, _rbt_foreach_once, &count);
+
+  ck_assert_msg(count == 1, "单节点遍历应执行1次回调,实际执行%d次", count);
</code_context>
<issue_to_address>
**suggestion (testing):** Add a test that verifies early termination when the callback returns false.

Since the tests only use callbacks that return `true`, they don't verify the new short-circuit behavior. Please add a test where the callback returns `false` after visiting the first (or second) node, and assert both that traversal stops invoking the callback after that node (e.g., via a call counter or collected keys) and that the `false` result is propagated (or at least that no further nodes are visited).

Suggested implementation:

```c
static bool _rbt_foreach_early_stop(om_rbt_node_t* node, void* data) {
  int* count = (int*)data;

  (*count)++;

  /* 在访问到第2个节点后中止遍历 */
  if (*count >= 2) {
    return false;
  }

  return true;
}

START_TEST(_RBT_FOREACH) {

```

```c
END_TEST

START_TEST(_RBT_FOREACH_EARLY_STOP) {
  RBT_ROOT(rbt);
  om_rbt_node_t node_a = {.key = "A"};
  om_rbt_node_t node_b = {.key = "B"};
  om_rbt_node_t node_c = {.key = "C"};
  int count = 0;

  om_rbtree_insert(&rbt, &node_b);
  om_rbtree_insert(&rbt, &node_a);
  om_rbtree_insert(&rbt, &node_c);

  om_rbtree_foreach(&rbt, _rbt_foreach_early_stop, &count);

  ck_assert_msg(count == 2, "回调返回false后应只访问前2个节点,实际访问%d个", count);
}
END_TEST

Suite* make_om_base_suite(void) {

```

```c
  tcase_add_test(tc_rbt, _RBT);
  tcase_add_test(tc_rbt, _RBT_FOREACH);
  tcase_add_test(tc_rbt, _RBT_FOREACH_EARLY_STOP);

```

1. This patch assumes `bool` is already available (e.g., via `#include <stdbool.h>` in this file or a common header). If not, add that include near the top of `om_test_base.c`.
2. The exact callback signature for `om_rbtree_foreach` was inferred from `_rbt_foreach_once`; if it differs (e.g., const qualifiers or different parameter order), adjust `_rbt_foreach_early_stop` to match it.
3. If your traversal guarantees a different visit order or if the tree may contain fewer than 3 nodes in some configurations, you may want to relax the assertion to `ck_assert_int_le(count, 2)` and separately assert `count >= 1`.
</issue_to_address>

### Comment 3
<location path="test/om_test_base.c" line_range="139" />
<code_context>
 END_TEST

+START_TEST(_RBT_FOREACH) {
+  RBT_ROOT(rbt);
+  om_rbt_node_t node = {.key = "B"};
+  int count = 0;
</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding a dedicated test for traversing an empty tree.

Since the fix now returns `true` when `node == NULL`, please add a test that calls `om_rbtree_foreach` on an empty `RBT_ROOT` and verifies that the callback is never invoked (e.g., a counter stays at 0). This will protect the `node == NULL` base case from regressions.

Suggested implementation:

```c
START_TEST(_RBT_FOREACH) {
  RBT_ROOT(rbt);
  om_rbt_node_t node = {.key = "B"};
  int count = 0;

  om_rbtree_insert(&rbt, &node);
  om_rbtree_foreach(&rbt, _rbt_foreach_once, &count);

  ck_assert_msg(count == 1, "单节点遍历应执行1次回调,实际执行%d次", count);
}
END_TEST

START_TEST(_RBT_FOREACH_EMPTY) {
  RBT_ROOT(rbt);
  int count = 0;

  om_rbtree_foreach(&rbt, _rbt_foreach_once, &count);

  ck_assert_msg(count == 0, "空树遍历不应执行回调,实际执行%d次", count);
}
END_TEST

```

To actually run the new `_RBT_FOREACH_EMPTY` test, you will also need to register it in the suite definition (likely in `make_om_base_suite` or wherever other `START_TEST` cases are added with `tcase_add_test`). Add a corresponding `tcase_add_test(tc, _RBT_FOREACH_EMPTY);` next to the existing `_RBT_FOREACH` registration.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread test/om_test_base.c
@@ -127,6 +135,18 @@ START_TEST(_RBT) {
}
END_TEST

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Add tests to cover multi-node traversals and verify full in-order behavior, not just the single-node case.

Given the traversal bug fixed in this PR, this test should exercise more than the single-node case. Please build a small multi-node tree (e.g., three nodes that force visiting both left and right branches) and assert that all nodes are visited and that the visit sequence is strictly in-order by key (for example, inserting keys "A", "B", "C" and collecting the visited keys in the callback to check the exact order).

Comment thread test/om_test_base.c
int count = 0;

om_rbtree_insert(&rbt, &node);
om_rbtree_foreach(&rbt, _rbt_foreach_once, &count);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Add a test that verifies early termination when the callback returns false.

Since the tests only use callbacks that return true, they don't verify the new short-circuit behavior. Please add a test where the callback returns false after visiting the first (or second) node, and assert both that traversal stops invoking the callback after that node (e.g., via a call counter or collected keys) and that the false result is propagated (or at least that no further nodes are visited).

Suggested implementation:

static bool _rbt_foreach_early_stop(om_rbt_node_t* node, void* data) {
  int* count = (int*)data;

  (*count)++;

  /* 在访问到第2个节点后中止遍历 */
  if (*count >= 2) {
    return false;
  }

  return true;
}

START_TEST(_RBT_FOREACH) {
END_TEST

START_TEST(_RBT_FOREACH_EARLY_STOP) {
  RBT_ROOT(rbt);
  om_rbt_node_t node_a = {.key = "A"};
  om_rbt_node_t node_b = {.key = "B"};
  om_rbt_node_t node_c = {.key = "C"};
  int count = 0;

  om_rbtree_insert(&rbt, &node_b);
  om_rbtree_insert(&rbt, &node_a);
  om_rbtree_insert(&rbt, &node_c);

  om_rbtree_foreach(&rbt, _rbt_foreach_early_stop, &count);

  ck_assert_msg(count == 2, "回调返回false后应只访问前2个节点,实际访问%d个", count);
}
END_TEST

Suite* make_om_base_suite(void) {
  tcase_add_test(tc_rbt, _RBT);
  tcase_add_test(tc_rbt, _RBT_FOREACH);
  tcase_add_test(tc_rbt, _RBT_FOREACH_EARLY_STOP);
  1. This patch assumes bool is already available (e.g., via #include <stdbool.h> in this file or a common header). If not, add that include near the top of om_test_base.c.
  2. The exact callback signature for om_rbtree_foreach was inferred from _rbt_foreach_once; if it differs (e.g., const qualifiers or different parameter order), adjust _rbt_foreach_early_stop to match it.
  3. If your traversal guarantees a different visit order or if the tree may contain fewer than 3 nodes in some configurations, you may want to relax the assertion to ck_assert_int_le(count, 2) and separately assert count >= 1.

Comment thread test/om_test_base.c
END_TEST

START_TEST(_RBT_FOREACH) {
RBT_ROOT(rbt);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Consider adding a dedicated test for traversing an empty tree.

Since the fix now returns true when node == NULL, please add a test that calls om_rbtree_foreach on an empty RBT_ROOT and verifies that the callback is never invoked (e.g., a counter stays at 0). This will protect the node == NULL base case from regressions.

Suggested implementation:

START_TEST(_RBT_FOREACH) {
  RBT_ROOT(rbt);
  om_rbt_node_t node = {.key = "B"};
  int count = 0;

  om_rbtree_insert(&rbt, &node);
  om_rbtree_foreach(&rbt, _rbt_foreach_once, &count);

  ck_assert_msg(count == 1, "单节点遍历应执行1次回调,实际执行%d次", count);
}
END_TEST

START_TEST(_RBT_FOREACH_EMPTY) {
  RBT_ROOT(rbt);
  int count = 0;

  om_rbtree_foreach(&rbt, _rbt_foreach_once, &count);

  ck_assert_msg(count == 0, "空树遍历不应执行回调,实际执行%d次", count);
}
END_TEST

To actually run the new _RBT_FOREACH_EMPTY test, you will also need to register it in the suite definition (likely in make_om_base_suite or wherever other START_TEST cases are added with tcase_add_test). Add a corresponding tcase_add_test(tc, _RBT_FOREACH_EMPTY); next to the existing _RBT_FOREACH registration.

@Jiu-xiao Jiu-xiao merged commit c0888db into master Apr 20, 2026
2 checks passed
@Jiu-xiao Jiu-xiao deleted the fix/rbtree-foreach branch April 20, 2026 17:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

_om_rbtree_foreach 错误

1 participant