All checks were successful
Eighty-five comments cited section numbers -- 1.1, 2.2.6, 3.6 -- from a numbering the file had already abandoned before the move to the tracker. They label completed work, so the pointer was the only wrong part. The citation is removed and the sentence kept, which is what issue #27 recommended: these are labels, not references, and a label carrying a version-dependent pointer goes stale again at the next reorganisation. Where a pointer earns its place it names what actually holds the content now -- UPGRADING.md for the confirmed defects, libakerror #15 for the target namespacing, issue #7 for the mutation survivors. README.md and akstdlib.h sent readers to TODO.md for 'what is still open'; they name the tracker. Verified: cmake --build build && ctest --test-dir build, 19/19. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
644 lines
19 KiB
C
644 lines
19 KiB
C
/*
|
|
* Linked list.
|
|
*
|
|
* 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 */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* The wrapper plan 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.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* The wrapper plan'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);
|
|
}
|