Fix the six confirmed defects and close the API contract gaps
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>
This commit is contained in:
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Linked-list behaviour that the library gets right today.
|
||||
* Linked list -- TODO.md section 1.7, complete.
|
||||
*
|
||||
* 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.
|
||||
* 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).
|
||||
*
|
||||
* 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.
|
||||
* 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
|
||||
{
|
||||
@@ -54,6 +54,31 @@ static akerr_ErrorContext AKERR_NOIGNORE *record_visit(aksl_ListNode *node, void
|
||||
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 */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
@@ -63,8 +88,8 @@ 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_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);
|
||||
@@ -74,11 +99,66 @@ static int test_append_single_node(void)
|
||||
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;
|
||||
|
||||
memset((void *)&node, 0x00, sizeof(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);
|
||||
@@ -90,8 +170,8 @@ 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));
|
||||
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);
|
||||
@@ -104,9 +184,9 @@ static int test_append_detects_two_node_cycle(void)
|
||||
aksl_ListNode b;
|
||||
aksl_ListNode node;
|
||||
|
||||
memset((void *)&a, 0x00, sizeof(a));
|
||||
memset((void *)&b, 0x00, sizeof(b));
|
||||
memset((void *)&node, 0x00, sizeof(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;
|
||||
@@ -115,6 +195,56 @@ static int test_append_detects_two_node_cycle(void)
|
||||
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 */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
@@ -124,7 +254,7 @@ static int test_iterate_null_arguments(void)
|
||||
aksl_ListNode node;
|
||||
VisitLog log;
|
||||
|
||||
memset((void *)&node, 0x00, sizeof(node));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
||||
visitlog_init(&log);
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_list_iterate(NULL, &record_visit, &log), AKERR_NULLPOINTER);
|
||||
@@ -138,7 +268,7 @@ static int test_iterate_single_node(void)
|
||||
aksl_ListNode node;
|
||||
VisitLog log;
|
||||
|
||||
memset((void *)&node, 0x00, sizeof(node));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
||||
visitlog_init(&log);
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_iterate(&node, &record_visit, &log));
|
||||
@@ -147,17 +277,69 @@ static int test_iterate_single_node(void)
|
||||
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;
|
||||
|
||||
memset((void *)&head, 0x00, sizeof(head));
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -167,8 +349,8 @@ static int test_iterate_detects_two_node_cycle(void)
|
||||
aksl_ListNode b;
|
||||
VisitLog log;
|
||||
|
||||
memset((void *)&a, 0x00, sizeof(a));
|
||||
memset((void *)&b, 0x00, sizeof(b));
|
||||
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;
|
||||
@@ -179,6 +361,29 @@ static int test_iterate_detects_two_node_cycle(void)
|
||||
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)
|
||||
@@ -186,13 +391,13 @@ static int test_iterate_propagates_callback_error(void)
|
||||
aksl_ListNode node;
|
||||
VisitLog log;
|
||||
|
||||
memset((void *)&node, 0x00, sizeof(node));
|
||||
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_list_iterate(&node, &record_visit, &log),
|
||||
AKERR_VALUE, "iterator failed at visit 0");
|
||||
AKSL_CHECK(log.count == 1);
|
||||
return 0;
|
||||
}
|
||||
@@ -203,7 +408,7 @@ static int test_iterate_break_is_not_an_error(void)
|
||||
aksl_ListNode node;
|
||||
VisitLog log;
|
||||
|
||||
memset((void *)&node, 0x00, sizeof(node));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
||||
visitlog_init(&log);
|
||||
log.break_at = 0;
|
||||
|
||||
@@ -212,6 +417,32 @@ static int test_iterate_break_is_not_an_error(void)
|
||||
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 */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
@@ -221,16 +452,16 @@ static int test_pop_middle_node(void)
|
||||
aksl_ListNode a;
|
||||
aksl_ListNode b;
|
||||
aksl_ListNode c;
|
||||
aksl_ListNode *head = &a;
|
||||
|
||||
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_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(&b));
|
||||
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);
|
||||
@@ -238,17 +469,23 @@ static int test_pop_middle_node(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_pop_head_node(void)
|
||||
/*
|
||||
* 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;
|
||||
|
||||
memset((void *)&a, 0x00, sizeof(a));
|
||||
memset((void *)&b, 0x00, sizeof(b));
|
||||
a.next = &b;
|
||||
b.prev = &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(&a));
|
||||
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);
|
||||
@@ -260,13 +497,14 @@ static int test_pop_tail_node(void)
|
||||
{
|
||||
aksl_ListNode a;
|
||||
aksl_ListNode b;
|
||||
aksl_ListNode *head = &a;
|
||||
|
||||
memset((void *)&a, 0x00, sizeof(a));
|
||||
memset((void *)&b, 0x00, sizeof(b));
|
||||
a.next = &b;
|
||||
b.prev = &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(&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);
|
||||
@@ -274,21 +512,93 @@ static int test_pop_tail_node(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_pop_only_node(void)
|
||||
/* 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;
|
||||
|
||||
memset((void *)&a, 0x00, sizeof(a));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_pop(&a));
|
||||
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_argument(void)
|
||||
static int test_pop_null_arguments(void)
|
||||
{
|
||||
AKSL_CHECK_STATUS(aksl_list_pop(NULL), AKERR_NULLPOINTER);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -298,23 +608,36 @@ int main(void)
|
||||
|
||||
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);
|
||||
AKSL_RUN(failures, test_pop_head_node_moves_the_head);
|
||||
AKSL_RUN(failures, test_pop_tail_node);
|
||||
AKSL_RUN(failures, test_pop_only_node);
|
||||
AKSL_RUN(failures, test_pop_null_argument);
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user