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>
639 lines
21 KiB
C
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);
|
|
}
|