Closes what was left of TODO.md sections 1, 2 and 3, and rewrites that
file to hold outstanding items only.
The API break gets a minor bump, because pre-1.0 the soname carries
MAJOR.MINOR and 0.1 and 0.2 are therefore different ABIs. Five
signatures changed and the ato* contract with them; UPGRADING.md is new
and lists every one, with the before/after for the cases the compiler
cannot warn about.
Section 3.1 is finished: reallocarray with the multiplication checked,
aligned_alloc and posix_memalign, asprintf/vasprintf, scanf/vscanf.
Four functions on that list are deliberately absent rather than missing
-- sprintf, strtok, setbuf and perror -- and TODO.md now says which and
why, so nobody adds them thinking they were forgotten.
Section 1.9, the cross-cutting tests:
tests/test_pool.c drives every failure path AKERR_MAX_ARRAY_ERROR
+ 10 times and checks the pool after each round,
because a wrapper that leaks a slot fails a
hundred calls later in unrelated code. It also
asserts that each error names the function and
file it was raised from, which is what catches a
FAIL that migrates into a helper during a
refactor: status right, message right, origin
quietly lying.
tests/negative/ two sources that must FAIL to compile, built with
-Werror and registered WILL_FAIL. AKERR_NOIGNORE
and the format attributes are enforced by the
compiler and by nothing else; drop either and
every ordinary test still passes.
Thread safety is answered rather than tested: the library is not
thread-safe and cannot be made so from here, because libakerror's error
pool is an unlocked process-global array. README.md says so plainly and
TODO.md carries it as the item blocking any future pthread wrappers.
Doxygen is configured and gated. All 147 public functions have @brief,
a @param each, @throws per status and @return; EXTRACT_ALL is off and
WARN_NO_PARAMDOC on, so `cmake --build build --target docs` fails on an
undocumented entity. It ran to 0 warnings. The Doxyfile carries no
version -- cmake/RunDoxygen.cmake feeds PROJECT_NUMBER in from
project(), so that stays the one place a version is written.
CI now builds against the submodule it pins instead of also installing
libakerror@main and never linking it, adds -Werror, and gains a
sanitizer job. The pre-push hook matches, and runs the docs check too.
Coverage: 99.5% of lines (1643/1651), 100% of functions (147/147). The
eight uncovered lines are each uncovered on purpose and TODO.md says
which and why.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1120 lines
38 KiB
C
1120 lines
38 KiB
C
/*
|
|
* List and tree additions -- src/collections.c, TODO.md section 3.6.
|
|
*
|
|
* The bare-node list functions, the tracked aksl_List container, and the binary
|
|
* search tree. The hash map and string buffer have their own files.
|
|
*
|
|
* Nodes here are stack arrays, which is the point: none of these functions
|
|
* allocates, so a caller drawing from a fixed pool can use all of them. The two
|
|
* *_free_all tests use aksl_malloc explicitly, because releasing is the one
|
|
* thing that has to know where the memory came from.
|
|
*/
|
|
|
|
#include "aksl_capture.h"
|
|
|
|
#define N 5
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Helpers */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
typedef struct VisitLog
|
|
{
|
|
int count;
|
|
aksl_ListNode *seen[16];
|
|
int break_at;
|
|
} VisitLog;
|
|
|
|
static void visitlog_init(VisitLog *log)
|
|
{
|
|
memset((void *)log, 0x00, sizeof(VisitLog));
|
|
log->break_at = -1;
|
|
}
|
|
|
|
static akerr_ErrorContext AKERR_NOIGNORE *record_visit(aksl_ListNode *node, void *data)
|
|
{
|
|
VisitLog *log = NULL;
|
|
int idx = 0;
|
|
|
|
PREPARE_ERROR(e);
|
|
FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "node");
|
|
FAIL_ZERO_RETURN(e, data, AKERR_NULLPOINTER, "data");
|
|
log = (VisitLog *)data;
|
|
idx = log->count;
|
|
if ( idx < 16 ) {
|
|
log->seen[idx] = node;
|
|
}
|
|
log->count += 1;
|
|
if ( log->break_at == idx ) {
|
|
FAIL_RETURN(e, AKERR_ITERATOR_BREAK, "stop at visit %d", idx);
|
|
}
|
|
SUCCEED_RETURN(e);
|
|
}
|
|
|
|
/* Accepts the node whose data pointer equals `data`. */
|
|
static akerr_ErrorContext AKERR_NOIGNORE *match_data(aksl_ListNode *node, void *data, int *matched)
|
|
{
|
|
PREPARE_ERROR(e);
|
|
FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "node");
|
|
FAIL_ZERO_RETURN(e, matched, AKERR_NULLPOINTER, "matched");
|
|
*matched = (node->data == data) ? 1 : 0;
|
|
SUCCEED_RETURN(e);
|
|
}
|
|
|
|
/* A predicate that fails, to prove the error comes back out of the search. */
|
|
static akerr_ErrorContext AKERR_NOIGNORE *failing_predicate(aksl_ListNode *node, void *data, int *matched)
|
|
{
|
|
PREPARE_ERROR(e);
|
|
(void)node;
|
|
(void)data;
|
|
(void)matched;
|
|
FAIL_RETURN(e, AKERR_VALUE, "predicate refused to answer");
|
|
}
|
|
|
|
/* Build node[0..n) into a chain and hand back the head. */
|
|
static void build_chain(aksl_ListNode *node, int n)
|
|
{
|
|
int i = 0;
|
|
|
|
for ( i = 0; i < n; i++ ) {
|
|
memset((void *)&node[i], 0x00, sizeof(aksl_ListNode));
|
|
}
|
|
for ( i = 1; i < n; i++ ) {
|
|
node[i - 1].next = &node[i];
|
|
node[i].prev = &node[i - 1];
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Insertion */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static int test_prepend_moves_the_head(void)
|
|
{
|
|
aksl_ListNode node[3];
|
|
aksl_ListNode *head = NULL;
|
|
|
|
build_chain(node, 3);
|
|
head = &node[1];
|
|
node[1].prev = NULL;
|
|
|
|
AKSL_CHECK_OK(aksl_list_prepend(&head, &node[0]));
|
|
AKSL_CHECK(head == &node[0]);
|
|
AKSL_CHECK(node[0].next == &node[1]);
|
|
AKSL_CHECK(node[0].prev == NULL);
|
|
AKSL_CHECK(node[1].prev == &node[0]);
|
|
|
|
/* Prepending onto an empty list makes a one-node list. */
|
|
head = NULL;
|
|
memset((void *)&node[2], 0x00, sizeof(node[2]));
|
|
AKSL_CHECK_OK(aksl_list_prepend(&head, &node[2]));
|
|
AKSL_CHECK(head == &node[2]);
|
|
AKSL_CHECK(node[2].next == NULL);
|
|
|
|
AKSL_CHECK_STATUS(aksl_list_prepend(&head, &node[2]), AKERR_VALUE);
|
|
AKSL_CHECK_STATUS(aksl_list_prepend(NULL, &node[0]), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_list_prepend(&head, NULL), AKERR_NULLPOINTER);
|
|
return 0;
|
|
}
|
|
|
|
static int test_insert_after_and_before(void)
|
|
{
|
|
aksl_ListNode node[4];
|
|
aksl_ListNode *head = &node[0];
|
|
|
|
build_chain(node, 2);
|
|
memset((void *)&node[2], 0x00, sizeof(node[2]));
|
|
memset((void *)&node[3], 0x00, sizeof(node[3]));
|
|
|
|
/* node[0] -> node[2] -> node[1] */
|
|
AKSL_CHECK_OK(aksl_list_insert_after(&node[0], &node[2]));
|
|
AKSL_CHECK(node[0].next == &node[2]);
|
|
AKSL_CHECK(node[2].prev == &node[0]);
|
|
AKSL_CHECK(node[2].next == &node[1]);
|
|
AKSL_CHECK(node[1].prev == &node[2]);
|
|
|
|
/* Inserting before the head moves it, which is why head is required. */
|
|
AKSL_CHECK_OK(aksl_list_insert_before(&head, &node[0], &node[3]));
|
|
AKSL_CHECK(head == &node[3]);
|
|
AKSL_CHECK(node[3].next == &node[0]);
|
|
AKSL_CHECK(node[0].prev == &node[3]);
|
|
|
|
AKSL_CHECK_STATUS(aksl_list_insert_after(&node[0], &node[0]), AKERR_VALUE);
|
|
AKSL_CHECK_STATUS(aksl_list_insert_after(NULL, &node[0]), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_list_insert_after(&node[0], NULL), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_list_insert_before(&head, &node[0], &node[0]), AKERR_VALUE);
|
|
AKSL_CHECK_STATUS(aksl_list_insert_before(NULL, &node[0], &node[1]), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_list_insert_before(&head, NULL, &node[1]), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_list_insert_before(&head, &node[0], NULL), AKERR_NULLPOINTER);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Inserting before a node that is *not* the head, which relinks the node in
|
|
* front of it as well -- a different path from inserting before the head, where
|
|
* there is no such node.
|
|
*/
|
|
static int test_insert_before_a_middle_node(void)
|
|
{
|
|
aksl_ListNode node[3];
|
|
aksl_ListNode fresh;
|
|
aksl_ListNode *head = &node[0];
|
|
|
|
build_chain(node, 3);
|
|
memset((void *)&fresh, 0x00, sizeof(fresh));
|
|
|
|
AKSL_CHECK_OK(aksl_list_insert_before(&head, &node[2], &fresh));
|
|
AKSL_CHECK(head == &node[0]); /* unchanged, unlike the head case */
|
|
AKSL_CHECK(node[1].next == &fresh);
|
|
AKSL_CHECK(fresh.prev == &node[1]);
|
|
AKSL_CHECK(fresh.next == &node[2]);
|
|
AKSL_CHECK(node[2].prev == &fresh);
|
|
return 0;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Inspection */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static int test_length_counts_and_refuses_cycles(void)
|
|
{
|
|
aksl_ListNode node[N];
|
|
size_t n = 99;
|
|
|
|
/* An empty list is length 0, not an error. */
|
|
AKSL_CHECK_OK(aksl_list_length(NULL, &n));
|
|
AKSL_CHECK(n == 0);
|
|
|
|
build_chain(node, N);
|
|
AKSL_CHECK_OK(aksl_list_length(&node[0], &n));
|
|
AKSL_CHECK(n == N);
|
|
|
|
node[N - 1].next = &node[0];
|
|
AKSL_CHECK_STATUS(aksl_list_length(&node[0], &n), AKERR_CIRCULAR_REFERENCE);
|
|
|
|
AKSL_CHECK_STATUS(aksl_list_length(&node[0], NULL), AKERR_NULLPOINTER);
|
|
return 0;
|
|
}
|
|
|
|
static int test_find_returns_the_first_match_or_null(void)
|
|
{
|
|
aksl_ListNode node[N];
|
|
aksl_ListNode *found = (aksl_ListNode *)0x1;
|
|
aksl_ListNode *cyclic = NULL;
|
|
int payload = 42;
|
|
int absent = 0;
|
|
|
|
build_chain(node, N);
|
|
node[2].data = &payload;
|
|
|
|
AKSL_CHECK_OK(aksl_list_find(&node[0], &match_data, &payload, &found));
|
|
AKSL_CHECK(found == &node[2]);
|
|
|
|
/* Nothing matches: NULL and success, not an error. */
|
|
AKSL_CHECK_OK(aksl_list_find(&node[0], &match_data, &absent, &found));
|
|
AKSL_CHECK(found == NULL);
|
|
|
|
/* An empty list finds nothing, equally without complaint. */
|
|
AKSL_CHECK_OK(aksl_list_find(NULL, &match_data, &payload, &found));
|
|
AKSL_CHECK(found == NULL);
|
|
|
|
/* A predicate that raises stops the search and propagates. */
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(
|
|
aksl_list_find(&node[0], &failing_predicate, NULL, &found),
|
|
AKERR_VALUE, "predicate refused");
|
|
|
|
/*
|
|
* A cyclic list is refused before the walk starts rather than searched
|
|
* forever. Every whole-list function shares one bounded tail walk, so this
|
|
* covers the bound for find, reverse, concat and free_all alike.
|
|
*/
|
|
node[N - 1].next = &node[0];
|
|
cyclic = &node[0];
|
|
AKSL_CHECK_STATUS(aksl_list_find(&node[0], &match_data, &payload, &found),
|
|
AKERR_CIRCULAR_REFERENCE);
|
|
AKSL_CHECK_STATUS(aksl_list_reverse(&cyclic), AKERR_CIRCULAR_REFERENCE);
|
|
AKSL_CHECK_STATUS(aksl_list_concat(&node[0], &node[2]), AKERR_CIRCULAR_REFERENCE);
|
|
node[N - 1].next = NULL;
|
|
|
|
AKSL_CHECK_STATUS(aksl_list_find(&node[0], NULL, NULL, &found), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_list_find(&node[0], &match_data, NULL, NULL), AKERR_NULLPOINTER);
|
|
return 0;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Rearranging */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static int test_reverse_flips_both_directions(void)
|
|
{
|
|
aksl_ListNode node[N];
|
|
aksl_ListNode *head = NULL;
|
|
aksl_ListNode *walk = NULL;
|
|
int i = 0;
|
|
|
|
build_chain(node, N);
|
|
head = &node[0];
|
|
|
|
AKSL_CHECK_OK(aksl_list_reverse(&head));
|
|
AKSL_CHECK(head == &node[N - 1]);
|
|
|
|
/* Forwards through the reversed list is backwards through the array. */
|
|
walk = head;
|
|
for ( i = N - 1; i >= 0; i-- ) {
|
|
AKSL_CHECK(walk == &node[i]);
|
|
walk = walk->next;
|
|
}
|
|
AKSL_CHECK(walk == NULL);
|
|
|
|
/* And the prev links were flipped too, not just the next ones. */
|
|
walk = &node[0];
|
|
for ( i = 0; i < N; i++ ) {
|
|
AKSL_CHECK(walk == &node[i]);
|
|
walk = walk->prev;
|
|
}
|
|
AKSL_CHECK(walk == NULL);
|
|
|
|
/* Reversing an empty list is a no-op, not an error. */
|
|
head = NULL;
|
|
AKSL_CHECK_OK(aksl_list_reverse(&head));
|
|
AKSL_CHECK(head == NULL);
|
|
AKSL_CHECK_STATUS(aksl_list_reverse(NULL), AKERR_NULLPOINTER);
|
|
return 0;
|
|
}
|
|
|
|
static int test_concat_joins_two_lists(void)
|
|
{
|
|
aksl_ListNode first[3];
|
|
aksl_ListNode second[2];
|
|
size_t n = 0;
|
|
|
|
build_chain(first, 3);
|
|
build_chain(second, 2);
|
|
|
|
AKSL_CHECK_OK(aksl_list_concat(&first[0], &second[0]));
|
|
AKSL_CHECK(first[2].next == &second[0]);
|
|
AKSL_CHECK(second[0].prev == &first[2]);
|
|
AKSL_CHECK_OK(aksl_list_length(&first[0], &n));
|
|
AKSL_CHECK(n == 5);
|
|
|
|
/* Concatenating with NULL is a no-op; with itself would make a cycle. */
|
|
AKSL_CHECK_OK(aksl_list_concat(&first[0], NULL));
|
|
AKSL_CHECK_STATUS(aksl_list_concat(&first[0], &first[0]), AKERR_VALUE);
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_list_concat(&first[0], &second[1]),
|
|
AKERR_VALUE, "already in the destination");
|
|
AKSL_CHECK_STATUS(aksl_list_concat(NULL, &second[0]), AKERR_NULLPOINTER);
|
|
return 0;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Reverse iteration */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static int test_iterate_reverse_walks_back_to_the_head(void)
|
|
{
|
|
aksl_ListNode node[N];
|
|
VisitLog log;
|
|
int i = 0;
|
|
|
|
build_chain(node, N);
|
|
visitlog_init(&log);
|
|
|
|
AKSL_CHECK_OK(aksl_list_iterate_reverse(&node[N - 1], &record_visit, &log));
|
|
AKSL_CHECK(log.count == N);
|
|
for ( i = 0; i < N; i++ ) {
|
|
AKSL_CHECK(log.seen[i] == &node[N - 1 - i]);
|
|
}
|
|
|
|
/* The break works going this way too. */
|
|
visitlog_init(&log);
|
|
log.break_at = 1;
|
|
AKSL_CHECK_OK(aksl_list_iterate_reverse(&node[N - 1], &record_visit, &log));
|
|
AKSL_CHECK(log.count == 2);
|
|
|
|
/* And a cycle in the prev links is refused, as it is in the next links. */
|
|
node[0].prev = &node[N - 1];
|
|
visitlog_init(&log);
|
|
AKSL_CHECK_STATUS(aksl_list_iterate_reverse(&node[N - 1], &record_visit, &log),
|
|
AKERR_CIRCULAR_REFERENCE);
|
|
|
|
AKSL_CHECK_STATUS(aksl_list_iterate_reverse(NULL, &record_visit, &log), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_list_iterate_reverse(&node[0], NULL, &log), AKERR_NULLPOINTER);
|
|
return 0;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Releasing */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static int test_free_all_releases_every_node(void)
|
|
{
|
|
aksl_ListNode *head = NULL;
|
|
aksl_ListNode *node = NULL;
|
|
int i = 0;
|
|
|
|
/* A heap list, since this is the one operation that has to own its memory. */
|
|
for ( i = 0; i < N; i++ ) {
|
|
AKSL_CHECK_OK(aksl_malloc(sizeof(aksl_ListNode), (void **)&node));
|
|
AKSL_CHECK_OK(aksl_list_node_init(node, NULL));
|
|
if ( head == NULL ) {
|
|
head = node;
|
|
} else {
|
|
AKSL_CHECK_OK(aksl_list_append(head, node));
|
|
}
|
|
}
|
|
|
|
AKSL_CHECK_OK(aksl_list_free_all(&head, NULL));
|
|
AKSL_CHECK(head == NULL);
|
|
|
|
/* An empty list frees cleanly, and a second call is a no-op rather than a
|
|
* double free, because the head was cleared. */
|
|
AKSL_CHECK_OK(aksl_list_free_all(&head, NULL));
|
|
AKSL_CHECK_STATUS(aksl_list_free_all(NULL, NULL), AKERR_NULLPOINTER);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* A free function that fails on one node in the middle.
|
|
*
|
|
* Both *_free_all functions go out of their way to keep the first error and
|
|
* carry on rather than returning immediately, because abandoning the walk would
|
|
* leak everything after the node that failed -- turning one bad free into a leak
|
|
* of the whole remaining structure. This is the test that says so.
|
|
*/
|
|
/* Defined with the tree tests below; declared here because the tree free-all
|
|
* case belongs beside the list one rather than beside its comparator. */
|
|
static akerr_ErrorContext AKERR_NOIGNORE *compare_ints(void *a, void *b, int *dest);
|
|
|
|
static int failing_free_countdown = 0;
|
|
static int failing_free_calls = 0;
|
|
|
|
static akerr_ErrorContext AKERR_NOIGNORE *free_that_fails_once(void *ptr)
|
|
{
|
|
PREPARE_ERROR(e);
|
|
failing_free_calls += 1;
|
|
if ( failing_free_calls == failing_free_countdown ) {
|
|
/* Refuse, but still release the memory: the point is the error path,
|
|
* not a deliberate leak for the sanitizer to find. */
|
|
free(ptr);
|
|
FAIL_RETURN(e, AKERR_VALUE, "free refused on call %d", failing_free_calls);
|
|
}
|
|
free(ptr);
|
|
SUCCEED_RETURN(e);
|
|
}
|
|
|
|
/*
|
|
* And one that refuses every time, so more than one error is raised during a
|
|
* single walk. Only the first is kept and handed back; the rest have to be
|
|
* released, or a walk over n nodes with a broken free would consume n pool slots
|
|
* and exhaust the pool -- the failure mode the whole pool-accounting section of
|
|
* TODO.md 1.9 exists to catch.
|
|
*/
|
|
static akerr_ErrorContext AKERR_NOIGNORE *free_that_always_fails(void *ptr)
|
|
{
|
|
PREPARE_ERROR(e);
|
|
failing_free_calls += 1;
|
|
free(ptr);
|
|
FAIL_RETURN(e, AKERR_VALUE, "free refused on call %d", failing_free_calls);
|
|
}
|
|
|
|
/*
|
|
* And one that refuses from a given call onwards, which is what it takes to fail
|
|
* repeatedly *inside* a drain: the dequeues that come first have to succeed, or
|
|
* the walk stops with a real error before it ever reaches the break.
|
|
*/
|
|
static int failing_free_from = 0;
|
|
|
|
static akerr_ErrorContext AKERR_NOIGNORE *free_that_fails_from(void *ptr)
|
|
{
|
|
PREPARE_ERROR(e);
|
|
failing_free_calls += 1;
|
|
free(ptr);
|
|
if ( failing_free_calls >= failing_free_from ) {
|
|
FAIL_RETURN(e, AKERR_VALUE, "free refused on call %d", failing_free_calls);
|
|
}
|
|
SUCCEED_RETURN(e);
|
|
}
|
|
|
|
static int test_free_all_releases_the_errors_it_does_not_return(void)
|
|
{
|
|
aksl_ListNode *head = NULL;
|
|
aksl_ListNode *node = NULL;
|
|
aksl_TreeNode *root = NULL;
|
|
aksl_TreeNode *tnode = NULL;
|
|
static int values[N] = { 5, 3, 8, 1, 9 };
|
|
int i = 0;
|
|
|
|
for ( i = 0; i < N; i++ ) {
|
|
AKSL_CHECK_OK(aksl_malloc(sizeof(aksl_ListNode), (void **)&node));
|
|
AKSL_CHECK_OK(aksl_list_node_init(node, NULL));
|
|
if ( head == NULL ) {
|
|
head = node;
|
|
} else {
|
|
AKSL_CHECK_OK(aksl_list_append(head, node));
|
|
}
|
|
}
|
|
|
|
failing_free_calls = 0;
|
|
/* The first of N errors comes back; the other N-1 must not leak. */
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_list_free_all(&head, &free_that_always_fails),
|
|
AKERR_VALUE, "free refused on call 1");
|
|
AKSL_CHECK(failing_free_calls == N);
|
|
AKSL_CHECK(aksl_slots_in_use() == 0);
|
|
|
|
for ( i = 0; i < N; i++ ) {
|
|
AKSL_CHECK_OK(aksl_malloc(sizeof(aksl_TreeNode), (void **)&tnode));
|
|
AKSL_CHECK_OK(aksl_tree_node_init(tnode, &values[i]));
|
|
AKSL_CHECK_OK(aksl_tree_insert(&root, tnode, &compare_ints));
|
|
}
|
|
|
|
failing_free_calls = 0;
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_tree_free_all(&root, &free_that_always_fails),
|
|
AKERR_VALUE, "free refused on call 1");
|
|
AKSL_CHECK(failing_free_calls == N);
|
|
AKSL_CHECK(aksl_slots_in_use() == 0);
|
|
return 0;
|
|
}
|
|
|
|
static int test_list_free_all_reports_the_first_failure_but_frees_everything(void)
|
|
{
|
|
aksl_ListNode *head = NULL;
|
|
aksl_ListNode *node = NULL;
|
|
int i = 0;
|
|
|
|
for ( i = 0; i < N; i++ ) {
|
|
AKSL_CHECK_OK(aksl_malloc(sizeof(aksl_ListNode), (void **)&node));
|
|
AKSL_CHECK_OK(aksl_list_node_init(node, NULL));
|
|
if ( head == NULL ) {
|
|
head = node;
|
|
} else {
|
|
AKSL_CHECK_OK(aksl_list_append(head, node));
|
|
}
|
|
}
|
|
|
|
failing_free_calls = 0;
|
|
failing_free_countdown = 2; /* fail on the second of five */
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_list_free_all(&head, &free_that_fails_once),
|
|
AKERR_VALUE, "free refused on call 2");
|
|
/* Every node was still visited: the walk did not stop at the failure. */
|
|
AKSL_CHECK(failing_free_calls == N);
|
|
AKSL_CHECK(head == NULL);
|
|
return 0;
|
|
}
|
|
|
|
static int test_tree_free_all_reports_the_first_failure_but_frees_everything(void)
|
|
{
|
|
static int values[5] = { 5, 3, 8, 1, 9 };
|
|
aksl_TreeNode *root = NULL;
|
|
aksl_TreeNode *node = NULL;
|
|
int i = 0;
|
|
|
|
for ( i = 0; i < 5; i++ ) {
|
|
AKSL_CHECK_OK(aksl_malloc(sizeof(aksl_TreeNode), (void **)&node));
|
|
AKSL_CHECK_OK(aksl_tree_node_init(node, &values[i]));
|
|
AKSL_CHECK_OK(aksl_tree_insert(&root, node, &compare_ints));
|
|
}
|
|
|
|
failing_free_calls = 0;
|
|
failing_free_countdown = 2;
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_tree_free_all(&root, &free_that_fails_once),
|
|
AKERR_VALUE, "free refused on call 2");
|
|
AKSL_CHECK(failing_free_calls == 5);
|
|
AKSL_CHECK(root == NULL);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The same idea for the breadth-first traversal queue: a failing lfree during
|
|
* the drain must not stop the drain, and must not mask the error that got the
|
|
* walk there in the first place. The traversal itself still succeeds -- the
|
|
* drain failure is logged and dropped, because losing the caller's real error to
|
|
* report a bookkeeping one would be the worse trade.
|
|
*/
|
|
static aksl_TreeNode *break_on_node = NULL;
|
|
|
|
static akerr_ErrorContext AKERR_NOIGNORE *break_at(aksl_TreeNode *node, void *data)
|
|
{
|
|
PREPARE_ERROR(e);
|
|
(void)data;
|
|
if ( node == break_on_node ) {
|
|
FAIL_RETURN(e, AKERR_ITERATOR_BREAK, "stop here");
|
|
}
|
|
SUCCEED_RETURN(e);
|
|
}
|
|
|
|
static akerr_ErrorContext AKERR_NOIGNORE *plain_alloc(size_t size, void **dest)
|
|
{
|
|
return aksl_malloc(size, dest);
|
|
}
|
|
|
|
static int test_bfs_queue_drain_survives_a_failing_free(void)
|
|
{
|
|
aksl_TreeNode tree[3];
|
|
int i = 0;
|
|
|
|
for ( i = 0; i < 3; i++ ) {
|
|
AKSL_CHECK_OK(aksl_tree_node_init(&tree[i], NULL));
|
|
}
|
|
tree[0].left = &tree[1];
|
|
tree[0].right = &tree[2];
|
|
|
|
/*
|
|
* The lfree calls, in order:
|
|
* 1 the root's queue entry, released as it is dequeued
|
|
* 2 the left child's entry, likewise
|
|
* -- the callback breaks on the left child, leaving the right child queued
|
|
* 3 the right child's entry, released by the drain in CLEANUP
|
|
*
|
|
* Failing on call 3 is therefore a failure inside the drain. The traversal
|
|
* still succeeds: the break is not an error, and losing that answer in order
|
|
* to report a bookkeeping failure would be the worse trade -- so the drain
|
|
* error is logged and dropped, which is what this asserts.
|
|
*/
|
|
break_on_node = &tree[1];
|
|
failing_free_calls = 0;
|
|
failing_free_countdown = 3;
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &break_at,
|
|
&plain_alloc, &free_that_fails_once,
|
|
AKSL_TREE_SEARCH_BFS, NULL));
|
|
AKSL_CHECK(failing_free_calls == 3);
|
|
|
|
/* And the pool is intact afterwards: the dropped context was released. */
|
|
AKSL_CHECK(aksl_slots_in_use() == 0);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* More than one failure inside a single drain, which is the case where the drain
|
|
* has to release the errors it is not keeping. A seven-node tree broken on the
|
|
* third visit leaves two entries queued:
|
|
*
|
|
* dequeue 0 (free 1), enqueue 1 and 2
|
|
* dequeue 1 (free 2), enqueue 3 and 4 queue: 2 3 4
|
|
* dequeue 2 (free 3), callback breaks queue: 3 4
|
|
* drain frees 3 and 4 (free 4, free 5)
|
|
*
|
|
* With a free that refuses every time, the drain raises twice and must return at
|
|
* most one context to be dropped -- keeping both would leak a pool slot per
|
|
* queued node, which over a large tree exhausts the pool outright.
|
|
*/
|
|
static int test_bfs_queue_drain_releases_repeated_failures(void)
|
|
{
|
|
aksl_TreeNode tree[7];
|
|
int i = 0;
|
|
|
|
for ( i = 0; i < 7; i++ ) {
|
|
AKSL_CHECK_OK(aksl_tree_node_init(&tree[i], NULL));
|
|
}
|
|
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];
|
|
|
|
break_on_node = &tree[2];
|
|
failing_free_calls = 0;
|
|
failing_free_from = 4; /* the first drain call */
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &break_at,
|
|
&plain_alloc, &free_that_fails_from,
|
|
AKSL_TREE_SEARCH_BFS, NULL));
|
|
/* Three dequeues plus two drained entries. */
|
|
AKSL_CHECK(failing_free_calls == 5);
|
|
AKSL_CHECK(aksl_slots_in_use() == 0);
|
|
return 0;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* The tracked container */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
/*
|
|
* The reason the container exists: aksl_list_append walks to the tail every
|
|
* time, so building n nodes with it is O(n^2). push is O(1) and the length is a
|
|
* field rather than a walk.
|
|
*/
|
|
static int test_container_push_and_unshift(void)
|
|
{
|
|
aksl_List list;
|
|
aksl_ListNode node[N];
|
|
int i = 0;
|
|
|
|
AKSL_CHECK_OK(aksl_list_init(&list));
|
|
AKSL_CHECK(list.head == NULL && list.tail == NULL && list.length == 0);
|
|
|
|
for ( i = 0; i < N; i++ ) {
|
|
memset((void *)&node[i], 0x00, sizeof(node[i]));
|
|
AKSL_CHECK_OK(aksl_list_push(&list, &node[i]));
|
|
AKSL_CHECK(list.length == (size_t)(i + 1));
|
|
AKSL_CHECK(list.tail == &node[i]);
|
|
}
|
|
AKSL_CHECK(list.head == &node[0]);
|
|
AKSL_CHECK(node[0].prev == NULL);
|
|
AKSL_CHECK(node[N - 1].next == NULL);
|
|
|
|
AKSL_CHECK_OK(aksl_list_init(&list));
|
|
for ( i = 0; i < N; i++ ) {
|
|
memset((void *)&node[i], 0x00, sizeof(node[i]));
|
|
AKSL_CHECK_OK(aksl_list_unshift(&list, &node[i]));
|
|
AKSL_CHECK(list.head == &node[i]);
|
|
AKSL_CHECK(list.tail == &node[0]);
|
|
}
|
|
AKSL_CHECK(list.length == N);
|
|
|
|
AKSL_CHECK_STATUS(aksl_list_init(NULL), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_list_push(NULL, &node[0]), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_list_push(&list, NULL), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_list_unshift(NULL, &node[0]), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_list_unshift(&list, NULL), AKERR_NULLPOINTER);
|
|
return 0;
|
|
}
|
|
|
|
/* Removing keeps head, tail and length describing the list. */
|
|
static int test_container_remove_maintains_the_endpoints(void)
|
|
{
|
|
aksl_List list;
|
|
aksl_ListNode node[3];
|
|
aksl_ListNode stranger;
|
|
int i = 0;
|
|
|
|
AKSL_CHECK_OK(aksl_list_init(&list));
|
|
for ( i = 0; i < 3; i++ ) {
|
|
memset((void *)&node[i], 0x00, sizeof(node[i]));
|
|
AKSL_CHECK_OK(aksl_list_push(&list, &node[i]));
|
|
}
|
|
memset((void *)&stranger, 0x00, sizeof(stranger));
|
|
|
|
/* Middle. */
|
|
AKSL_CHECK_OK(aksl_list_remove(&list, &node[1]));
|
|
AKSL_CHECK(list.length == 2);
|
|
AKSL_CHECK(list.head == &node[0] && list.tail == &node[2]);
|
|
AKSL_CHECK(node[0].next == &node[2] && node[2].prev == &node[0]);
|
|
|
|
/* Head. */
|
|
AKSL_CHECK_OK(aksl_list_remove(&list, &node[0]));
|
|
AKSL_CHECK(list.head == &node[2] && list.tail == &node[2]);
|
|
AKSL_CHECK(list.length == 1);
|
|
|
|
/* Last one out empties both endpoints. */
|
|
AKSL_CHECK_OK(aksl_list_remove(&list, &node[2]));
|
|
AKSL_CHECK(list.head == NULL && list.tail == NULL && list.length == 0);
|
|
|
|
/* A node that is not in this list is refused rather than corrupting it. */
|
|
AKSL_CHECK_OK(aksl_list_push(&list, &node[0]));
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_list_remove(&list, &stranger),
|
|
AKERR_VALUE, "not in this list");
|
|
AKSL_CHECK(list.length == 1);
|
|
|
|
AKSL_CHECK_STATUS(aksl_list_remove(NULL, &node[0]), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_list_remove(&list, NULL), AKERR_NULLPOINTER);
|
|
return 0;
|
|
}
|
|
|
|
static int test_container_clear(void)
|
|
{
|
|
aksl_List list;
|
|
aksl_ListNode *node = NULL;
|
|
int i = 0;
|
|
|
|
AKSL_CHECK_OK(aksl_list_init(&list));
|
|
for ( i = 0; i < N; i++ ) {
|
|
AKSL_CHECK_OK(aksl_malloc(sizeof(aksl_ListNode), (void **)&node));
|
|
AKSL_CHECK_OK(aksl_list_node_init(node, NULL));
|
|
AKSL_CHECK_OK(aksl_list_push(&list, node));
|
|
}
|
|
AKSL_CHECK(list.length == N);
|
|
|
|
AKSL_CHECK_OK(aksl_list_clear(&list, NULL));
|
|
AKSL_CHECK(list.head == NULL && list.tail == NULL && list.length == 0);
|
|
AKSL_CHECK_STATUS(aksl_list_clear(NULL, NULL), AKERR_NULLPOINTER);
|
|
return 0;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Binary search tree */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
/* Compares the ints the leaf pointers point at. */
|
|
static akerr_ErrorContext AKERR_NOIGNORE *compare_ints(void *a, void *b, int *dest)
|
|
{
|
|
PREPARE_ERROR(e);
|
|
FAIL_ZERO_RETURN(e, a, AKERR_NULLPOINTER, "a");
|
|
FAIL_ZERO_RETURN(e, b, AKERR_NULLPOINTER, "b");
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "dest");
|
|
*dest = *(int *)a - *(int *)b;
|
|
SUCCEED_RETURN(e);
|
|
}
|
|
|
|
typedef struct OrderLog
|
|
{
|
|
int count;
|
|
int seen[16];
|
|
} OrderLog;
|
|
|
|
static akerr_ErrorContext AKERR_NOIGNORE *record_leaf(aksl_TreeNode *node, void *data)
|
|
{
|
|
OrderLog *log = NULL;
|
|
|
|
PREPARE_ERROR(e);
|
|
FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "node");
|
|
FAIL_ZERO_RETURN(e, data, AKERR_NULLPOINTER, "data");
|
|
log = (OrderLog *)data;
|
|
if ( log->count < 16 ) {
|
|
log->seen[log->count] = *(int *)node->leaf;
|
|
}
|
|
log->count += 1;
|
|
SUCCEED_RETURN(e);
|
|
}
|
|
|
|
/*
|
|
* Insertion order 5 3 8 1 4 7 9 builds a tree whose in-order walk is sorted --
|
|
* which is the whole invariant a search tree exists to maintain, so asserting it
|
|
* is worth more than asserting any particular shape.
|
|
*/
|
|
static int test_tree_insert_orders_the_leaves(void)
|
|
{
|
|
static int values[7] = { 5, 3, 8, 1, 4, 7, 9 };
|
|
aksl_TreeNode node[7];
|
|
aksl_TreeNode *root = NULL;
|
|
OrderLog log;
|
|
int i = 0;
|
|
|
|
for ( i = 0; i < 7; i++ ) {
|
|
AKSL_CHECK_OK(aksl_tree_node_init(&node[i], &values[i]));
|
|
AKSL_CHECK_OK(aksl_tree_insert(&root, &node[i], &compare_ints));
|
|
}
|
|
AKSL_CHECK(root == &node[0]);
|
|
|
|
memset((void *)&log, 0x00, sizeof(log));
|
|
AKSL_CHECK_OK(aksl_tree_iterate(root, &record_leaf, NULL, NULL,
|
|
AKSL_TREE_SEARCH_DFS_INORDER, &log));
|
|
AKSL_CHECK(log.count == 7);
|
|
for ( i = 1; i < log.count; i++ ) {
|
|
AKSL_CHECK(log.seen[i - 1] < log.seen[i]);
|
|
}
|
|
|
|
AKSL_CHECK_STATUS(aksl_tree_insert(NULL, &node[0], &compare_ints), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_tree_insert(&root, NULL, &compare_ints), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_tree_insert(&root, &node[0], NULL), AKERR_NULLPOINTER);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* TODO.md 2.2.15: aksl_TreeNode.parent was declared and never touched by
|
|
* anything in the library. These are the functions that set it, and
|
|
* aksl_tree_remove is the one that needs it.
|
|
*/
|
|
static int test_tree_insert_sets_the_parent_links(void)
|
|
{
|
|
static int values[3] = { 5, 3, 8 };
|
|
aksl_TreeNode node[3];
|
|
aksl_TreeNode *root = NULL;
|
|
int i = 0;
|
|
|
|
for ( i = 0; i < 3; i++ ) {
|
|
AKSL_CHECK_OK(aksl_tree_node_init(&node[i], &values[i]));
|
|
AKSL_CHECK_OK(aksl_tree_insert(&root, &node[i], &compare_ints));
|
|
}
|
|
|
|
AKSL_CHECK(node[0].parent == NULL); /* the root */
|
|
AKSL_CHECK(node[1].parent == &node[0]);
|
|
AKSL_CHECK(node[2].parent == &node[0]);
|
|
AKSL_CHECK(node[0].left == &node[1]);
|
|
AKSL_CHECK(node[0].right == &node[2]);
|
|
return 0;
|
|
}
|
|
|
|
static int test_tree_find(void)
|
|
{
|
|
static int values[5] = { 5, 3, 8, 1, 9 };
|
|
int wanted = 8;
|
|
int absent = 6;
|
|
aksl_TreeNode node[5];
|
|
aksl_TreeNode *root = NULL;
|
|
aksl_TreeNode *found = (aksl_TreeNode *)0x1;
|
|
int i = 0;
|
|
|
|
for ( i = 0; i < 5; i++ ) {
|
|
AKSL_CHECK_OK(aksl_tree_node_init(&node[i], &values[i]));
|
|
AKSL_CHECK_OK(aksl_tree_insert(&root, &node[i], &compare_ints));
|
|
}
|
|
|
|
AKSL_CHECK_OK(aksl_tree_find(root, &wanted, &compare_ints, &found));
|
|
AKSL_CHECK(found == &node[2]);
|
|
|
|
/* Absent is NULL and success. */
|
|
AKSL_CHECK_OK(aksl_tree_find(root, &absent, &compare_ints, &found));
|
|
AKSL_CHECK(found == NULL);
|
|
|
|
/* An empty tree finds nothing, equally without complaint. */
|
|
AKSL_CHECK_OK(aksl_tree_find(NULL, &wanted, &compare_ints, &found));
|
|
AKSL_CHECK(found == NULL);
|
|
|
|
AKSL_CHECK_STATUS(aksl_tree_find(root, &wanted, NULL, &found), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_tree_find(root, &wanted, &compare_ints, NULL), AKERR_NULLPOINTER);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* All three removal cases, each checked by re-walking the tree and confirming it
|
|
* is still sorted and one node shorter. The two-child case is the interesting
|
|
* one: the in-order successor takes the node's place, which is the only value
|
|
* that keeps the ordering invariant on both sides.
|
|
*/
|
|
static int test_tree_remove_all_three_cases(void)
|
|
{
|
|
static int values[7] = { 5, 3, 8, 1, 4, 7, 9 };
|
|
aksl_TreeNode node[7];
|
|
aksl_TreeNode *root = NULL;
|
|
OrderLog log;
|
|
size_t count = 0;
|
|
int i = 0;
|
|
|
|
for ( i = 0; i < 7; i++ ) {
|
|
AKSL_CHECK_OK(aksl_tree_node_init(&node[i], &values[i]));
|
|
AKSL_CHECK_OK(aksl_tree_insert(&root, &node[i], &compare_ints));
|
|
}
|
|
|
|
/* Leaf: node[3] holds 1 and has no children. */
|
|
AKSL_CHECK_OK(aksl_tree_remove(&root, &node[3]));
|
|
AKSL_CHECK(node[3].parent == NULL && node[3].left == NULL && node[3].right == NULL);
|
|
AKSL_CHECK_OK(aksl_tree_count(root, &count));
|
|
AKSL_CHECK(count == 6);
|
|
|
|
/* One child: node[1] holds 3 and now has only its right child (4). */
|
|
AKSL_CHECK_OK(aksl_tree_remove(&root, &node[1]));
|
|
AKSL_CHECK_OK(aksl_tree_count(root, &count));
|
|
AKSL_CHECK(count == 5);
|
|
|
|
/* Two children: the root, 5, with 4 on the left and 8 on the right. */
|
|
AKSL_CHECK_OK(aksl_tree_remove(&root, &node[0]));
|
|
AKSL_CHECK_OK(aksl_tree_count(root, &count));
|
|
AKSL_CHECK(count == 4);
|
|
AKSL_CHECK(root != &node[0]);
|
|
AKSL_CHECK(root->parent == NULL);
|
|
|
|
/* Still sorted after all of that, which is the invariant that matters. */
|
|
memset((void *)&log, 0x00, sizeof(log));
|
|
AKSL_CHECK_OK(aksl_tree_iterate(root, &record_leaf, NULL, NULL,
|
|
AKSL_TREE_SEARCH_DFS_INORDER, &log));
|
|
AKSL_CHECK(log.count == 4);
|
|
for ( i = 1; i < log.count; i++ ) {
|
|
AKSL_CHECK(log.seen[i - 1] < log.seen[i]);
|
|
}
|
|
|
|
AKSL_CHECK_STATUS(aksl_tree_remove(NULL, &node[0]), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_tree_remove(&root, NULL), AKERR_NULLPOINTER);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The mirror images of the cases above: a node that is its parent's *right*
|
|
* child, and a node whose only child is on the left. Both take different
|
|
* branches through tree_replace, and neither is reached by the test above.
|
|
*/
|
|
static int test_tree_remove_mirrored_shapes(void)
|
|
{
|
|
static int values[4] = { 5, 8, 7, 6 };
|
|
aksl_TreeNode node[4];
|
|
aksl_TreeNode *root = NULL;
|
|
aksl_TreeNode *found = NULL;
|
|
int missing = 8;
|
|
size_t count = 0;
|
|
int i = 0;
|
|
|
|
/*
|
|
* 5 node[0]
|
|
* \
|
|
* 8 node[1], a right child
|
|
* /
|
|
* 7 node[2], whose only child is on the left
|
|
* /
|
|
* 6 node[3]
|
|
*/
|
|
for ( i = 0; i < 4; i++ ) {
|
|
AKSL_CHECK_OK(aksl_tree_node_init(&node[i], &values[i]));
|
|
AKSL_CHECK_OK(aksl_tree_insert(&root, &node[i], &compare_ints));
|
|
}
|
|
AKSL_CHECK(node[0].right == &node[1]);
|
|
AKSL_CHECK(node[1].left == &node[2]);
|
|
AKSL_CHECK(node[2].left == &node[3]);
|
|
|
|
/* A right child with a single left child underneath it. */
|
|
AKSL_CHECK_OK(aksl_tree_remove(&root, &node[1]));
|
|
AKSL_CHECK_OK(aksl_tree_count(root, &count));
|
|
AKSL_CHECK(count == 3);
|
|
AKSL_CHECK_OK(aksl_tree_find(root, &missing, &compare_ints, &found));
|
|
AKSL_CHECK(found == NULL);
|
|
/* 7 took its place as the root's right child. */
|
|
AKSL_CHECK(node[0].right == &node[2]);
|
|
AKSL_CHECK(node[2].parent == &node[0]);
|
|
|
|
/* And now a node whose only child is on the left. */
|
|
AKSL_CHECK_OK(aksl_tree_remove(&root, &node[2]));
|
|
AKSL_CHECK_OK(aksl_tree_count(root, &count));
|
|
AKSL_CHECK(count == 2);
|
|
AKSL_CHECK(node[0].right == &node[3]);
|
|
AKSL_CHECK(node[3].parent == &node[0]);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The two-children case where the successor is not the removed node's own right
|
|
* child, so the successor has to be lifted out of its position first. The other
|
|
* removal test happens to hit the adjacent-successor path.
|
|
*/
|
|
static int test_tree_remove_with_a_distant_successor(void)
|
|
{
|
|
static int values[5] = { 5, 3, 9, 7, 8 };
|
|
aksl_TreeNode node[5];
|
|
aksl_TreeNode *root = NULL;
|
|
OrderLog log;
|
|
int i = 0;
|
|
|
|
/*
|
|
* 5 node[0]
|
|
* / \
|
|
* 3 9 node[1], node[2]
|
|
* /
|
|
* 7 node[3] -- the in-order successor of 5, two levels down
|
|
* \
|
|
* 8 node[4]
|
|
*/
|
|
for ( i = 0; i < 5; i++ ) {
|
|
AKSL_CHECK_OK(aksl_tree_node_init(&node[i], &values[i]));
|
|
AKSL_CHECK_OK(aksl_tree_insert(&root, &node[i], &compare_ints));
|
|
}
|
|
|
|
AKSL_CHECK_OK(aksl_tree_remove(&root, &node[0]));
|
|
/* 7 becomes the root, and its own right child (8) is not lost. */
|
|
AKSL_CHECK(root == &node[3]);
|
|
AKSL_CHECK(root->parent == NULL);
|
|
|
|
memset((void *)&log, 0x00, sizeof(log));
|
|
AKSL_CHECK_OK(aksl_tree_iterate(root, &record_leaf, NULL, NULL,
|
|
AKSL_TREE_SEARCH_DFS_INORDER, &log));
|
|
AKSL_CHECK(log.count == 4);
|
|
for ( i = 1; i < log.count; i++ ) {
|
|
AKSL_CHECK(log.seen[i - 1] < log.seen[i]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Removing the last node empties the tree rather than leaving a dangling root. */
|
|
static int test_tree_remove_the_only_node(void)
|
|
{
|
|
int value = 1;
|
|
aksl_TreeNode node;
|
|
aksl_TreeNode *root = NULL;
|
|
size_t count = 99;
|
|
|
|
AKSL_CHECK_OK(aksl_tree_node_init(&node, &value));
|
|
AKSL_CHECK_OK(aksl_tree_insert(&root, &node, &compare_ints));
|
|
AKSL_CHECK_OK(aksl_tree_remove(&root, &node));
|
|
AKSL_CHECK(root == NULL);
|
|
AKSL_CHECK_OK(aksl_tree_count(root, &count));
|
|
AKSL_CHECK(count == 0);
|
|
return 0;
|
|
}
|
|
|
|
static int test_tree_height_and_count(void)
|
|
{
|
|
static int values[7] = { 5, 3, 8, 1, 4, 7, 9 };
|
|
static int chain[4] = { 1, 2, 3, 4 };
|
|
aksl_TreeNode node[7];
|
|
aksl_TreeNode *root = NULL;
|
|
size_t count = 99;
|
|
int height = 99;
|
|
int i = 0;
|
|
|
|
/* Empty. */
|
|
AKSL_CHECK_OK(aksl_tree_height(NULL, &height));
|
|
AKSL_CHECK(height == 0);
|
|
AKSL_CHECK_OK(aksl_tree_count(NULL, &count));
|
|
AKSL_CHECK(count == 0);
|
|
|
|
/* Balanced by construction: 7 nodes, 3 levels. */
|
|
for ( i = 0; i < 7; i++ ) {
|
|
AKSL_CHECK_OK(aksl_tree_node_init(&node[i], &values[i]));
|
|
AKSL_CHECK_OK(aksl_tree_insert(&root, &node[i], &compare_ints));
|
|
}
|
|
AKSL_CHECK_OK(aksl_tree_count(root, &count));
|
|
AKSL_CHECK(count == 7);
|
|
AKSL_CHECK_OK(aksl_tree_height(root, &height));
|
|
AKSL_CHECK(height == 3);
|
|
|
|
/* Sorted input gives a degenerate chain: 4 nodes, 4 levels. This is a plain
|
|
* unbalanced BST and does not pretend otherwise. */
|
|
root = NULL;
|
|
for ( i = 0; i < 4; i++ ) {
|
|
AKSL_CHECK_OK(aksl_tree_node_init(&node[i], &chain[i]));
|
|
AKSL_CHECK_OK(aksl_tree_insert(&root, &node[i], &compare_ints));
|
|
}
|
|
AKSL_CHECK_OK(aksl_tree_height(root, &height));
|
|
AKSL_CHECK(height == 4);
|
|
|
|
AKSL_CHECK_STATUS(aksl_tree_height(root, NULL), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_tree_count(root, NULL), AKERR_NULLPOINTER);
|
|
return 0;
|
|
}
|
|
|
|
static int test_tree_free_all(void)
|
|
{
|
|
static int values[5] = { 5, 3, 8, 1, 9 };
|
|
aksl_TreeNode *root = NULL;
|
|
aksl_TreeNode *node = NULL;
|
|
int i = 0;
|
|
|
|
for ( i = 0; i < 5; i++ ) {
|
|
AKSL_CHECK_OK(aksl_malloc(sizeof(aksl_TreeNode), (void **)&node));
|
|
AKSL_CHECK_OK(aksl_tree_node_init(node, &values[i]));
|
|
AKSL_CHECK_OK(aksl_tree_insert(&root, node, &compare_ints));
|
|
}
|
|
|
|
AKSL_CHECK_OK(aksl_tree_free_all(&root, NULL));
|
|
AKSL_CHECK(root == NULL);
|
|
|
|
/* An empty tree frees cleanly and a second call is a no-op. */
|
|
AKSL_CHECK_OK(aksl_tree_free_all(&root, NULL));
|
|
AKSL_CHECK_STATUS(aksl_tree_free_all(NULL, NULL), AKERR_NULLPOINTER);
|
|
return 0;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int failures = 0;
|
|
|
|
akerr_init();
|
|
|
|
AKSL_RUN(failures, test_prepend_moves_the_head);
|
|
AKSL_RUN(failures, test_insert_after_and_before);
|
|
AKSL_RUN(failures, test_insert_before_a_middle_node);
|
|
AKSL_RUN(failures, test_length_counts_and_refuses_cycles);
|
|
AKSL_RUN(failures, test_find_returns_the_first_match_or_null);
|
|
AKSL_RUN(failures, test_reverse_flips_both_directions);
|
|
AKSL_RUN(failures, test_concat_joins_two_lists);
|
|
AKSL_RUN(failures, test_iterate_reverse_walks_back_to_the_head);
|
|
AKSL_RUN(failures, test_free_all_releases_every_node);
|
|
AKSL_RUN(failures, test_free_all_releases_the_errors_it_does_not_return);
|
|
AKSL_RUN(failures, test_list_free_all_reports_the_first_failure_but_frees_everything);
|
|
AKSL_RUN(failures, test_tree_free_all_reports_the_first_failure_but_frees_everything);
|
|
AKSL_RUN(failures, test_bfs_queue_drain_survives_a_failing_free);
|
|
AKSL_RUN(failures, test_bfs_queue_drain_releases_repeated_failures);
|
|
|
|
AKSL_RUN(failures, test_container_push_and_unshift);
|
|
AKSL_RUN(failures, test_container_remove_maintains_the_endpoints);
|
|
AKSL_RUN(failures, test_container_clear);
|
|
|
|
AKSL_RUN(failures, test_tree_insert_orders_the_leaves);
|
|
AKSL_RUN(failures, test_tree_insert_sets_the_parent_links);
|
|
AKSL_RUN(failures, test_tree_find);
|
|
AKSL_RUN(failures, test_tree_remove_all_three_cases);
|
|
AKSL_RUN(failures, test_tree_remove_mirrored_shapes);
|
|
AKSL_RUN(failures, test_tree_remove_with_a_distant_successor);
|
|
AKSL_RUN(failures, test_tree_remove_the_only_node);
|
|
AKSL_RUN(failures, test_tree_height_and_count);
|
|
AKSL_RUN(failures, test_tree_free_all);
|
|
|
|
AKSL_REPORT(failures);
|
|
}
|