Wrap the strings, streams and collections the wishlist asked for
TODO.md section 3.1's high-priority list and section 3.6's data
structures. This is the surface akbasic went without: it makes 10 calls
into this library and 116 to raw libc, and 69 of those 116 are strlen,
strcmp, strncpy and strstr.
Three new translation units, because src/stdlib.c covering four times
what it did would stop being readable:
src/string.c lengths, bounded copy and concatenation, duplication,
comparison including the case-insensitive forms,
searching, the reentrant tokenisers, and a status
message that knows this library's own statuses as
well as errno's.
src/stream.c positioning, flushing and buffering, character and
line I/O, stream state, freopen/fdopen/tmpfile,
formatted input, and the file operations.
src/collections.c the list functions that were missing, a head/tail
container so append is O(1), a binary search tree,
FNV-1a, a fixed-capacity hash map and a growable
string buffer.
Two conventions run through all of it. The copying functions take the
destination size even where the libc function they are named for does
not, because strcpy(3) cannot be called safely without it, and
truncation is an error that writes nothing rather than a plausible
prefix -- this is the idiom akbasic writes out by hand at ten sites.
The searching functions treat "not found" as a successful answer of
NULL, because absent is an answer and raising on it would make every
caller handle a non-error.
The hash map is akbasic's src/symtab.c generalised: open-addressed with
linear probing over a caller-supplied slot array, keys copied into fixed
slots so the map owns them, tombstones on delete so a removal cannot cut
a probe chain, and a refusal rather than a resize when full.
Tests: 16 binaries, green under the normal and sanitizer builds. The
scanf wrappers take the number of conversions the caller expects, since
comparing scanf(3)'s return against that by hand is the check everyone
eventually forgets.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
730
tests/test_collections.c
Normal file
730
tests/test_collections.c
Normal file
@@ -0,0 +1,730 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* 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;
|
||||
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");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* 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_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_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_the_only_node);
|
||||
AKSL_RUN(failures, test_tree_height_and_count);
|
||||
AKSL_RUN(failures, test_tree_free_all);
|
||||
|
||||
AKSL_REPORT(failures);
|
||||
}
|
||||
Reference in New Issue
Block a user