#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 * report the failure rather than dropping the registration quietly. */ int main(void) { akerr_capture_install(); akerr_init(); /* Any int is a legal status, at either extreme of the range. */ AKERR_CHECK(akerr_reserve_status_range(INT_MIN, 1, "min-owner") == AKERR_STATUS_RANGE_OK); AKERR_CHECK(akerr_reserve_status_range(INT_MAX, 1, "max-owner") == AKERR_STATUS_RANGE_OK); AKERR_CHECK(akerr_register_status_name("max-owner", INT_MAX, "Maximum Status") == AKERR_STATUS_NAME_OK); AKERR_CHECK(akerr_register_status_name("min-owner", INT_MIN, "Minimum Status") == AKERR_STATUS_NAME_OK); 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(akerr_reserve_status_range(1000000, 1, "trunc") == AKERR_STATUS_RANGE_OK); const char *long_name = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-extra"; AKERR_CHECK(akerr_register_status_name("trunc", 1000000, long_name) == AKERR_STATUS_NAME_OK); 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(akerr_reserve_status_range(256, 16, "component-a") == AKERR_STATUS_RANGE_OK); AKERR_CHECK(akerr_reserve_status_range(256, 16, "component-a") == AKERR_STATUS_RANGE_OK); AKERR_CHECK(akerr_reserve_status_range(260, 2, "component-b") == AKERR_STATUS_RANGE_OVERLAP); AKERR_CHECK_CONTAINS("component-a"); /* The library's own 0..255 band is reserved and cannot be encroached on. */ AKERR_CHECK(akerr_reserve_status_range(255, 1, "component-b") == AKERR_STATUS_RANGE_OVERLAP); AKERR_CHECK(akerr_reserve_status_range(0, AKERR_RESERVED_STATUS_COUNT, AKERR_LIBRARY_OWNER) == AKERR_STATUS_RANGE_OK); /* exact repeat by the owner */ /* Argument validation. */ AKERR_CHECK(akerr_reserve_status_range(INT_MAX, 2, "overflow") == AKERR_STATUS_RANGE_INVALID); AKERR_CHECK(akerr_reserve_status_range(300, 0, "empty") == AKERR_STATUS_RANGE_INVALID); AKERR_CHECK(akerr_reserve_status_range(300, -1, "negative") == AKERR_STATUS_RANGE_INVALID); AKERR_CHECK(akerr_reserve_status_range(300, 1, NULL) == AKERR_STATUS_RANGE_INVALID); AKERR_CHECK(akerr_reserve_status_range(300, 1, "") == AKERR_STATUS_RANGE_INVALID); /* Owner strings: 63 chars fit, 64 do not. */ char owner63[AKERR_MAX_ERROR_NAME_LENGTH]; char owner64[AKERR_MAX_ERROR_NAME_LENGTH + 1]; memset(owner63, 'a', sizeof(owner63) - 1); owner63[sizeof(owner63) - 1] = '\0'; memset(owner64, 'b', sizeof(owner64) - 1); owner64[sizeof(owner64) - 1] = '\0'; AKERR_CHECK(akerr_reserve_status_range(400, 1, owner63) == AKERR_STATUS_RANGE_OK); AKERR_CHECK(akerr_reserve_status_range(401, 1, owner64) == AKERR_STATUS_RANGE_INVALID); /* Partial overlaps at either endpoint, and a same-range different owner. */ AKERR_CHECK(akerr_reserve_status_range(500, 2, "endpoint") == AKERR_STATUS_RANGE_OK); AKERR_CHECK(akerr_reserve_status_range(499, 2, "left") == AKERR_STATUS_RANGE_OVERLAP); AKERR_CHECK(akerr_reserve_status_range(500, 1, "endpoint") == AKERR_STATUS_RANGE_OVERLAP); /* subset, not an exact repeat */ AKERR_CHECK(akerr_reserve_status_range(501, 1, "endpoint") == AKERR_STATUS_RANGE_OVERLAP); AKERR_CHECK(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(akerr_reserve_status_range(2000000, 100000, "fill") == AKERR_STATUS_RANGE_OK); /* * Range table capacity. The limit is private to src/error.c on purpose, so * discover it by filling rather than by hardcoding it here. */ akerr_capture_reset(); int ranges_added = 0; int range_rc = AKERR_STATUS_RANGE_OK; for ( int i = 0; i < 100000; i++ ) { range_rc = akerr_reserve_status_range(1000 + (i * 2), 1, "pad"); if ( range_rc != AKERR_STATUS_RANGE_OK ) { break; } ranges_added++; } AKERR_CHECK(ranges_added > 0); AKERR_CHECK(range_rc == AKERR_STATUS_RANGE_FULL); AKERR_CHECK_CONTAINS("range table is full"); /* * Name table capacity. Exhaustion must be reported, not silent: a dropped * name degrades every future stack trace for that code to "Unknown Error". */ akerr_capture_reset(); int full_at = -1; for ( int i = 0; i < 100000; i++ ) { char name[32]; snprintf(name, sizeof(name), "Filled %d", i); int rc = akerr_register_status_name("fill", 2000000 + i, name); if ( rc != AKERR_STATUS_NAME_OK ) { AKERR_CHECK(rc == AKERR_STATUS_NAME_FULL); full_at = i; break; } } AKERR_CHECK_CONTAINS("registry is full"); AKERR_CHECK_CONTAINS("AKERR_STATUS_NAME_SLOTS"); /* * 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); fprintf(stderr, "err_maxval ok (%d consumer names before full)\n", full_at); return 0; }