/* * 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); }