Files
libakstdlib/tests/aksl_capture.h
Andrew Kesterson 8003239116 Add memory wrapper tests
Cover deterministic TODO 1.1 memory-wrapper behavior with a new CTest target. Exercise malloc/free round trips, NULL argument handling, memset fill/no-op semantics, and memcpy copy/no-op/null-pointer paths.

Harden AKSL_CHECK_OK so success requires no returned error context, catching wrappers that accidentally raise status-0 errors from stale errno.

Co-authored-by: Codex GPT-5.5 Default <codex-gpt-5.5-default@openai.com>
2026-07-29 14:42:27 -04:00

241 lines
9.4 KiB
C

#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() assert a call returned no error context at all.
* 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. Failure-path assertions should go through this so that no test
* leaks a pool slot. Success-path assertions use AKSL_CHECK_OK, which also
* verifies that no bogus status-0 context was returned.
*/
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 )
/*
* Success is represented by a NULL error context, not just status 0. This catches
* wrappers that incorrectly raise an error using a stale errno value of 0.
*/
#define AKSL_CHECK_OK(__expr) \
do { \
akerr_ErrorContext *__e = (__expr); \
if ( __e != NULL ) { \
int __st = aksl_take(__e); \
fprintf(stderr, \
" CHECK FAILED: %s\n" \
" returned error context with status %d (%s) \"%s\"\n" \
" expected no error context\n" \
" at %s:%d\n", \
#__expr, \
__st, akerr_name_for_status(__st, NULL), \
aksl_last_message, \
__FILE__, __LINE__); \
return 1; \
} \
aksl_last_status = 0; \
aksl_last_message[0] = '\0'; \
aksl_last_function[0] = '\0'; \
} while ( 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