Files
libakstdlib/tests/test_linkedlist.c
Andrew Kesterson 01734f511b
All checks were successful
libakstdlib CI Build / cmake_build (push) Successful in 2m46s
libakstdlib CI Build / mutation_test (push) Successful in 8m1s
Make error-status assertions authoritative
Replace standalone message assertions with a helper that checks the returned akerr status first, then validates message content only as secondary context. Drop ambiguous memcpy message checks where both NULL guards use the same format string.
2026-07-29 15:47:17 -04:00

321 lines
8.4 KiB
C

/*
* 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"
#define MAX_VISITS 16
typedef struct VisitLog
{
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, 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);
}
/* ---------------------------------------------------------------------- */
/* aksl_list_append */
/* ---------------------------------------------------------------------- */
static int test_append_single_node(void)
{
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_MSG_CONTAINS(
aksl_list_iterate(&node, &record_visit, &log),
AKERR_VALUE, "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)
{
int failures = 0;
akerr_init();
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);
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);
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);
AKSL_REPORT(failures);
}