akerr_reserve_status_range() and akerr_register_status_name() returned private int enumerations, which was the one place in the library where a failure was not an akerr_ErrorContext *. They now return one like everything else: NULL on success, and on refusal an error whose status is a real code in the library's reserved band, so it can be CATCH-ed, HANDLE-d, PASS-ed, or left to propagate into a stack trace. Both are marked AKERR_NOIGNORE, so discarding the result warns at compile time. AKERR_STATUS_RANGE_OK and AKERR_STATUS_NAME_OK are gone; the remaining seven codes move into the AKERR_* offset span and get registered names. AKERR_LAST_LIBRARY_STATUS replaces AKERR_BADEXC as the top of that span in the reserved-band static assert and the exhaustiveness sweep. The refusal detail that used to go straight to akerr_log_method now travels in the error message, so a caller that handles the error decides whether it is reported. The two-argument akerr_name_for_status() set path is the exception: it returns a name and cannot raise, so it logs and releases. akerr_init() likewise has no caller to raise into, so failing to reserve its own band or name its own codes is logged and fatal -- that can only happen on a misconfigured build, and continuing would degrade every later stack trace to "Unknown Error". Move the 1.0.0 upgrade notice out of README.md into UPGRADING.md and rewrite its return-code tables in terms of the statuses now raised. Tests: ctest 29/29, coverage 97.5% line / 64.5% branch, mutation 77.5% (was 77.6%; the new survivors are the fatal init path, which needs a library built with an undersized name table -- TODO item 7). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
132 lines
4.2 KiB
C
132 lines
4.2 KiB
C
#ifndef AKERR_TEST_CAPTURE_H
|
|
#define AKERR_TEST_CAPTURE_H
|
|
|
|
/*
|
|
* Shared test helpers for libakerror.
|
|
*
|
|
* Installs a capturing implementation of akerr_log_method so tests can assert
|
|
* on the *content* of log/stacktrace output (messages, status codes, error
|
|
* names) instead of relying solely on process exit codes.
|
|
*
|
|
* Also provides AKERR_CHECK(), a NDEBUG-proof assertion that fails the test by
|
|
* returning non-zero from main() (unlike assert(), which is compiled out in
|
|
* release builds and would silently turn a test into a no-op).
|
|
*/
|
|
|
|
#include "akerror.h"
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#define AKERR_CAPTURE_BUFSZ 65536
|
|
static char akerr_capture_buf[AKERR_CAPTURE_BUFSZ];
|
|
static size_t akerr_capture_len = 0;
|
|
|
|
static void __attribute__((unused)) akerr_capture_logger(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
int n = vsnprintf(akerr_capture_buf + akerr_capture_len,
|
|
AKERR_CAPTURE_BUFSZ - akerr_capture_len, fmt, ap);
|
|
va_end(ap);
|
|
if ( n > 0 ) {
|
|
akerr_capture_len += (size_t)n;
|
|
if ( akerr_capture_len >= AKERR_CAPTURE_BUFSZ ) {
|
|
akerr_capture_len = AKERR_CAPTURE_BUFSZ - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void __attribute__((unused)) akerr_capture_reset(void)
|
|
{
|
|
akerr_capture_len = 0;
|
|
akerr_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)) akerr_capture_install(void)
|
|
{
|
|
akerr_capture_reset();
|
|
akerr_log_method = &akerr_capture_logger;
|
|
}
|
|
|
|
/* Count array slots currently checked out of the pool (refcount != 0). */
|
|
static int __attribute__((unused)) akerr_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;
|
|
}
|
|
|
|
#define AKERR_CHECK(cond) \
|
|
do { \
|
|
if ( !(cond) ) { \
|
|
fprintf(stderr, "CHECK FAILED: %s at %s:%d\n", \
|
|
#cond, __FILE__, __LINE__); \
|
|
return 1; \
|
|
} \
|
|
} while ( 0 )
|
|
|
|
#define AKERR_CHECK_STATUS(errctx, expected_status) \
|
|
do { \
|
|
AKERR_CHECK((errctx) != NULL); \
|
|
AKERR_CHECK((errctx)->status == (expected_status)); \
|
|
} while ( 0 )
|
|
|
|
/*
|
|
* Helpers for functions that report failure by returning akerr_ErrorContext *.
|
|
* Both release the context they consume, so a test that makes thousands of
|
|
* failing calls cannot exhaust the pool. AKERR_CHECK_RAISES keeps a copy of the
|
|
* message for AKERR_CHECK_MESSAGE_CONTAINS, since the context is gone by then.
|
|
*/
|
|
static char __attribute__((unused)) akerr_last_message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH];
|
|
|
|
#define AKERR_CHECK_SUCCEEDS(expr) \
|
|
do { \
|
|
akerr_ErrorContext *__akerr_result = (expr); \
|
|
if ( __akerr_result != NULL ) { \
|
|
fprintf(stderr, "UNEXPECTED ERROR from %s: %d (%s): %s" \
|
|
" at %s:%d\n", #expr, __akerr_result->status, \
|
|
akerr_name_for_status(__akerr_result->status, NULL), \
|
|
__akerr_result->message, __FILE__, __LINE__); \
|
|
RELEASE_ERROR(__akerr_result); \
|
|
return 1; \
|
|
} \
|
|
} while ( 0 )
|
|
|
|
#define AKERR_CHECK_RAISES(expr, expected_status) \
|
|
do { \
|
|
akerr_ErrorContext *__akerr_result = (expr); \
|
|
AKERR_CHECK(__akerr_result != NULL); \
|
|
snprintf(akerr_last_message, sizeof(akerr_last_message), "%s", \
|
|
__akerr_result->message); \
|
|
if ( __akerr_result->status != (expected_status) ) { \
|
|
fprintf(stderr, "WRONG STATUS from %s: got %d, want %s" \
|
|
" at %s:%d\n", #expr, __akerr_result->status, \
|
|
#expected_status, __FILE__, __LINE__); \
|
|
RELEASE_ERROR(__akerr_result); \
|
|
return 1; \
|
|
} \
|
|
RELEASE_ERROR(__akerr_result); \
|
|
AKERR_CHECK(__akerr_result == NULL); \
|
|
} while ( 0 )
|
|
|
|
#define AKERR_CHECK_MESSAGE_CONTAINS(needle) \
|
|
AKERR_CHECK(strstr(akerr_last_message, (needle)) != NULL)
|
|
|
|
#define AKERR_CHECK_CONTAINS(needle) \
|
|
AKERR_CHECK(strstr(akerr_capture_buf, (needle)) != NULL)
|
|
|
|
#define AKERR_CHECK_NOT_CONTAINS(needle) \
|
|
AKERR_CHECK(strstr(akerr_capture_buf, (needle)) == NULL)
|
|
|
|
#endif // AKERR_TEST_CAPTURE_H
|