/* * 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(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) { 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); }