TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
644 lines
19 KiB
C
644 lines
19 KiB
C
/*
|
|
* Linked list -- TODO.md section 1.7, complete.
|
|
*
|
|
* The two confirmed list defects are fixed, so the tests that used to live in
|
|
* tests/test_list_append_chain.c and tests/test_list_iterate_head.c are folded
|
|
* back in here: aksl_list_append builds the whole chain rather than truncating
|
|
* it at the midpoint (2.1.1), and aksl_list_iterate starts at the head rather
|
|
* than at whatever node Floyd's slow pointer happened to stop on (2.1.2).
|
|
*
|
|
* The chain assertions build their lists with aksl_list_append now, which is the
|
|
* point -- they could not, while append was the thing under suspicion.
|
|
*/
|
|
|
|
#include "aksl_capture.h"
|
|
|
|
#define MAX_VISITS 16
|
|
#define CHAIN_LEN 5
|
|
|
|
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_node_init */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
/*
|
|
* TODO.md 2.2.14: every caller used to have to remember to memset a node before
|
|
* its first use, and a stack node that skipped it walked straight into garbage.
|
|
*/
|
|
static int test_node_init_zeroes_the_links(void)
|
|
{
|
|
aksl_ListNode node;
|
|
int payload = 7;
|
|
|
|
memset((void *)&node, 0xff, sizeof(node));
|
|
AKSL_CHECK_OK(aksl_list_node_init(&node, &payload));
|
|
AKSL_CHECK(node.next == NULL);
|
|
AKSL_CHECK(node.prev == NULL);
|
|
AKSL_CHECK(node.data == (void *)&payload);
|
|
|
|
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
|
AKSL_CHECK(node.data == NULL);
|
|
AKSL_CHECK_STATUS(aksl_list_node_init(NULL, NULL), AKERR_NULLPOINTER);
|
|
return 0;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* aksl_list_append */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static int test_append_single_node(void)
|
|
{
|
|
aksl_ListNode head;
|
|
aksl_ListNode tail;
|
|
|
|
AKSL_CHECK_OK(aksl_list_node_init(&head, NULL));
|
|
AKSL_CHECK_OK(aksl_list_node_init(&tail, NULL));
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* The defect that made this library's list unusable: `tail` was assigned from
|
|
* Floyd's `slow` cursor *before* slow advanced, so it tracked the node behind
|
|
* the midpoint rather than the last node. Appending n1..n4 to n0 produced the
|
|
* chain "n0 -> n4" and silently dropped n1, n2 and n3. TODO.md 2.1.1.
|
|
*/
|
|
static int test_append_builds_the_whole_chain(void)
|
|
{
|
|
aksl_ListNode node[CHAIN_LEN];
|
|
aksl_ListNode *walk = NULL;
|
|
int i = 0;
|
|
|
|
for ( i = 0; i < CHAIN_LEN; i++ ) {
|
|
AKSL_CHECK_OK(aksl_list_node_init(&node[i], NULL));
|
|
}
|
|
for ( i = 1; i < CHAIN_LEN; i++ ) {
|
|
AKSL_CHECK_OK(aksl_list_append(&node[0], &node[i]));
|
|
}
|
|
|
|
/* Forward links, head to tail. */
|
|
walk = &node[0];
|
|
for ( i = 0; i < CHAIN_LEN; i++ ) {
|
|
AKSL_CHECK(walk == &node[i]);
|
|
walk = walk->next;
|
|
}
|
|
AKSL_CHECK(walk == NULL);
|
|
|
|
/* Back links, tail to head. */
|
|
walk = &node[CHAIN_LEN - 1];
|
|
for ( i = CHAIN_LEN - 1; i >= 0; i-- ) {
|
|
AKSL_CHECK(walk == &node[i]);
|
|
walk = walk->prev;
|
|
}
|
|
AKSL_CHECK(walk == NULL);
|
|
return 0;
|
|
}
|
|
|
|
/* append points obj->prev at the real tail and terminates the chain at obj. */
|
|
static int test_append_links_prev_to_the_real_tail(void)
|
|
{
|
|
aksl_ListNode node[4];
|
|
int i = 0;
|
|
|
|
for ( i = 0; i < 4; i++ ) {
|
|
AKSL_CHECK_OK(aksl_list_node_init(&node[i], NULL));
|
|
}
|
|
for ( i = 1; i < 4; i++ ) {
|
|
AKSL_CHECK_OK(aksl_list_append(&node[0], &node[i]));
|
|
AKSL_CHECK(node[i].prev == &node[i - 1]);
|
|
AKSL_CHECK(node[i].next == NULL);
|
|
AKSL_CHECK(node[i - 1].next == &node[i]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int test_append_null_arguments(void)
|
|
{
|
|
aksl_ListNode node;
|
|
|
|
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
|
|
|
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;
|
|
|
|
AKSL_CHECK_OK(aksl_list_node_init(&head, NULL));
|
|
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
|
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;
|
|
|
|
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
|
AKSL_CHECK_OK(aksl_list_node_init(&b, NULL));
|
|
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
|
a.next = &b;
|
|
b.prev = &a;
|
|
b.next = &a;
|
|
|
|
AKSL_CHECK_STATUS(aksl_list_append(&a, &node), AKERR_CIRCULAR_REFERENCE);
|
|
return 0;
|
|
}
|
|
|
|
/* A cycle that does not include the head: a -> b -> c -> b. */
|
|
static int test_append_detects_cycle_below_the_head(void)
|
|
{
|
|
aksl_ListNode a;
|
|
aksl_ListNode b;
|
|
aksl_ListNode c;
|
|
aksl_ListNode node;
|
|
|
|
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
|
AKSL_CHECK_OK(aksl_list_node_init(&b, NULL));
|
|
AKSL_CHECK_OK(aksl_list_node_init(&c, NULL));
|
|
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
|
a.next = &b;
|
|
b.prev = &a;
|
|
b.next = &c;
|
|
c.prev = &b;
|
|
c.next = &b;
|
|
|
|
AKSL_CHECK_STATUS(aksl_list_append(&a, &node), AKERR_CIRCULAR_REFERENCE);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* TODO.md 1.7 asked for the aliasing contract to be defined. It is refusal:
|
|
* relinking a node that is already in the list would orphan everything between
|
|
* its old position and the tail, so the tail walk -- which happens anyway --
|
|
* doubles as the check.
|
|
*/
|
|
static int test_append_refuses_a_node_already_in_the_list(void)
|
|
{
|
|
aksl_ListNode node[3];
|
|
int i = 0;
|
|
|
|
for ( i = 0; i < 3; i++ ) {
|
|
AKSL_CHECK_OK(aksl_list_node_init(&node[i], NULL));
|
|
}
|
|
AKSL_CHECK_OK(aksl_list_append(&node[0], &node[1]));
|
|
AKSL_CHECK_OK(aksl_list_append(&node[0], &node[2]));
|
|
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_list_append(&node[0], &node[1]),
|
|
AKERR_VALUE, "already in this list");
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_list_append(&node[0], &node[0]),
|
|
AKERR_VALUE, "already the head");
|
|
/* The list is untouched by the refusal. */
|
|
AKSL_CHECK(node[0].next == &node[1]);
|
|
AKSL_CHECK(node[1].next == &node[2]);
|
|
AKSL_CHECK(node[2].next == NULL);
|
|
return 0;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* aksl_list_iterate */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static int test_iterate_null_arguments(void)
|
|
{
|
|
aksl_ListNode node;
|
|
VisitLog log;
|
|
|
|
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
|
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;
|
|
|
|
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Every node, exactly once, in order, starting at the head. The cycle check
|
|
* leaves Floyd's `slow` cursor at the list midpoint, and the visiting loop used
|
|
* to start from there -- so the whole first half of the list, head included, was
|
|
* never passed to the callback at all. TODO.md 2.1.2.
|
|
*/
|
|
static int test_iterate_visits_every_node_from_the_head(void)
|
|
{
|
|
aksl_ListNode node[CHAIN_LEN];
|
|
VisitLog log;
|
|
int i = 0;
|
|
|
|
for ( i = 0; i < CHAIN_LEN; i++ ) {
|
|
AKSL_CHECK_OK(aksl_list_node_init(&node[i], NULL));
|
|
}
|
|
for ( i = 1; i < CHAIN_LEN; i++ ) {
|
|
AKSL_CHECK_OK(aksl_list_append(&node[0], &node[i]));
|
|
}
|
|
visitlog_init(&log);
|
|
|
|
AKSL_CHECK_OK(aksl_list_iterate(&node[0], &record_visit, &log));
|
|
AKSL_CHECK(log.count == CHAIN_LEN);
|
|
for ( i = 0; i < CHAIN_LEN; i++ ) {
|
|
AKSL_CHECK(log.seen[i] == &node[i]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* An even-length list too: Floyd's midpoint lands differently, the answer does not. */
|
|
static int test_iterate_visits_every_node_of_an_even_list(void)
|
|
{
|
|
aksl_ListNode node[4];
|
|
VisitLog log;
|
|
int i = 0;
|
|
|
|
for ( i = 0; i < 4; i++ ) {
|
|
AKSL_CHECK_OK(aksl_list_node_init(&node[i], NULL));
|
|
}
|
|
for ( i = 1; i < 4; i++ ) {
|
|
AKSL_CHECK_OK(aksl_list_append(&node[0], &node[i]));
|
|
}
|
|
visitlog_init(&log);
|
|
|
|
AKSL_CHECK_OK(aksl_list_iterate(&node[0], &record_visit, &log));
|
|
AKSL_CHECK(log.count == 4);
|
|
for ( i = 0; i < 4; i++ ) {
|
|
AKSL_CHECK(log.seen[i] == &node[i]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int test_iterate_detects_self_cycle(void)
|
|
{
|
|
aksl_ListNode head;
|
|
VisitLog log;
|
|
|
|
AKSL_CHECK_OK(aksl_list_node_init(&head, NULL));
|
|
head.next = &head;
|
|
visitlog_init(&log);
|
|
|
|
AKSL_CHECK_STATUS(aksl_list_iterate(&head, &record_visit, &log),
|
|
AKERR_CIRCULAR_REFERENCE);
|
|
AKSL_CHECK(log.count == 0);
|
|
return 0;
|
|
}
|
|
|
|
static int test_iterate_detects_two_node_cycle(void)
|
|
{
|
|
aksl_ListNode a;
|
|
aksl_ListNode b;
|
|
VisitLog log;
|
|
|
|
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
|
AKSL_CHECK_OK(aksl_list_node_init(&b, NULL));
|
|
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;
|
|
}
|
|
|
|
/* Tail pointing back into the middle: a -> b -> c -> b. */
|
|
static int test_iterate_detects_tail_to_middle_cycle(void)
|
|
{
|
|
aksl_ListNode a;
|
|
aksl_ListNode b;
|
|
aksl_ListNode c;
|
|
VisitLog log;
|
|
|
|
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
|
AKSL_CHECK_OK(aksl_list_node_init(&b, NULL));
|
|
AKSL_CHECK_OK(aksl_list_node_init(&c, NULL));
|
|
a.next = &b;
|
|
b.prev = &a;
|
|
b.next = &c;
|
|
c.prev = &b;
|
|
c.next = &b;
|
|
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;
|
|
|
|
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
|
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;
|
|
|
|
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
|
visitlog_init(&log);
|
|
log.break_at = 0;
|
|
|
|
AKSL_CHECK_OK(aksl_list_iterate(&node, &record_visit, &log));
|
|
AKSL_CHECK(log.count == 1);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* And the count is what proves it stopped early rather than merely finishing.
|
|
* A break on the second of five nodes must leave three nodes unvisited.
|
|
*/
|
|
static int test_iterate_break_stops_at_that_node(void)
|
|
{
|
|
aksl_ListNode node[CHAIN_LEN];
|
|
VisitLog log;
|
|
int i = 0;
|
|
|
|
for ( i = 0; i < CHAIN_LEN; i++ ) {
|
|
AKSL_CHECK_OK(aksl_list_node_init(&node[i], NULL));
|
|
}
|
|
for ( i = 1; i < CHAIN_LEN; i++ ) {
|
|
AKSL_CHECK_OK(aksl_list_append(&node[0], &node[i]));
|
|
}
|
|
visitlog_init(&log);
|
|
log.break_at = 1;
|
|
|
|
AKSL_CHECK_OK(aksl_list_iterate(&node[0], &record_visit, &log));
|
|
AKSL_CHECK(log.count == 2);
|
|
AKSL_CHECK(log.seen[0] == &node[0]);
|
|
AKSL_CHECK(log.seen[1] == &node[1]);
|
|
return 0;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* aksl_list_pop */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static int test_pop_middle_node(void)
|
|
{
|
|
aksl_ListNode a;
|
|
aksl_ListNode b;
|
|
aksl_ListNode c;
|
|
aksl_ListNode *head = &a;
|
|
|
|
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
|
AKSL_CHECK_OK(aksl_list_node_init(&b, NULL));
|
|
AKSL_CHECK_OK(aksl_list_node_init(&c, NULL));
|
|
AKSL_CHECK_OK(aksl_list_append(&a, &b));
|
|
AKSL_CHECK_OK(aksl_list_append(&a, &c));
|
|
|
|
AKSL_CHECK_OK(aksl_list_pop(&head, &b));
|
|
AKSL_CHECK(head == &a);
|
|
AKSL_CHECK(a.next == &c);
|
|
AKSL_CHECK(c.prev == &a);
|
|
AKSL_CHECK(b.next == NULL);
|
|
AKSL_CHECK(b.prev == NULL);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* TODO.md 2.2.12: popping the head used to leave the caller's own head pointer
|
|
* aimed at a node that was no longer in the list, with no way to learn the new
|
|
* one. That is what the head out-param is for, and this is the assertion.
|
|
*/
|
|
static int test_pop_head_node_moves_the_head(void)
|
|
{
|
|
aksl_ListNode a;
|
|
aksl_ListNode b;
|
|
aksl_ListNode *head = &a;
|
|
|
|
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
|
AKSL_CHECK_OK(aksl_list_node_init(&b, NULL));
|
|
AKSL_CHECK_OK(aksl_list_append(&a, &b));
|
|
|
|
AKSL_CHECK_OK(aksl_list_pop(&head, &a));
|
|
AKSL_CHECK(head == &b);
|
|
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;
|
|
aksl_ListNode *head = &a;
|
|
|
|
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
|
AKSL_CHECK_OK(aksl_list_node_init(&b, NULL));
|
|
AKSL_CHECK_OK(aksl_list_append(&a, &b));
|
|
|
|
AKSL_CHECK_OK(aksl_list_pop(&head, &b));
|
|
AKSL_CHECK(head == &a);
|
|
AKSL_CHECK(a.next == NULL);
|
|
AKSL_CHECK(a.prev == NULL);
|
|
AKSL_CHECK(b.next == NULL);
|
|
AKSL_CHECK(b.prev == NULL);
|
|
return 0;
|
|
}
|
|
|
|
/* Popping the only node empties the list, and the head becomes NULL. */
|
|
static int test_pop_only_node_empties_the_list(void)
|
|
{
|
|
aksl_ListNode a;
|
|
aksl_ListNode *head = &a;
|
|
|
|
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
|
|
|
AKSL_CHECK_OK(aksl_list_pop(&head, &a));
|
|
AKSL_CHECK(head == NULL);
|
|
AKSL_CHECK(a.next == NULL);
|
|
AKSL_CHECK(a.prev == NULL);
|
|
return 0;
|
|
}
|
|
|
|
static int test_pop_null_arguments(void)
|
|
{
|
|
aksl_ListNode a;
|
|
aksl_ListNode *head = &a;
|
|
|
|
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
|
AKSL_CHECK_STATUS(aksl_list_pop(NULL, &a), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_list_pop(&head, NULL), AKERR_NULLPOINTER);
|
|
return 0;
|
|
}
|
|
|
|
/* The list is still walkable after a pop, and the popped node is gone from it. */
|
|
static int test_pop_then_iterate(void)
|
|
{
|
|
aksl_ListNode node[4];
|
|
aksl_ListNode *head = &node[0];
|
|
VisitLog log;
|
|
int i = 0;
|
|
|
|
for ( i = 0; i < 4; i++ ) {
|
|
AKSL_CHECK_OK(aksl_list_node_init(&node[i], NULL));
|
|
}
|
|
for ( i = 1; i < 4; i++ ) {
|
|
AKSL_CHECK_OK(aksl_list_append(&node[0], &node[i]));
|
|
}
|
|
|
|
AKSL_CHECK_OK(aksl_list_pop(&head, &node[2]));
|
|
visitlog_init(&log);
|
|
AKSL_CHECK_OK(aksl_list_iterate(head, &record_visit, &log));
|
|
AKSL_CHECK(log.count == 3);
|
|
AKSL_CHECK(log.seen[0] == &node[0]);
|
|
AKSL_CHECK(log.seen[1] == &node[1]);
|
|
AKSL_CHECK(log.seen[2] == &node[3]);
|
|
|
|
/* And again, this time taking the head out. */
|
|
AKSL_CHECK_OK(aksl_list_pop(&head, &node[0]));
|
|
visitlog_init(&log);
|
|
AKSL_CHECK_OK(aksl_list_iterate(head, &record_visit, &log));
|
|
AKSL_CHECK(log.count == 2);
|
|
AKSL_CHECK(log.seen[0] == &node[1]);
|
|
AKSL_CHECK(log.seen[1] == &node[3]);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* TODO.md 1.7's pool-accounting case. AKSL_RUN already asserts that each test
|
|
* leaves the pool as it found it; this one drives enough failures in a row to
|
|
* exhaust the pool several times over, which is where a wrapper that raises an
|
|
* error and forgets to release it shows up as an outright exhaustion rather than
|
|
* as a slow leak.
|
|
*/
|
|
static int test_error_pool_survives_a_long_run_of_failures(void)
|
|
{
|
|
aksl_ListNode a;
|
|
aksl_ListNode b;
|
|
aksl_ListNode *head = &a;
|
|
VisitLog log;
|
|
int i = 0;
|
|
|
|
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
|
AKSL_CHECK_OK(aksl_list_node_init(&b, NULL));
|
|
a.next = &a;
|
|
|
|
for ( i = 0; i < AKERR_MAX_ARRAY_ERROR + 10; i++ ) {
|
|
AKSL_CHECK_STATUS(aksl_list_append(&a, &b), AKERR_CIRCULAR_REFERENCE);
|
|
AKSL_CHECK_STATUS(aksl_list_append(NULL, &b), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_list_pop(&head, NULL), AKERR_NULLPOINTER);
|
|
visitlog_init(&log);
|
|
AKSL_CHECK_STATUS(aksl_list_iterate(&a, &record_visit, &log),
|
|
AKERR_CIRCULAR_REFERENCE);
|
|
AKSL_CHECK(aksl_slots_in_use() == 0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int failures = 0;
|
|
|
|
akerr_init();
|
|
|
|
AKSL_RUN(failures, test_node_init_zeroes_the_links);
|
|
|
|
AKSL_RUN(failures, test_append_single_node);
|
|
AKSL_RUN(failures, test_append_builds_the_whole_chain);
|
|
AKSL_RUN(failures, test_append_links_prev_to_the_real_tail);
|
|
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_append_detects_cycle_below_the_head);
|
|
AKSL_RUN(failures, test_append_refuses_a_node_already_in_the_list);
|
|
|
|
AKSL_RUN(failures, test_iterate_null_arguments);
|
|
AKSL_RUN(failures, test_iterate_single_node);
|
|
AKSL_RUN(failures, test_iterate_visits_every_node_from_the_head);
|
|
AKSL_RUN(failures, test_iterate_visits_every_node_of_an_even_list);
|
|
AKSL_RUN(failures, test_iterate_detects_self_cycle);
|
|
AKSL_RUN(failures, test_iterate_detects_two_node_cycle);
|
|
AKSL_RUN(failures, test_iterate_detects_tail_to_middle_cycle);
|
|
AKSL_RUN(failures, test_iterate_propagates_callback_error);
|
|
AKSL_RUN(failures, test_iterate_break_is_not_an_error);
|
|
AKSL_RUN(failures, test_iterate_break_stops_at_that_node);
|
|
|
|
AKSL_RUN(failures, test_pop_middle_node);
|
|
AKSL_RUN(failures, test_pop_head_node_moves_the_head);
|
|
AKSL_RUN(failures, test_pop_tail_node);
|
|
AKSL_RUN(failures, test_pop_only_node_empties_the_list);
|
|
AKSL_RUN(failures, test_pop_null_arguments);
|
|
AKSL_RUN(failures, test_pop_then_iterate);
|
|
|
|
AKSL_RUN(failures, test_error_pool_survives_a_long_run_of_failures);
|
|
|
|
AKSL_REPORT(failures);
|
|
}
|