731 lines
24 KiB
C
731 lines
24 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;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* 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);
|
||
|
|
}
|