Files
libakstdlib/tests/test_tree.c
Andrew Kesterson cc5e7899bb Cap the depth of a right-leaning tree too
Mutation testing found it: changing the right child's `depth + 1` to
`depth + 0` in the depth-first recursion survived the entire suite. The
depth cap was only ever tested against a chain that leans left, so
nothing said whether the right-hand descent counted at all -- a tree
that goes right for a million nodes would have recursed until the
process died, which is precisely what AKSL_TREE_MAX_DEPTH exists to
prevent.

The right-leaning chain is now tested in all three depth-first orders
and breadth-first, which carries its depth on the queue entry instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:09:04 -04:00

639 lines
21 KiB
C

/*
* Tree traversal -- TODO.md section 1.8, complete.
*
* The old version of this file counted steps, which cannot tell the three
* depth-first orders apart because all three visit all seven nodes -- and could
* not prove that AKERR_ITERATOR_BREAK stopped anything either, because it hid
* its target in tree[6], the last node visited in every depth-first order. Every
* traversal here records the node pointers it was handed, in order, and compares
* against the expected sequence.
*
* The 7-node tree used throughout:
*
* TREE[0]
* +--------^^---------+
* | |
* TREE[1] TREE[2]
* +---^^---+ +---^^---+
* | | | |
* TREE[3] TREE[4] TREE[5] TREE[6]
*
* pre-order 0 1 3 4 2 5 6
* in-order 3 1 4 0 5 2 6
* post-order 3 4 1 5 6 2 0
* BFS 0 1 2 3 4 5 6
* BFS_RIGHT 0 2 1 6 5 4 3
*/
#include "aksl_capture.h"
#define MAX_LEAVES 7
#define MAX_VISITS 32
typedef struct VisitLog
{
int count;
aksl_TreeNode *seen[MAX_VISITS];
aksl_TreeNode *break_at; /* node to raise ITERATOR_BREAK on, or NULL */
aksl_TreeNode *fail_at; /* node to raise AKERR_VALUE on, or NULL */
} VisitLog;
static void visitlog_init(VisitLog *log)
{
memset((void *)log, 0x00, sizeof(VisitLog));
}
static akerr_ErrorContext AKERR_NOIGNORE *record_visit(aksl_TreeNode *node, void *data)
{
VisitLog *log = NULL;
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "node");
FAIL_ZERO_RETURN(e, data, AKERR_NULLPOINTER, "data");
log = (VisitLog *)data;
if ( log->count < MAX_VISITS ) {
log->seen[log->count] = node;
}
log->count += 1;
if ( log->fail_at == node ) {
FAIL_RETURN(e, AKERR_VALUE, "iterator failed at node %p", (void *)node);
}
if ( log->break_at == node ) {
FAIL_RETURN(e, AKERR_ITERATOR_BREAK, "stop at node %p", (void *)node);
}
SUCCEED_RETURN(e);
}
static void build_tree(aksl_TreeNode *tree)
{
int i = 0;
for ( i = 0; i < MAX_LEAVES; i++ ) {
memset((void *)&tree[i], 0x00, sizeof(aksl_TreeNode));
}
tree[0].left = &tree[1];
tree[0].right = &tree[2];
tree[1].left = &tree[3];
tree[1].right = &tree[4];
tree[2].left = &tree[5];
tree[2].right = &tree[6];
}
/* Assert that the visit log matches `expected`, which is a list of tree indices. */
static int check_order(VisitLog *log, aksl_TreeNode *tree,
const int *expected, int n)
{
int i = 0;
if ( log->count != n ) {
fprintf(stderr, " CHECK FAILED: visited %d nodes, expected %d\n",
log->count, n);
return 1;
}
for ( i = 0; i < n; i++ ) {
if ( log->seen[i] != &tree[expected[i]] ) {
fprintf(stderr, " CHECK FAILED: visit %d was node %ld, expected %d\n",
i, (long)(log->seen[i] - tree), expected[i]);
return 1;
}
}
return 0;
}
/* ---------------------------------------------------------------------- */
/* aksl_tree_node_init */
/* ---------------------------------------------------------------------- */
static int test_node_init_zeroes_the_links(void)
{
aksl_TreeNode node;
int payload = 3;
memset((void *)&node, 0xff, sizeof(node));
AKSL_CHECK_OK(aksl_tree_node_init(&node, &payload));
AKSL_CHECK(node.parent == NULL);
AKSL_CHECK(node.left == NULL);
AKSL_CHECK(node.right == NULL);
AKSL_CHECK(node.leaf == (void *)&payload);
AKSL_CHECK_STATUS(aksl_tree_node_init(NULL, NULL), AKERR_NULLPOINTER);
return 0;
}
/* ---------------------------------------------------------------------- */
/* Traversal orders */
/* ---------------------------------------------------------------------- */
static int test_preorder_visits_root_left_right(void)
{
aksl_TreeNode tree[MAX_LEAVES];
VisitLog log;
static const int expected[MAX_LEAVES] = { 0, 1, 3, 4, 2, 5, 6 };
build_tree(tree);
visitlog_init(&log);
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_DFS_PREORDER, &log));
AKSL_CHECK(check_order(&log, tree, expected, MAX_LEAVES) == 0);
return 0;
}
static int test_inorder_visits_left_root_right(void)
{
aksl_TreeNode tree[MAX_LEAVES];
VisitLog log;
static const int expected[MAX_LEAVES] = { 3, 1, 4, 0, 5, 2, 6 };
build_tree(tree);
visitlog_init(&log);
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_DFS_INORDER, &log));
AKSL_CHECK(check_order(&log, tree, expected, MAX_LEAVES) == 0);
return 0;
}
static int test_postorder_visits_left_right_root(void)
{
aksl_TreeNode tree[MAX_LEAVES];
VisitLog log;
static const int expected[MAX_LEAVES] = { 3, 4, 1, 5, 6, 2, 0 };
build_tree(tree);
visitlog_init(&log);
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_DFS_POSTORDER, &log));
AKSL_CHECK(check_order(&log, tree, expected, MAX_LEAVES) == 0);
return 0;
}
/* AKSL_TREE_SEARCH_DFS is documented as an alias for the pre-order mode. */
static int test_dfs_is_an_alias_for_preorder(void)
{
aksl_TreeNode tree[MAX_LEAVES];
VisitLog log;
static const int expected[MAX_LEAVES] = { 0, 1, 3, 4, 2, 5, 6 };
build_tree(tree);
visitlog_init(&log);
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_DFS, &log));
AKSL_CHECK(check_order(&log, tree, expected, MAX_LEAVES) == 0);
return 0;
}
/*
* BFS was AKERR_NOT_IMPLEMENTED, and the lalloc/lfree parameters that existed to
* serve it were defaulted and then never called -- TODO.md 2.2.8 and 2.2.10.
* Both modes work now, and the allocator test below proves the queue is real.
*/
static int test_bfs_visits_level_by_level(void)
{
aksl_TreeNode tree[MAX_LEAVES];
VisitLog log;
static const int expected[MAX_LEAVES] = { 0, 1, 2, 3, 4, 5, 6 };
build_tree(tree);
visitlog_init(&log);
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_BFS, &log));
AKSL_CHECK(check_order(&log, tree, expected, MAX_LEAVES) == 0);
return 0;
}
static int test_bfs_right_visits_right_child_first(void)
{
aksl_TreeNode tree[MAX_LEAVES];
VisitLog log;
static const int expected[MAX_LEAVES] = { 0, 2, 1, 6, 5, 4, 3 };
build_tree(tree);
visitlog_init(&log);
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_BFS_RIGHT, &log));
AKSL_CHECK(check_order(&log, tree, expected, MAX_LEAVES) == 0);
return 0;
}
/* AKSL_TREE_SEARCH_VISIT: this node and no further. */
static int test_visit_mode_does_not_descend(void)
{
aksl_TreeNode tree[MAX_LEAVES];
VisitLog log;
static const int expected[1] = { 0 };
build_tree(tree);
visitlog_init(&log);
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_VISIT, &log));
AKSL_CHECK(check_order(&log, tree, expected, 1) == 0);
return 0;
}
/* ---------------------------------------------------------------------- */
/* Custom allocator */
/* ---------------------------------------------------------------------- */
static int counting_alloc_calls = 0;
static int counting_free_calls = 0;
static akerr_ErrorContext AKERR_NOIGNORE *counting_alloc(size_t size, void **dest)
{
counting_alloc_calls += 1;
return aksl_malloc(size, dest);
}
static akerr_ErrorContext AKERR_NOIGNORE *counting_free(void *ptr)
{
counting_free_calls += 1;
return aksl_free(ptr);
}
/*
* TODO.md 1.8: "Custom lalloc/lfree are actually invoked -- currently they are
* stored and never called". They are called now, once per node enqueued, and
* every allocation is released. The depth-first modes allocate nothing at all,
* which is the other half of the contract.
*/
static int test_custom_allocator_is_used_and_balanced(void)
{
aksl_TreeNode tree[MAX_LEAVES];
VisitLog log;
build_tree(tree);
visitlog_init(&log);
counting_alloc_calls = 0;
counting_free_calls = 0;
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit,
&counting_alloc, &counting_free,
AKSL_TREE_SEARCH_BFS, &log));
AKSL_CHECK(log.count == MAX_LEAVES);
/* One queue entry per node visited, and every one of them released. */
AKSL_CHECK(counting_alloc_calls == MAX_LEAVES);
AKSL_CHECK(counting_free_calls == MAX_LEAVES);
/* Depth-first needs no queue, so it must not touch the allocator. */
counting_alloc_calls = 0;
counting_free_calls = 0;
visitlog_init(&log);
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit,
&counting_alloc, &counting_free,
AKSL_TREE_SEARCH_DFS_PREORDER, &log));
AKSL_CHECK(counting_alloc_calls == 0);
AKSL_CHECK(counting_free_calls == 0);
return 0;
}
/* A break part-way through a BFS still drains the queue it had already built. */
static int test_break_during_bfs_releases_the_queue(void)
{
aksl_TreeNode tree[MAX_LEAVES];
VisitLog log;
build_tree(tree);
visitlog_init(&log);
log.break_at = &tree[1];
counting_alloc_calls = 0;
counting_free_calls = 0;
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit,
&counting_alloc, &counting_free,
AKSL_TREE_SEARCH_BFS, &log));
/* 0 and 1 visited; the walk stops at 1 with 2 still sitting in the queue. */
AKSL_CHECK(log.count == 2);
AKSL_CHECK(counting_alloc_calls > 0);
AKSL_CHECK(counting_alloc_calls == counting_free_calls);
return 0;
}
/* ---------------------------------------------------------------------- */
/* Degenerate shapes */
/* ---------------------------------------------------------------------- */
static int test_single_node_tree_visits_once_in_every_order(void)
{
aksl_TreeNode node;
VisitLog log;
static const uint8_t modes[] = {
AKSL_TREE_SEARCH_DFS_PREORDER,
AKSL_TREE_SEARCH_DFS_INORDER,
AKSL_TREE_SEARCH_DFS_POSTORDER,
AKSL_TREE_SEARCH_BFS,
AKSL_TREE_SEARCH_BFS_RIGHT,
AKSL_TREE_SEARCH_VISIT,
};
size_t i = 0;
for ( i = 0; i < sizeof(modes) / sizeof(modes[0]); i++ ) {
AKSL_CHECK_OK(aksl_tree_node_init(&node, NULL));
visitlog_init(&log);
AKSL_CHECK_OK(aksl_tree_iterate(&node, &record_visit, NULL, NULL,
modes[i], &log));
AKSL_CHECK(log.count == 1);
AKSL_CHECK(log.seen[0] == &node);
}
return 0;
}
/* Left-only and right-only chains of three nodes, no branching anywhere. */
static int test_degenerate_chains(void)
{
aksl_TreeNode chain[3];
VisitLog log;
static const int forward[3] = { 0, 1, 2 };
static const int backward[3] = { 2, 1, 0 };
int i = 0;
/* Left-only: 0 -> 1 -> 2 */
for ( i = 0; i < 3; i++ ) {
AKSL_CHECK_OK(aksl_tree_node_init(&chain[i], NULL));
}
chain[0].left = &chain[1];
chain[1].left = &chain[2];
visitlog_init(&log);
AKSL_CHECK_OK(aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_DFS_PREORDER, &log));
AKSL_CHECK(check_order(&log, chain, forward, 3) == 0);
/* In-order down a left chain arrives at the deepest node first. */
visitlog_init(&log);
AKSL_CHECK_OK(aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_DFS_INORDER, &log));
AKSL_CHECK(check_order(&log, chain, backward, 3) == 0);
/* Right-only: the same shape, mirrored. */
for ( i = 0; i < 3; i++ ) {
AKSL_CHECK_OK(aksl_tree_node_init(&chain[i], NULL));
}
chain[0].right = &chain[1];
chain[1].right = &chain[2];
visitlog_init(&log);
AKSL_CHECK_OK(aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_DFS_INORDER, &log));
AKSL_CHECK(check_order(&log, chain, forward, 3) == 0);
visitlog_init(&log);
AKSL_CHECK_OK(aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_BFS, &log));
AKSL_CHECK(check_order(&log, chain, forward, 3) == 0);
return 0;
}
/*
* TODO.md 1.8 / 2.2.7: a chain deeper than the recursion can take. It used to
* overflow the stack; it is AKERR_OUTOFBOUNDS now, and the message names the
* documented limit. Built one node past the cap so the failure is the cap itself
* and not some incidental shortfall. `static` because AKSL_TREE_MAX_DEPTH nodes
* on the stack of a test function is not the point of the test.
*/
static int test_tree_deeper_than_the_cap_is_out_of_bounds(void)
{
static aksl_TreeNode chain[AKSL_TREE_MAX_DEPTH + 2];
VisitLog log;
int i = 0;
for ( i = 0; i < AKSL_TREE_MAX_DEPTH + 2; i++ ) {
AKSL_CHECK_OK(aksl_tree_node_init(&chain[i], NULL));
}
for ( i = 0; i < AKSL_TREE_MAX_DEPTH + 1; i++ ) {
chain[i].left = &chain[i + 1];
}
visitlog_init(&log);
AKSL_CHECK_STATUS_MSG_CONTAINS(
aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_DFS_PREORDER, &log),
AKERR_OUTOFBOUNDS, "AKSL_TREE_MAX_DEPTH");
/* Exactly at the cap is fine -- a limit, not an off-by-one. */
chain[AKSL_TREE_MAX_DEPTH - 1].left = NULL;
visitlog_init(&log);
AKSL_CHECK_OK(aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_DFS_PREORDER, &log));
AKSL_CHECK(log.count == AKSL_TREE_MAX_DEPTH);
/*
* And again leaning right. The recursion counts depth separately for each
* child, so a chain that only ever goes left says nothing about whether the
* right-hand descent counts at all -- mutation testing found exactly that:
* changing the right child's `depth + 1` to `depth + 0` survived the whole
* suite, because nothing here had ever recursed right more than three deep.
*/
for ( i = 0; i < AKSL_TREE_MAX_DEPTH + 2; i++ ) {
AKSL_CHECK_OK(aksl_tree_node_init(&chain[i], NULL));
}
for ( i = 0; i < AKSL_TREE_MAX_DEPTH + 1; i++ ) {
chain[i].right = &chain[i + 1];
}
visitlog_init(&log);
AKSL_CHECK_STATUS(aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_DFS_PREORDER, &log),
AKERR_OUTOFBOUNDS);
/* Both orders that descend right, so in-order and post-order count too. */
visitlog_init(&log);
AKSL_CHECK_STATUS(aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_DFS_INORDER, &log),
AKERR_OUTOFBOUNDS);
visitlog_init(&log);
AKSL_CHECK_STATUS(aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_DFS_POSTORDER, &log),
AKERR_OUTOFBOUNDS);
/* And breadth-first, whose depth is carried on the queue entry instead. */
visitlog_init(&log);
AKSL_CHECK_STATUS(aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_BFS, &log),
AKERR_OUTOFBOUNDS);
return 0;
}
/*
* A child pointing back at an ancestor. This used to recurse until the process
* died. The depth-first walk carries the ancestor chain and recognises the node
* as its own forebear; the breadth-first walk has no ancestor chain to compare
* against, so the same tree comes back as AKERR_OUTOFBOUNDS through the depth
* cap instead -- a different status for the same shape, which the header says
* out loud rather than leaving to be discovered.
*/
static int test_cyclic_tree_is_caught(void)
{
aksl_TreeNode tree[MAX_LEAVES];
VisitLog log;
build_tree(tree);
tree[3].left = &tree[0]; /* back to the root */
visitlog_init(&log);
AKSL_CHECK_STATUS_MSG_CONTAINS(
aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_DFS_PREORDER, &log),
AKERR_CIRCULAR_REFERENCE, "own ancestor");
visitlog_init(&log);
AKSL_CHECK_STATUS(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_BFS, &log),
AKERR_OUTOFBOUNDS);
return 0;
}
/* ---------------------------------------------------------------------- */
/* Callback control flow */
/* ---------------------------------------------------------------------- */
/*
* The defect that made ITERATOR_BREAK useless on a tree: the recursive frame
* that raised the break handled it in its own PROCESS/HANDLE block and returned
* success, so the parent frame's PASS saw nothing wrong and carried straight on
* into the sibling subtree. All seven nodes were visited no matter where the
* break was raised. TODO.md 2.1.3.
*
* One case per order, each breaking on a node that is *not* last in that order --
* which is precisely what the old test could not do, because it hid its target
* in tree[6], the final node in all three depth-first walks.
*/
static int test_break_aborts_the_whole_traversal(void)
{
aksl_TreeNode tree[MAX_LEAVES];
VisitLog log;
build_tree(tree);
/* Pre-order 0 1 3 ... : breaking at 3 is the third visit. */
visitlog_init(&log);
log.break_at = &tree[3];
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_DFS_PREORDER, &log));
AKSL_CHECK(log.count == 3);
/* In-order 3 1 4 ... : breaking at 4 is the third visit. */
visitlog_init(&log);
log.break_at = &tree[4];
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_DFS_INORDER, &log));
AKSL_CHECK(log.count == 3);
/* Post-order 3 4 1 5 ... : breaking at 5 is the fourth visit. */
visitlog_init(&log);
log.break_at = &tree[5];
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_DFS_POSTORDER, &log));
AKSL_CHECK(log.count == 4);
/* BFS 0 1 2 3 ... : breaking at 2 is the third visit. */
visitlog_init(&log);
log.break_at = &tree[2];
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_BFS, &log));
AKSL_CHECK(log.count == 3);
return 0;
}
/* Any other status propagates out with its message and its own status intact. */
static int test_callback_error_propagates(void)
{
aksl_TreeNode tree[MAX_LEAVES];
VisitLog log;
build_tree(tree);
visitlog_init(&log);
log.fail_at = &tree[3];
AKSL_CHECK_STATUS_MSG_CONTAINS(
aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_DFS_PREORDER, &log),
AKERR_VALUE, "iterator failed at node");
AKSL_CHECK(log.count == 3);
/* And out of a BFS, where the queue has to be drained on the way past. */
visitlog_init(&log);
log.fail_at = &tree[2];
AKSL_CHECK_STATUS(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_BFS, &log),
AKERR_VALUE);
return 0;
}
/* ---------------------------------------------------------------------- */
/* Argument validation */
/* ---------------------------------------------------------------------- */
static int test_null_arguments(void)
{
aksl_TreeNode tree[MAX_LEAVES];
VisitLog log;
build_tree(tree);
visitlog_init(&log);
AKSL_CHECK_STATUS(aksl_tree_iterate(NULL, &record_visit, NULL, NULL,
AKSL_TREE_SEARCH_DFS_PREORDER, &log),
AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_tree_iterate(&tree[0], NULL, NULL, NULL,
AKSL_TREE_SEARCH_DFS_PREORDER, &log),
AKERR_NULLPOINTER);
AKSL_CHECK(log.count == 0);
return 0;
}
/*
* TODO.md 2.2.9: the switch had no default, so an unrecognised mode -- and
* AKSL_TREE_SEARCH_VISIT, which the header documented but nothing implemented --
* fell straight through to SUCCEED_RETURN having visited nothing at all. A
* traversal that silently did not happen, reported as success.
*/
static int test_unknown_searchmode_is_a_value_error(void)
{
aksl_TreeNode tree[MAX_LEAVES];
VisitLog log;
build_tree(tree);
visitlog_init(&log);
AKSL_CHECK_STATUS_MSG_CONTAINS(
aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL, 99, &log),
AKERR_VALUE, "unknown searchmode");
AKSL_CHECK(log.count == 0);
/* 6 is one past the last defined mode, and just as unacceptable. */
AKSL_CHECK_STATUS(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL, 6, &log),
AKERR_VALUE);
AKSL_CHECK(log.count == 0);
return 0;
}
int main(void)
{
int failures = 0;
akerr_init();
AKSL_RUN(failures, test_node_init_zeroes_the_links);
AKSL_RUN(failures, test_preorder_visits_root_left_right);
AKSL_RUN(failures, test_inorder_visits_left_root_right);
AKSL_RUN(failures, test_postorder_visits_left_right_root);
AKSL_RUN(failures, test_dfs_is_an_alias_for_preorder);
AKSL_RUN(failures, test_bfs_visits_level_by_level);
AKSL_RUN(failures, test_bfs_right_visits_right_child_first);
AKSL_RUN(failures, test_visit_mode_does_not_descend);
AKSL_RUN(failures, test_custom_allocator_is_used_and_balanced);
AKSL_RUN(failures, test_break_during_bfs_releases_the_queue);
AKSL_RUN(failures, test_single_node_tree_visits_once_in_every_order);
AKSL_RUN(failures, test_degenerate_chains);
AKSL_RUN(failures, test_tree_deeper_than_the_cap_is_out_of_bounds);
AKSL_RUN(failures, test_cyclic_tree_is_caught);
AKSL_RUN(failures, test_break_aborts_the_whole_traversal);
AKSL_RUN(failures, test_callback_error_propagates);
AKSL_RUN(failures, test_null_arguments);
AKSL_RUN(failures, test_unknown_searchmode_is_a_value_error);
AKSL_REPORT(failures);
}