#include "akerror.h" #include "err_capture.h" #include #include /* * Status magnitude is no longer coupled to a public array bound: any int is a * legal status, and storage is a private sparse registry. What bounds the * registry now is its *capacity*, not the value of the largest code. * * Covers: arbitrary int status values, name truncation, range reservation * semantics (overlap, idempotency, endpoints, validation, overflow), and both * capacity limits -- the range table and the name table -- each of which must * raise rather than dropping the registration quietly. * * Every refusal is an error context the caller owns, so each check below also * asserts the context returns to the pool; a leak here would exhaust the * 128-slot pool long before these loops finish. */ int main(void) { akerr_capture_install(); akerr_init(); /* Any int is a legal status, at either extreme of the range. */ AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(INT_MIN, 1, "min-owner")); AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(INT_MAX, 1, "max-owner")); AKERR_CHECK_SUCCEEDS(akerr_register_status_name("max-owner", INT_MAX, "Maximum Status")); AKERR_CHECK_SUCCEEDS(akerr_register_status_name("min-owner", INT_MIN, "Minimum Status")); AKERR_CHECK(strcmp(akerr_name_for_status(INT_MAX, NULL), "Maximum Status") == 0); AKERR_CHECK(strcmp(akerr_name_for_status(INT_MIN, NULL), "Minimum Status") == 0); /* A name longer than the buffer is truncated and always terminated. */ AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(1000000, 1, "trunc")); const char *long_name = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-extra"; AKERR_CHECK_SUCCEEDS(akerr_register_status_name("trunc", 1000000, long_name)); char *stored = akerr_name_for_status(1000000, NULL); AKERR_CHECK(strlen(stored) == AKERR_MAX_ERROR_NAME_LENGTH - 1); AKERR_CHECK(stored[AKERR_MAX_ERROR_NAME_LENGTH - 1] == '\0'); /* Reservation: overlap detection, and idempotency for an exact repeat. */ AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(256, 16, "component-a")); AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(256, 16, "component-a")); AKERR_CHECK_RAISES(akerr_reserve_status_range(260, 2, "component-b"), AKERR_STATUS_RANGE_OVERLAP); AKERR_CHECK_MESSAGE_CONTAINS("component-a"); /* The library's own 0..255 band is reserved and cannot be encroached on. */ AKERR_CHECK_RAISES(akerr_reserve_status_range(255, 1, "component-b"), AKERR_STATUS_RANGE_OVERLAP); AKERR_CHECK_MESSAGE_CONTAINS(AKERR_LIBRARY_OWNER); /* An exact repeat by the owner is still a no-op. */ AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(0, AKERR_RESERVED_STATUS_COUNT, AKERR_LIBRARY_OWNER)); /* Argument validation. */ AKERR_CHECK_RAISES(akerr_reserve_status_range(INT_MAX, 2, "overflow"), AKERR_STATUS_RANGE_INVALID); AKERR_CHECK_RAISES(akerr_reserve_status_range(300, 0, "empty"), AKERR_STATUS_RANGE_INVALID); AKERR_CHECK_MESSAGE_CONTAINS("empty"); /* the message names the caller */ AKERR_CHECK_RAISES(akerr_reserve_status_range(300, -1, "negative"), AKERR_STATUS_RANGE_INVALID); AKERR_CHECK_RAISES(akerr_reserve_status_range(300, 1, NULL), AKERR_STATUS_RANGE_INVALID); AKERR_CHECK_RAISES(akerr_reserve_status_range(300, 1, ""), AKERR_STATUS_RANGE_INVALID); /* Owner strings: 63 chars fit, 64 do not, and neither does anything past. */ char owner63[AKERR_MAX_ERROR_NAME_LENGTH]; char owner64[AKERR_MAX_ERROR_NAME_LENGTH + 1]; char owner70[AKERR_MAX_ERROR_NAME_LENGTH + 7]; memset(owner63, 'a', sizeof(owner63) - 1); owner63[sizeof(owner63) - 1] = '\0'; memset(owner64, 'b', sizeof(owner64) - 1); owner64[sizeof(owner64) - 1] = '\0'; memset(owner70, 'c', sizeof(owner70) - 1); owner70[sizeof(owner70) - 1] = '\0'; AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(400, 1, owner63)); AKERR_CHECK_RAISES(akerr_reserve_status_range(401, 1, owner64), AKERR_STATUS_RANGE_INVALID); AKERR_CHECK_RAISES(akerr_reserve_status_range(402, 1, owner70), AKERR_STATUS_RANGE_INVALID); /* Partial overlaps at either endpoint, and a same-range different owner. */ AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(500, 2, "endpoint")); AKERR_CHECK_RAISES(akerr_reserve_status_range(499, 2, "left"), AKERR_STATUS_RANGE_OVERLAP); AKERR_CHECK_RAISES(akerr_reserve_status_range(500, 1, "endpoint"), AKERR_STATUS_RANGE_OVERLAP); /* subset, not an exact repeat */ AKERR_CHECK_RAISES(akerr_reserve_status_range(501, 1, "endpoint"), AKERR_STATUS_RANGE_OVERLAP); AKERR_CHECK_RAISES(akerr_reserve_status_range(500, 2, "other"), AKERR_STATUS_RANGE_OVERLAP); /* same range, wrong owner */ /* Claim room for the name-exhaustion sweep before filling the range table. */ AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(2000000, 100000, "fill")); /* * Range table capacity. The limit is private to src/error.c on purpose, so * discover it by filling rather than by hardcoding it here. */ int ranges_added = 0; akerr_ErrorContext *range_err = NULL; for ( int i = 0; i < 100000; i++ ) { range_err = akerr_reserve_status_range(1000 + (i * 2), 1, "pad"); if ( range_err != NULL ) { break; } ranges_added++; } AKERR_CHECK(ranges_added > 0); AKERR_CHECK_STATUS(range_err, AKERR_STATUS_RANGE_FULL); AKERR_CHECK(strstr(range_err->message, "range table is full") != NULL); RELEASE_ERROR(range_err); AKERR_CHECK(range_err == NULL); /* * Name table capacity. Exhaustion must be reported, not silent: a dropped * name degrades every future stack trace for that code to "Unknown Error". */ int full_at = -1; for ( int i = 0; i < 100000; i++ ) { char name[32]; snprintf(name, sizeof(name), "Filled %d", i); akerr_ErrorContext *name_err = akerr_register_status_name("fill", 2000000 + i, name); if ( name_err != NULL ) { AKERR_CHECK_STATUS(name_err, AKERR_STATUS_NAME_FULL); AKERR_CHECK(strstr(name_err->message, "registry is full") != NULL); AKERR_CHECK(strstr(name_err->message, "AKERR_STATUS_NAME_SLOTS") != NULL); RELEASE_ERROR(name_err); AKERR_CHECK(name_err == NULL); full_at = i; break; } } /* * The table must actually hold everything it accepted. A probe sequence * that revisits slots instead of walking the table -- e.g. masking with * SLOTS rather than SLOTS-1 -- both collapses the usable capacity and * loses earlier entries, and each check below catches it independently. * The floor assumes at least the default table size (4096 slots). */ AKERR_CHECK(full_at > 256); for ( int i = 0; i < full_at; i++ ) { char expected[32]; snprintf(expected, sizeof(expected), "Filled %d", i); AKERR_CHECK(strcmp(akerr_name_for_status(2000000 + i, NULL), expected) == 0); } /* A dropped name reads back as the sentinel, and earlier ones survive. */ AKERR_CHECK(strcmp(akerr_name_for_status(2000000 + full_at, NULL), "Unknown Error") == 0); AKERR_CHECK(strcmp(akerr_name_for_status(INT_MIN, NULL), "Minimum Status") == 0); /* Thousands of refusals later, every context went back to the pool. */ AKERR_CHECK(akerr_slots_in_use() == 0); fprintf(stderr, "err_maxval ok (%d consumer names before full)\n", full_at); return 0; }