#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 #include #include #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