Version at 0.2.0: complete the wishlist, document it, gate the docs
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>
This commit is contained in:
@@ -149,6 +149,29 @@ static int test_insert_after_and_before(void)
|
||||
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 */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
@@ -177,6 +200,7 @@ 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;
|
||||
|
||||
@@ -199,6 +223,19 @@ static int test_find_returns_the_first_match_or_null(void)
|
||||
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;
|
||||
@@ -336,6 +373,257 @@ static int test_free_all_releases_every_node(void)
|
||||
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 */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
@@ -619,6 +907,99 @@ static int test_tree_remove_all_three_cases(void)
|
||||
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)
|
||||
{
|
||||
@@ -707,12 +1088,18 @@ int main(void)
|
||||
|
||||
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);
|
||||
@@ -722,6 +1109,8 @@ int main(void)
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user