TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
605 lines
20 KiB
C
605 lines
20 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);
|
|
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);
|
|
}
|