Build a real test harness (TODO.md section 1.0)
The suite could not fail and did not run. test_linkedlist.c asserted nothing at all -- it printed node names and returned 0 -- so every confirmed list defect passed it. test_tree.c was red on every run because parms.steps was never reset between its three searches, and four libakerror tests registered but never built, reporting Not Run. CI, meanwhile, was not fetching the submodule, so configure failed before reaching any of it. - tests/aksl_capture.h: AKSL_CHECK (NDEBUG-proof), AKSL_CHECK_STATUS/_OK to run an akerror-returning call, assert its status and release the context, a capturing akerr_log_method, aksl_slots_in_use(), and an AKSL_RUN driver that fails any test leaking an error-pool slot. - test_linkedlist.c: rewritten as 15 assertion cases over the append, iterate and pop behaviour that is correct today. - test_tree.c: each search builds its own tree and params. - Registration driven by AKSL_TESTS, plus AKSL_WILL_FAIL_TESTS (aborts by design) and AKSL_KNOWN_FAILING_TESTS, which marks WILL_FAIL the three new tests asserting correct behaviour for the confirmed defects in TODO.md 2.1.1-2.1.3. Fixing a defect flips its test to "unexpectedly passed", which is the cue to promote it into AKSL_TESTS. - add_test/set_tests_properties are shadowed across the libakerror add_subdirectory call: CMake cannot un-register a test and set_tests_properties cannot cross directory scopes. The dependency has its own CI. - AKSL_SANITIZE=ON builds library, tests and dependency with ASan+UBSan. - scripts/mutation_test.py ported and retargeted at src/stdlib.c; wired to a manual `mutation` target, not a CI gate until 1.1-1.9 exist. - CI: checkout with submodules: recursive, and ctest --output-on-failure. ctest is now 5/5 green in both the default and sanitizer builds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,75 +1,319 @@
|
||||
#include <stdio.h>
|
||||
#include <akstdlib.h>
|
||||
/*
|
||||
* Linked-list behaviour that the library gets right today.
|
||||
*
|
||||
* The two confirmed list defects (TODO.md 2.1.1 aksl_list_append truncating the
|
||||
* chain, and 2.1.2 aksl_list_iterate skipping the head) are asserted separately
|
||||
* in tests/test_list_append_chain.c and tests/test_list_iterate_head.c, which
|
||||
* are registered as known-failing. Everything in this file must pass.
|
||||
*
|
||||
* Note that the list-shape assertions here build their lists by hand rather
|
||||
* than with aksl_list_append: append cannot be trusted to produce a chain
|
||||
* longer than two nodes until 2.1.1 is fixed, and using it would make these
|
||||
* tests fail for a reason that has nothing to do with what they are checking.
|
||||
*/
|
||||
|
||||
#include "aksl_capture.h"
|
||||
|
||||
// This iterator does nothing but print the node names it is visiting
|
||||
akerr_ErrorContext AKERR_NOIGNORE *myiter(aksl_ListNode *node, void *data)
|
||||
#define MAX_VISITS 16
|
||||
|
||||
typedef struct VisitLog
|
||||
{
|
||||
int count = 0;
|
||||
int count;
|
||||
aksl_ListNode *seen[MAX_VISITS];
|
||||
int break_at; /* visit index to raise ITERATOR_BREAK on, or -1 */
|
||||
int fail_at; /* visit index to raise AKERR_VALUE on, or -1 */
|
||||
} VisitLog;
|
||||
|
||||
static void visitlog_init(VisitLog *log)
|
||||
{
|
||||
memset((void *)log, 0x00, sizeof(VisitLog));
|
||||
log->break_at = -1;
|
||||
log->fail_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, node->data, AKERR_NULLPOINTER, "node->data");
|
||||
PASS(e, aksl_fprintf(&count, stderr, "Visiting node : %s\n", node->data));
|
||||
FAIL_ZERO_RETURN(e, data, AKERR_NULLPOINTER, "data");
|
||||
log = (VisitLog *)data;
|
||||
idx = log->count;
|
||||
if ( idx < MAX_VISITS ) {
|
||||
log->seen[idx] = node;
|
||||
}
|
||||
log->count += 1;
|
||||
if ( log->fail_at == idx ) {
|
||||
FAIL_RETURN(e, AKERR_VALUE, "iterator failed at visit %d", idx);
|
||||
}
|
||||
if ( log->break_at == idx ) {
|
||||
FAIL_RETURN(e, AKERR_ITERATOR_BREAK, "stop at visit %d", idx);
|
||||
}
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
// This iterator function exits early once the index in `(int *)data` is reached
|
||||
akerr_ErrorContext AKERR_NOIGNORE *myiter_earlyhalt(aksl_ListNode *node, void *data)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* aksl_list_append */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
static int test_append_single_node(void)
|
||||
{
|
||||
int *idx;
|
||||
int count = 0;
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "node");
|
||||
FAIL_ZERO_RETURN(e, node->data, AKERR_NULLPOINTER, "node->data");
|
||||
FAIL_ZERO_RETURN(e, data, AKERR_NULLPOINTER, "data");
|
||||
idx = (int *)data;
|
||||
if ( *idx == 1 ) {
|
||||
// This exception is eaten by the iterator, we will never see it
|
||||
FAIL_RETURN(e, AKERR_ITERATOR_BREAK, "stop");
|
||||
}
|
||||
*idx += 1;
|
||||
SUCCEED_RETURN(e);
|
||||
aksl_ListNode head;
|
||||
aksl_ListNode tail;
|
||||
|
||||
memset((void *)&head, 0x00, sizeof(head));
|
||||
memset((void *)&tail, 0x00, sizeof(tail));
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_append(&head, &tail));
|
||||
AKSL_CHECK(head.next == &tail);
|
||||
AKSL_CHECK(head.prev == NULL);
|
||||
AKSL_CHECK(tail.prev == &head);
|
||||
AKSL_CHECK(tail.next == NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_append_null_arguments(void)
|
||||
{
|
||||
aksl_ListNode node;
|
||||
|
||||
memset((void *)&node, 0x00, sizeof(node));
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_list_append(NULL, &node), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_list_append(&node, NULL), AKERR_NULLPOINTER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_append_detects_self_cycle(void)
|
||||
{
|
||||
aksl_ListNode head;
|
||||
aksl_ListNode node;
|
||||
|
||||
memset((void *)&head, 0x00, sizeof(head));
|
||||
memset((void *)&node, 0x00, sizeof(node));
|
||||
head.next = &head;
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_list_append(&head, &node), AKERR_CIRCULAR_REFERENCE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_append_detects_two_node_cycle(void)
|
||||
{
|
||||
aksl_ListNode a;
|
||||
aksl_ListNode b;
|
||||
aksl_ListNode node;
|
||||
|
||||
memset((void *)&a, 0x00, sizeof(a));
|
||||
memset((void *)&b, 0x00, sizeof(b));
|
||||
memset((void *)&node, 0x00, sizeof(node));
|
||||
a.next = &b;
|
||||
b.prev = &a;
|
||||
b.next = &a;
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_list_append(&a, &node), AKERR_CIRCULAR_REFERENCE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* aksl_list_iterate */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
static int test_iterate_null_arguments(void)
|
||||
{
|
||||
aksl_ListNode node;
|
||||
VisitLog log;
|
||||
|
||||
memset((void *)&node, 0x00, sizeof(node));
|
||||
visitlog_init(&log);
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_list_iterate(NULL, &record_visit, &log), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_list_iterate(&node, NULL, &log), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK(log.count == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_iterate_single_node(void)
|
||||
{
|
||||
aksl_ListNode node;
|
||||
VisitLog log;
|
||||
|
||||
memset((void *)&node, 0x00, sizeof(node));
|
||||
visitlog_init(&log);
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_iterate(&node, &record_visit, &log));
|
||||
AKSL_CHECK(log.count == 1);
|
||||
AKSL_CHECK(log.seen[0] == &node);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_iterate_detects_self_cycle(void)
|
||||
{
|
||||
aksl_ListNode head;
|
||||
VisitLog log;
|
||||
|
||||
memset((void *)&head, 0x00, sizeof(head));
|
||||
head.next = &head;
|
||||
visitlog_init(&log);
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_list_iterate(&head, &record_visit, &log),
|
||||
AKERR_CIRCULAR_REFERENCE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_iterate_detects_two_node_cycle(void)
|
||||
{
|
||||
aksl_ListNode a;
|
||||
aksl_ListNode b;
|
||||
VisitLog log;
|
||||
|
||||
memset((void *)&a, 0x00, sizeof(a));
|
||||
memset((void *)&b, 0x00, sizeof(b));
|
||||
a.next = &b;
|
||||
b.prev = &a;
|
||||
b.next = &a;
|
||||
visitlog_init(&log);
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_list_iterate(&a, &record_visit, &log),
|
||||
AKERR_CIRCULAR_REFERENCE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* An error other than ITERATOR_BREAK must come back out of the iteration with
|
||||
* its status and message intact. */
|
||||
static int test_iterate_propagates_callback_error(void)
|
||||
{
|
||||
aksl_ListNode node;
|
||||
VisitLog log;
|
||||
|
||||
memset((void *)&node, 0x00, sizeof(node));
|
||||
visitlog_init(&log);
|
||||
log.fail_at = 0;
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_list_iterate(&node, &record_visit, &log), AKERR_VALUE);
|
||||
AKSL_CHECK_MSG_CONTAINS("iterator failed at visit 0");
|
||||
AKSL_CHECK(log.count == 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ITERATOR_BREAK is a control signal, not a failure: the caller sees success. */
|
||||
static int test_iterate_break_is_not_an_error(void)
|
||||
{
|
||||
aksl_ListNode node;
|
||||
VisitLog log;
|
||||
|
||||
memset((void *)&node, 0x00, sizeof(node));
|
||||
visitlog_init(&log);
|
||||
log.break_at = 0;
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_iterate(&node, &record_visit, &log));
|
||||
AKSL_CHECK(log.count == 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* aksl_list_pop */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
static int test_pop_middle_node(void)
|
||||
{
|
||||
aksl_ListNode a;
|
||||
aksl_ListNode b;
|
||||
aksl_ListNode c;
|
||||
|
||||
memset((void *)&a, 0x00, sizeof(a));
|
||||
memset((void *)&b, 0x00, sizeof(b));
|
||||
memset((void *)&c, 0x00, sizeof(c));
|
||||
a.next = &b;
|
||||
b.prev = &a;
|
||||
b.next = &c;
|
||||
c.prev = &b;
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_pop(&b));
|
||||
AKSL_CHECK(a.next == &c);
|
||||
AKSL_CHECK(c.prev == &a);
|
||||
AKSL_CHECK(b.next == NULL);
|
||||
AKSL_CHECK(b.prev == NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_pop_head_node(void)
|
||||
{
|
||||
aksl_ListNode a;
|
||||
aksl_ListNode b;
|
||||
|
||||
memset((void *)&a, 0x00, sizeof(a));
|
||||
memset((void *)&b, 0x00, sizeof(b));
|
||||
a.next = &b;
|
||||
b.prev = &a;
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_pop(&a));
|
||||
AKSL_CHECK(b.prev == NULL);
|
||||
AKSL_CHECK(b.next == NULL);
|
||||
AKSL_CHECK(a.next == NULL);
|
||||
AKSL_CHECK(a.prev == NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_pop_tail_node(void)
|
||||
{
|
||||
aksl_ListNode a;
|
||||
aksl_ListNode b;
|
||||
|
||||
memset((void *)&a, 0x00, sizeof(a));
|
||||
memset((void *)&b, 0x00, sizeof(b));
|
||||
a.next = &b;
|
||||
b.prev = &a;
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_pop(&b));
|
||||
AKSL_CHECK(a.next == NULL);
|
||||
AKSL_CHECK(a.prev == NULL);
|
||||
AKSL_CHECK(b.next == NULL);
|
||||
AKSL_CHECK(b.prev == NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_pop_only_node(void)
|
||||
{
|
||||
aksl_ListNode a;
|
||||
|
||||
memset((void *)&a, 0x00, sizeof(a));
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_pop(&a));
|
||||
AKSL_CHECK(a.next == NULL);
|
||||
AKSL_CHECK(a.prev == NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_pop_null_argument(void)
|
||||
{
|
||||
AKSL_CHECK_STATUS(aksl_list_pop(NULL), AKERR_NULLPOINTER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
int idx = 0;
|
||||
int count = 0;
|
||||
aksl_ListNode mylist;
|
||||
aksl_ListNode node1;
|
||||
aksl_ListNode node2;
|
||||
int failures = 0;
|
||||
|
||||
ATTEMPT {
|
||||
memset((void *)&mylist, 0x00, sizeof(aksl_ListNode));
|
||||
memset((void *)&node1, 0x00, sizeof(aksl_ListNode));
|
||||
memset((void *)&node2, 0x00, sizeof(aksl_ListNode));
|
||||
akerr_init();
|
||||
|
||||
mylist.data = "Root";
|
||||
|
||||
node1.data = "Node 1";
|
||||
CATCH(e, aksl_list_append(&mylist, &node1));
|
||||
|
||||
node2.data = "Node 2";
|
||||
CATCH(e, aksl_list_append(&mylist, &node2));
|
||||
AKSL_RUN(failures, test_append_single_node);
|
||||
AKSL_RUN(failures, test_append_null_arguments);
|
||||
AKSL_RUN(failures, test_append_detects_self_cycle);
|
||||
AKSL_RUN(failures, test_append_detects_two_node_cycle);
|
||||
|
||||
// Iterate over all nodes in the list using the myiter() function
|
||||
CATCH(e, aksl_list_iterate(&mylist, &myiter, NULL));
|
||||
AKSL_RUN(failures, test_iterate_null_arguments);
|
||||
AKSL_RUN(failures, test_iterate_single_node);
|
||||
AKSL_RUN(failures, test_iterate_detects_self_cycle);
|
||||
AKSL_RUN(failures, test_iterate_detects_two_node_cycle);
|
||||
AKSL_RUN(failures, test_iterate_propagates_callback_error);
|
||||
AKSL_RUN(failures, test_iterate_break_is_not_an_error);
|
||||
|
||||
// Iterate over up to the first 2 nodes in the list and then exit early
|
||||
idx = 0;
|
||||
CATCH(e, aksl_list_iterate(&mylist, &myiter_earlyhalt, &idx));
|
||||
CATCH(e, aksl_fprintf(&count, stderr, "Iterator exited early at index %d\n", idx));
|
||||
AKSL_RUN(failures, test_pop_middle_node);
|
||||
AKSL_RUN(failures, test_pop_head_node);
|
||||
AKSL_RUN(failures, test_pop_tail_node);
|
||||
AKSL_RUN(failures, test_pop_only_node);
|
||||
AKSL_RUN(failures, test_pop_null_argument);
|
||||
|
||||
// Break the list with a circular reference, and iterate it again
|
||||
node2.next = &mylist;
|
||||
CATCH(e, aksl_list_iterate(&mylist, &myiter, NULL));
|
||||
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} HANDLE(e, AKERR_CIRCULAR_REFERENCE) {
|
||||
fprintf(stderr, "Circular reference error caught\n");
|
||||
} FINISH_NORETURN(e);
|
||||
|
||||
return 0;
|
||||
AKSL_REPORT(failures);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user