Build a real test harness (TODO.md section 1.0)
The suite could not fail and did not run. test_linkedlist.c asserted nothing at all -- it printed node names and returned 0 -- so every confirmed list defect passed it. test_tree.c was red on every run because parms.steps was never reset between its three searches, and four libakerror tests registered but never built, reporting Not Run. CI, meanwhile, was not fetching the submodule, so configure failed before reaching any of it. - tests/aksl_capture.h: AKSL_CHECK (NDEBUG-proof), AKSL_CHECK_STATUS/_OK to run an akerror-returning call, assert its status and release the context, a capturing akerr_log_method, aksl_slots_in_use(), and an AKSL_RUN driver that fails any test leaking an error-pool slot. - test_linkedlist.c: rewritten as 15 assertion cases over the append, iterate and pop behaviour that is correct today. - test_tree.c: each search builds its own tree and params. - Registration driven by AKSL_TESTS, plus AKSL_WILL_FAIL_TESTS (aborts by design) and AKSL_KNOWN_FAILING_TESTS, which marks WILL_FAIL the three new tests asserting correct behaviour for the confirmed defects in TODO.md 2.1.1-2.1.3. Fixing a defect flips its test to "unexpectedly passed", which is the cue to promote it into AKSL_TESTS. - add_test/set_tests_properties are shadowed across the libakerror add_subdirectory call: CMake cannot un-register a test and set_tests_properties cannot cross directory scopes. The dependency has its own CI. - AKSL_SANITIZE=ON builds library, tests and dependency with ASan+UBSan. - scripts/mutation_test.py ported and retargeted at src/stdlib.c; wired to a manual `mutation` target, not a CI gate until 1.1-1.9 exist. - CI: checkout with submodules: recursive, and ctest --output-on-failure. ctest is now 5/5 green in both the default and sanitizer builds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
216
tests/aksl_capture.h
Normal file
216
tests/aksl_capture.h
Normal file
@@ -0,0 +1,216 @@
|
||||
#ifndef AKSL_TEST_CAPTURE_H
|
||||
#define AKSL_TEST_CAPTURE_H
|
||||
|
||||
/*
|
||||
* Shared test helpers for libakstdlib.
|
||||
*
|
||||
* Modelled on libakerror's tests/err_capture.h, with additions for the shape of
|
||||
* this library: almost every akstdlib entry point returns an
|
||||
* akerr_ErrorContext * that the caller owns and must release, so the common
|
||||
* assertion is "this call returned status X" rather than "this call logged Y".
|
||||
*
|
||||
* What is here:
|
||||
*
|
||||
* AKSL_CHECK() an NDEBUG-proof assertion. Fails the test by
|
||||
* returning 1 from the enclosing function (unlike
|
||||
* assert(), which is compiled out in release builds
|
||||
* and would silently turn a test into a no-op).
|
||||
* AKSL_CHECK_STATUS() run an akerror-returning expression, assert on the
|
||||
* status it came back with, and release the context
|
||||
* so the error pool does not leak.
|
||||
* AKSL_CHECK_OK() the status == 0 (success) case of the above.
|
||||
* aksl_last_status/... the status, message and function name of the most
|
||||
* recent context taken by AKSL_CHECK_STATUS.
|
||||
* aksl_capture_install() swap in a capturing akerr_log_method so a test can
|
||||
* assert on the *content* of stack traces and
|
||||
* unhandled-error output.
|
||||
* aksl_slots_in_use() how many slots are currently checked out of
|
||||
* AKERR_ARRAY_ERROR, for pool-leak assertions.
|
||||
* AKSL_RUN() run one test function and tally the result.
|
||||
*
|
||||
* Tests are written as a set of `static int test_xxx(void)` functions that
|
||||
* return 0 on success and non-zero on failure, driven from main() by AKSL_RUN.
|
||||
*/
|
||||
|
||||
#include <akstdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Log capture */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
#define AKSL_CAPTURE_BUFSZ 65536
|
||||
static char aksl_capture_buf[AKSL_CAPTURE_BUFSZ];
|
||||
static size_t aksl_capture_len = 0;
|
||||
|
||||
static void __attribute__((unused)) aksl_capture_logger(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int n = vsnprintf(aksl_capture_buf + aksl_capture_len,
|
||||
AKSL_CAPTURE_BUFSZ - aksl_capture_len, fmt, ap);
|
||||
va_end(ap);
|
||||
if ( n > 0 ) {
|
||||
aksl_capture_len += (size_t)n;
|
||||
if ( aksl_capture_len >= AKSL_CAPTURE_BUFSZ ) {
|
||||
aksl_capture_len = AKSL_CAPTURE_BUFSZ - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void __attribute__((unused)) aksl_capture_reset(void)
|
||||
{
|
||||
aksl_capture_len = 0;
|
||||
aksl_capture_buf[0] = '\0';
|
||||
}
|
||||
|
||||
/*
|
||||
* Install the capturing logger. akerr_init() only assigns a default logger when
|
||||
* akerr_log_method is NULL, and it is idempotent, so calling this either before
|
||||
* or after the first PREPARE_ERROR keeps our logger in place.
|
||||
*/
|
||||
static void __attribute__((unused)) aksl_capture_install(void)
|
||||
{
|
||||
aksl_capture_reset();
|
||||
akerr_log_method = &aksl_capture_logger;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Error pool accounting */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/* Count array slots currently checked out of the pool (refcount != 0). */
|
||||
static int __attribute__((unused)) aksl_slots_in_use(void)
|
||||
{
|
||||
int n = 0;
|
||||
for ( int i = 0; i < AKERR_MAX_ARRAY_ERROR; i++ ) {
|
||||
if ( AKERR_ARRAY_ERROR[i].refcount != 0 ) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Taking ownership of a returned error context */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
static int aksl_last_status = 0;
|
||||
static char aksl_last_message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH];
|
||||
/* Sized to match akerr_ErrorContext.function, which akerror declares with
|
||||
* AKERR_MAX_ERROR_FNAME_LENGTH rather than AKERR_MAX_ERROR_FUNCTION_LENGTH. */
|
||||
static char aksl_last_function[AKERR_MAX_ERROR_FNAME_LENGTH];
|
||||
|
||||
/*
|
||||
* Record the status/message/function of a returned context, release it back to
|
||||
* the pool, and hand back the status. A NULL context means success, which is
|
||||
* status 0. Every akstdlib call in a test should go through this (or through
|
||||
* AKSL_CHECK_STATUS, which wraps it) so that no test leaks a pool slot.
|
||||
*/
|
||||
static int __attribute__((unused)) aksl_take(akerr_ErrorContext *e)
|
||||
{
|
||||
akerr_ErrorContext *released = NULL;
|
||||
|
||||
aksl_last_message[0] = '\0';
|
||||
aksl_last_function[0] = '\0';
|
||||
if ( e == NULL ) {
|
||||
aksl_last_status = 0;
|
||||
return 0;
|
||||
}
|
||||
aksl_last_status = e->status;
|
||||
snprintf(aksl_last_message, sizeof(aksl_last_message), "%s", e->message);
|
||||
snprintf(aksl_last_function, sizeof(aksl_last_function), "%s", e->function);
|
||||
/*
|
||||
* akerr_release_error is marked warn_unused_result, and a (void) cast does
|
||||
* not silence that in GCC, so the result is assigned and discarded.
|
||||
*/
|
||||
released = akerr_release_error(e);
|
||||
(void)released;
|
||||
return aksl_last_status;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Assertions */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
#define AKSL_CHECK(cond) \
|
||||
do { \
|
||||
if ( !(cond) ) { \
|
||||
fprintf(stderr, " CHECK FAILED: %s at %s:%d\n", \
|
||||
#cond, __FILE__, __LINE__); \
|
||||
return 1; \
|
||||
} \
|
||||
} while ( 0 )
|
||||
|
||||
/*
|
||||
* Run an akerr_ErrorContext *-returning expression and assert on its status.
|
||||
* The context is always released, including on the failure path, so a failing
|
||||
* assertion does not also corrupt the pool-leak checks that follow it.
|
||||
*/
|
||||
#define AKSL_CHECK_STATUS(__expr, __expected) \
|
||||
do { \
|
||||
int __st = aksl_take(__expr); \
|
||||
if ( __st != (__expected) ) { \
|
||||
fprintf(stderr, \
|
||||
" CHECK FAILED: %s\n" \
|
||||
" got %d (%s) \"%s\"\n" \
|
||||
" expected %d (%s)\n" \
|
||||
" at %s:%d\n", \
|
||||
#__expr, \
|
||||
__st, akerr_name_for_status(__st, NULL), \
|
||||
aksl_last_message, \
|
||||
(__expected), \
|
||||
akerr_name_for_status((__expected), NULL), \
|
||||
__FILE__, __LINE__); \
|
||||
return 1; \
|
||||
} \
|
||||
} while ( 0 )
|
||||
|
||||
#define AKSL_CHECK_OK(__expr) AKSL_CHECK_STATUS(__expr, 0)
|
||||
|
||||
#define AKSL_CHECK_CONTAINS(needle) \
|
||||
AKSL_CHECK(strstr(aksl_capture_buf, (needle)) != NULL)
|
||||
|
||||
#define AKSL_CHECK_NOT_CONTAINS(needle) \
|
||||
AKSL_CHECK(strstr(aksl_capture_buf, (needle)) == NULL)
|
||||
|
||||
/* Assert on the message of the context most recently taken. */
|
||||
#define AKSL_CHECK_MSG_CONTAINS(needle) \
|
||||
AKSL_CHECK(strstr(aksl_last_message, (needle)) != NULL)
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Test driver */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* Run one test function, report it, and tally failures. Also asserts that the
|
||||
* test left the error pool as it found it -- a wrapper that fails to release a
|
||||
* context is a bug in the library, not just in the test.
|
||||
*/
|
||||
#define AKSL_RUN(__failures, __fn) \
|
||||
do { \
|
||||
int __before = aksl_slots_in_use(); \
|
||||
int __r = __fn(); \
|
||||
int __after = aksl_slots_in_use(); \
|
||||
if ( __r != 0 ) { \
|
||||
fprintf(stderr, "FAIL %s\n", #__fn); \
|
||||
(__failures)++; \
|
||||
} else if ( __after != __before ) { \
|
||||
fprintf(stderr, \
|
||||
"FAIL %s (leaked %d error pool slot(s))\n", \
|
||||
#__fn, __after - __before); \
|
||||
(__failures)++; \
|
||||
} else { \
|
||||
fprintf(stderr, "ok %s\n", #__fn); \
|
||||
} \
|
||||
} while ( 0 )
|
||||
|
||||
#define AKSL_REPORT(__failures) \
|
||||
do { \
|
||||
fprintf(stderr, "%s: %d failure(s)\n", __FILE__, (__failures)); \
|
||||
return (__failures) == 0 ? 0 : 1; \
|
||||
} while ( 0 )
|
||||
|
||||
#endif // AKSL_TEST_CAPTURE_H
|
||||
@@ -1,75 +1,319 @@
|
||||
#include <stdio.h>
|
||||
#include <akstdlib.h>
|
||||
/*
|
||||
* 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"
|
||||
|
||||
// This iterator does nothing but print the node names it is visiting
|
||||
akerr_ErrorContext AKERR_NOIGNORE *myiter(aksl_ListNode *node, void *data)
|
||||
#define MAX_VISITS 16
|
||||
|
||||
typedef struct VisitLog
|
||||
{
|
||||
int count = 0;
|
||||
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, node->data, AKERR_NULLPOINTER, "node->data");
|
||||
PASS(e, aksl_fprintf(&count, stderr, "Visiting node : %s\n", node->data));
|
||||
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);
|
||||
}
|
||||
|
||||
// This iterator function exits early once the index in `(int *)data` is reached
|
||||
akerr_ErrorContext AKERR_NOIGNORE *myiter_earlyhalt(aksl_ListNode *node, void *data)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* aksl_list_append */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
static int test_append_single_node(void)
|
||||
{
|
||||
int *idx;
|
||||
int count = 0;
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "node");
|
||||
FAIL_ZERO_RETURN(e, node->data, AKERR_NULLPOINTER, "node->data");
|
||||
FAIL_ZERO_RETURN(e, data, AKERR_NULLPOINTER, "data");
|
||||
idx = (int *)data;
|
||||
if ( *idx == 1 ) {
|
||||
// This exception is eaten by the iterator, we will never see it
|
||||
FAIL_RETURN(e, AKERR_ITERATOR_BREAK, "stop");
|
||||
}
|
||||
*idx += 1;
|
||||
SUCCEED_RETURN(e);
|
||||
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)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
int idx = 0;
|
||||
int count = 0;
|
||||
aksl_ListNode mylist;
|
||||
aksl_ListNode node1;
|
||||
aksl_ListNode node2;
|
||||
int failures = 0;
|
||||
|
||||
ATTEMPT {
|
||||
memset((void *)&mylist, 0x00, sizeof(aksl_ListNode));
|
||||
memset((void *)&node1, 0x00, sizeof(aksl_ListNode));
|
||||
memset((void *)&node2, 0x00, sizeof(aksl_ListNode));
|
||||
akerr_init();
|
||||
|
||||
mylist.data = "Root";
|
||||
|
||||
node1.data = "Node 1";
|
||||
CATCH(e, aksl_list_append(&mylist, &node1));
|
||||
|
||||
node2.data = "Node 2";
|
||||
CATCH(e, aksl_list_append(&mylist, &node2));
|
||||
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);
|
||||
|
||||
// Iterate over all nodes in the list using the myiter() function
|
||||
CATCH(e, aksl_list_iterate(&mylist, &myiter, NULL));
|
||||
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);
|
||||
|
||||
// Iterate over up to the first 2 nodes in the list and then exit early
|
||||
idx = 0;
|
||||
CATCH(e, aksl_list_iterate(&mylist, &myiter_earlyhalt, &idx));
|
||||
CATCH(e, aksl_fprintf(&count, stderr, "Iterator exited early at index %d\n", idx));
|
||||
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);
|
||||
|
||||
// Break the list with a circular reference, and iterate it again
|
||||
node2.next = &mylist;
|
||||
CATCH(e, aksl_list_iterate(&mylist, &myiter, NULL));
|
||||
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} HANDLE(e, AKERR_CIRCULAR_REFERENCE) {
|
||||
fprintf(stderr, "Circular reference error caught\n");
|
||||
} FINISH_NORETURN(e);
|
||||
|
||||
return 0;
|
||||
AKSL_REPORT(failures);
|
||||
}
|
||||
|
||||
59
tests/test_list_append_chain.c
Normal file
59
tests/test_list_append_chain.c
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* KNOWN FAILING -- TODO.md section 2.1.1
|
||||
*
|
||||
* aksl_list_append conflates Floyd cycle detection with finding the tail: the
|
||||
* `tail` cursor is assigned from `slow` *before* `slow` advances, so it tracks
|
||||
* the node behind the list midpoint rather than the last node. Appending to any
|
||||
* list of two or more nodes therefore overwrites an interior link and silently
|
||||
* drops every node after it.
|
||||
*
|
||||
* Appending n1..n4 to n0 produces the chain "n0 -> n4"; this test asserts the
|
||||
* correct "n0 -> n1 -> n2 -> n3 -> n4" and so fails until append is fixed.
|
||||
* It is registered in AKSL_KNOWN_FAILING_TESTS, which marks it WILL_FAIL; when
|
||||
* the fix lands, CTest will report it as unexpectedly passing, which is the
|
||||
* signal to move it into AKSL_TESTS.
|
||||
*/
|
||||
|
||||
#include "aksl_capture.h"
|
||||
|
||||
#define CHAIN_LEN 5
|
||||
|
||||
static int test_append_builds_full_chain(void)
|
||||
{
|
||||
aksl_ListNode node[CHAIN_LEN];
|
||||
aksl_ListNode *walk = NULL;
|
||||
int i = 0;
|
||||
|
||||
memset((void *)node, 0x00, sizeof(node));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int failures = 0;
|
||||
|
||||
akerr_init();
|
||||
AKSL_RUN(failures, test_append_builds_full_chain);
|
||||
AKSL_REPORT(failures);
|
||||
}
|
||||
71
tests/test_list_iterate_head.c
Normal file
71
tests/test_list_iterate_head.c
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* KNOWN FAILING -- TODO.md section 2.1.2
|
||||
*
|
||||
* aksl_list_iterate runs a Floyd cycle check that leaves `slow` sitting at the
|
||||
* list midpoint, and then starts the visiting loop from `slow` instead of from
|
||||
* `list`. Every node before the midpoint -- including the head -- is never
|
||||
* passed to the callback.
|
||||
*
|
||||
* The list here is built by hand rather than with aksl_list_append so that this
|
||||
* test fails only for the iterate defect, not for the separate append defect in
|
||||
* TODO.md 2.1.1.
|
||||
*
|
||||
* Registered in AKSL_KNOWN_FAILING_TESTS (WILL_FAIL). When iterate is fixed,
|
||||
* CTest reports this as unexpectedly passing -- move it into AKSL_TESTS then.
|
||||
*/
|
||||
|
||||
#include "aksl_capture.h"
|
||||
|
||||
#define CHAIN_LEN 3
|
||||
|
||||
typedef struct VisitLog
|
||||
{
|
||||
int count;
|
||||
aksl_ListNode *seen[CHAIN_LEN];
|
||||
} VisitLog;
|
||||
|
||||
static akerr_ErrorContext AKERR_NOIGNORE *record_visit(aksl_ListNode *node, void *data)
|
||||
{
|
||||
VisitLog *log = NULL;
|
||||
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "node");
|
||||
FAIL_ZERO_RETURN(e, data, AKERR_NULLPOINTER, "data");
|
||||
log = (VisitLog *)data;
|
||||
if ( log->count < CHAIN_LEN ) {
|
||||
log->seen[log->count] = node;
|
||||
}
|
||||
log->count += 1;
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
static int test_iterate_visits_every_node_from_the_head(void)
|
||||
{
|
||||
aksl_ListNode node[CHAIN_LEN];
|
||||
VisitLog log;
|
||||
int i = 0;
|
||||
|
||||
memset((void *)node, 0x00, sizeof(node));
|
||||
memset((void *)&log, 0x00, sizeof(log));
|
||||
|
||||
for ( i = 1; i < CHAIN_LEN; i++ ) {
|
||||
node[i - 1].next = &node[i];
|
||||
node[i].prev = &node[i - 1];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int failures = 0;
|
||||
|
||||
akerr_init();
|
||||
AKSL_RUN(failures, test_iterate_visits_every_node_from_the_head);
|
||||
AKSL_REPORT(failures);
|
||||
}
|
||||
@@ -1,7 +1,23 @@
|
||||
#include <stdio.h>
|
||||
#include <akstdlib.h>
|
||||
/*
|
||||
* Depth-first tree search.
|
||||
*
|
||||
* Previously this test shared one TreeSearchParams across all three searches
|
||||
* without resetting it, so `steps` accumulated (7, then 14, then 21) and the
|
||||
* second assertion failed -- `tree` was a red test on every run. Each search
|
||||
* now gets its own params.
|
||||
*
|
||||
* Caveat worth knowing when reading the step counts below: the value is hidden
|
||||
* in tree[6], which is the last node visited in pre-, in- and post-order alike,
|
||||
* so "7 steps" holds for all three orders and does not actually distinguish
|
||||
* them -- nor does it prove that AKERR_ITERATOR_BREAK stopped anything (it does
|
||||
* not; see tests/test_tree_iterate_break.c and TODO.md 2.1.3). Visit-order
|
||||
* assertions that tell the three traversals apart are TODO.md section 1.8.
|
||||
*/
|
||||
|
||||
#define MAX_LEAVES 7
|
||||
#include "aksl_capture.h"
|
||||
|
||||
#define MAX_LEAVES 7
|
||||
#define HIDDEN_VALUE ((void *)17336)
|
||||
|
||||
typedef struct TreeSearchParams
|
||||
{
|
||||
@@ -10,11 +26,10 @@ typedef struct TreeSearchParams
|
||||
aksl_TreeNode *node;
|
||||
} TreeSearchParams;
|
||||
|
||||
// This iterator does nothing but print the node names it is visiting
|
||||
akerr_ErrorContext AKERR_NOIGNORE *myiter(aksl_TreeNode *node, void *data)
|
||||
static akerr_ErrorContext AKERR_NOIGNORE *find_value(aksl_TreeNode *node, void *data)
|
||||
{
|
||||
int count = 0;
|
||||
TreeSearchParams *parms = NULL;
|
||||
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "node");
|
||||
FAIL_ZERO_RETURN(e, data, AKERR_NULLPOINTER, "data");
|
||||
@@ -27,70 +42,72 @@ akerr_ErrorContext AKERR_NOIGNORE *myiter(aksl_TreeNode *node, void *data)
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* Build the 3-level tree used by every case here, with the search value hidden
|
||||
* in the bottom-right leaf.
|
||||
*
|
||||
* LEFT RIGHT
|
||||
* TREE[0]
|
||||
* +--------^^---------+
|
||||
* | |
|
||||
* TREE[1] TREE[2]
|
||||
* +---^^---+ +---^^---+
|
||||
* | | | |
|
||||
*TREE[3] TREE[4] TREE[5] TREE[6]
|
||||
*/
|
||||
static void build_tree(aksl_TreeNode *tree, TreeSearchParams *parms)
|
||||
{
|
||||
memset((void *)tree, 0x00, sizeof(aksl_TreeNode) * MAX_LEAVES);
|
||||
tree[0].left = &tree[1];
|
||||
tree[0].right = &tree[2];
|
||||
tree[1].left = &tree[3];
|
||||
tree[1].right = &tree[4];
|
||||
tree[2].left = &tree[5];
|
||||
tree[2].right = &tree[6];
|
||||
tree[6].leaf = HIDDEN_VALUE;
|
||||
|
||||
memset((void *)parms, 0x00, sizeof(TreeSearchParams));
|
||||
parms->value = HIDDEN_VALUE;
|
||||
}
|
||||
|
||||
static int search_finds_hidden_value(uint8_t searchmode)
|
||||
{
|
||||
aksl_TreeNode tree[MAX_LEAVES];
|
||||
TreeSearchParams parms;
|
||||
|
||||
build_tree(tree, &parms);
|
||||
|
||||
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &find_value, NULL, NULL,
|
||||
searchmode, &parms, NULL));
|
||||
AKSL_CHECK(parms.node == &tree[6]);
|
||||
AKSL_CHECK(parms.steps == MAX_LEAVES);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_dfs_preorder(void)
|
||||
{
|
||||
return search_finds_hidden_value(AKSL_TREE_SEARCH_DFS_PREORDER);
|
||||
}
|
||||
|
||||
static int test_dfs_inorder(void)
|
||||
{
|
||||
return search_finds_hidden_value(AKSL_TREE_SEARCH_DFS_INORDER);
|
||||
}
|
||||
|
||||
static int test_dfs_postorder(void)
|
||||
{
|
||||
return search_finds_hidden_value(AKSL_TREE_SEARCH_DFS_POSTORDER);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
aksl_TreeNode tree[MAX_LEAVES];
|
||||
TreeSearchParams parms = {
|
||||
.value = (void *)17336,
|
||||
.steps = 0,
|
||||
.node = NULL
|
||||
};
|
||||
int failures = 0;
|
||||
|
||||
ATTEMPT {
|
||||
memset((void *)&tree, 0x00, sizeof(aksl_TreeNode) * MAX_LEAVES);
|
||||
/*
|
||||
* Here we build a 3 level tree
|
||||
*
|
||||
* LEFT RIGHT
|
||||
* TREE[0]
|
||||
* +--------^^---------+
|
||||
* | |
|
||||
* TREE[1] TREE[2]
|
||||
* +---^^---+ +---^^---+
|
||||
* | | | |
|
||||
*TREE[3] TREE[4] TREE[5] TREE[6]
|
||||
*/
|
||||
tree[0].left = &tree[1];
|
||||
tree[0].right = &tree[2];
|
||||
tree[1].left = &tree[3];
|
||||
tree[1].right = &tree[4];
|
||||
tree[2].left = &tree[5];
|
||||
tree[2].right = &tree[6];
|
||||
akerr_init();
|
||||
|
||||
// Hide a value in tree[6]
|
||||
tree[6].leaf = (void *)17336;
|
||||
AKSL_RUN(failures, test_dfs_preorder);
|
||||
AKSL_RUN(failures, test_dfs_inorder);
|
||||
AKSL_RUN(failures, test_dfs_postorder);
|
||||
|
||||
// Search for the value 17336 using DFS_PREORDER
|
||||
CATCH(e, aksl_tree_iterate(&tree[0], &myiter, NULL, NULL, AKSL_TREE_SEARCH_DFS_PREORDER, &parms, NULL));
|
||||
if ( parms.node != &tree[6] ) {
|
||||
FAIL_BREAK(e, AKERR_API, "DFS_PREORDER_SEARCH didn't find the node");
|
||||
}
|
||||
if ( parms.steps != 7 ) {
|
||||
FAIL_BREAK(e, AKERR_API, "DFS_PREORDER_SEARCH should've found the node in 7 steps, instead took %d", parms.steps);
|
||||
}
|
||||
|
||||
// Search for the value 17336 using DFS_INORDER
|
||||
CATCH(e, aksl_tree_iterate(&tree[0], &myiter, NULL, NULL, AKSL_TREE_SEARCH_DFS_INORDER, &parms, NULL));
|
||||
if ( parms.node != &tree[6] ) {
|
||||
FAIL_BREAK(e, AKERR_API, "DFS_INORDER_SEARCH didn't find the node");
|
||||
}
|
||||
if ( parms.steps != 7 ) {
|
||||
FAIL_BREAK(e, AKERR_API, "DFS_INORDER_SEARCH should've found the node in 7 steps, instead took %d", parms.steps);
|
||||
}
|
||||
|
||||
// Search for the value 17336 using DFS_PREORDER
|
||||
CATCH(e, aksl_tree_iterate(&tree[0], &myiter, NULL, NULL, AKSL_TREE_SEARCH_DFS_POSTORDER, &parms, NULL));
|
||||
if ( parms.node != &tree[6] ) {
|
||||
FAIL_BREAK(e, AKERR_API, "DFS_POSTORDER_SEARCH didn't find the node");
|
||||
}
|
||||
if ( parms.steps != 7 ) {
|
||||
FAIL_BREAK(e, AKERR_API, "DFS_POSTORDER_SEARCH should've found the node in 7 steps, instead took %d", parms.steps);
|
||||
}
|
||||
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} FINISH_NORETURN(e);
|
||||
|
||||
return 0;
|
||||
AKSL_REPORT(failures);
|
||||
}
|
||||
|
||||
89
tests/test_tree_iterate_break.c
Normal file
89
tests/test_tree_iterate_break.c
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* KNOWN FAILING -- TODO.md section 2.1.3
|
||||
*
|
||||
* AKERR_ITERATOR_BREAK does not abort a tree traversal. aksl_tree_iterate
|
||||
* recurses into itself, and the frame in which the callback raises the break
|
||||
* handles it in its own PROCESS/HANDLE(e, AKERR_ITERATOR_BREAK) block and
|
||||
* returns *success*. The parent frame's PASS therefore sees no error at all and
|
||||
* carries straight on into the sibling subtree, so the traversal runs to
|
||||
* completion.
|
||||
*
|
||||
* The 7-node tree below is walked pre-order (0, 1, 3, 4, 2, 5, 6) with the
|
||||
* callback breaking on tree[3], the third node visited. A working break stops
|
||||
* the walk at 3 visits; today the callback is invoked all 7 times.
|
||||
*
|
||||
* tests/test_tree.c cannot catch this: it hides its value in tree[6], which is
|
||||
* the last node visited in all three depth-first orders, so a break that never
|
||||
* fires is indistinguishable from one that fires on the final node.
|
||||
*
|
||||
* Registered in AKSL_KNOWN_FAILING_TESTS (WILL_FAIL). When the break semantics
|
||||
* are fixed, CTest reports this as unexpectedly passing -- move it into
|
||||
* AKSL_TESTS then.
|
||||
*/
|
||||
|
||||
#include "aksl_capture.h"
|
||||
|
||||
#define MAX_LEAVES 7
|
||||
|
||||
typedef struct BreakParams
|
||||
{
|
||||
aksl_TreeNode *stop_at;
|
||||
int visits;
|
||||
} BreakParams;
|
||||
|
||||
static akerr_ErrorContext AKERR_NOIGNORE *break_at_node(aksl_TreeNode *node, void *data)
|
||||
{
|
||||
BreakParams *parms = NULL;
|
||||
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "node");
|
||||
FAIL_ZERO_RETURN(e, data, AKERR_NULLPOINTER, "data");
|
||||
parms = (BreakParams *)data;
|
||||
parms->visits += 1;
|
||||
if ( node == parms->stop_at ) {
|
||||
FAIL_RETURN(e, AKERR_ITERATOR_BREAK, "stop");
|
||||
}
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
static int test_break_aborts_the_whole_traversal(void)
|
||||
{
|
||||
aksl_TreeNode tree[MAX_LEAVES];
|
||||
BreakParams parms;
|
||||
|
||||
memset((void *)tree, 0x00, sizeof(tree));
|
||||
memset((void *)&parms, 0x00, sizeof(parms));
|
||||
|
||||
/*
|
||||
* TREE[0]
|
||||
* +--------^^---------+
|
||||
* | |
|
||||
* TREE[1] TREE[2]
|
||||
* +---^^---+ +---^^---+
|
||||
* | | | |
|
||||
*TREE[3] TREE[4] TREE[5] TREE[6]
|
||||
*/
|
||||
tree[0].left = &tree[1];
|
||||
tree[0].right = &tree[2];
|
||||
tree[1].left = &tree[3];
|
||||
tree[1].right = &tree[4];
|
||||
tree[2].left = &tree[5];
|
||||
tree[2].right = &tree[6];
|
||||
|
||||
parms.stop_at = &tree[3];
|
||||
|
||||
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &break_at_node, NULL, NULL,
|
||||
AKSL_TREE_SEARCH_DFS_PREORDER, &parms, NULL));
|
||||
/* Pre-order visits 0, 1, 3 -- the break on tree[3] must stop the walk there. */
|
||||
AKSL_CHECK(parms.visits == 3);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int failures = 0;
|
||||
|
||||
akerr_init();
|
||||
AKSL_RUN(failures, test_break_aborts_the_whole_traversal);
|
||||
AKSL_REPORT(failures);
|
||||
}
|
||||
Reference in New Issue
Block a user