/* * libakstdlib's side of the libakerror 1.0.0 status registry contract. * * See deps/libakerror/UPGRADING.md. That release made the status-name table * private, moved consumer status codes to a reserved band starting at * AKERR_FIRST_CONSUMER_STATUS, and made ownership of a range enforced rather * than advisory. None of it broke this library's build, because libakstdlib * defines no status codes of its own -- it raises libakerror's AKERR_* codes and * propagates the host's errno values, both of which live in libakerror's own * 0-255 band. * * That "defines none of its own" is now a contract worth pinning rather than an * accident, because it is what lets libakstdlib sit in a process alongside any * other libakerror consumer without a range negotiation. These tests assert it, * and assert that the registry the codes are looked up in is actually populated * -- a status whose name fails to register degrades to "Unknown Error" in every * later stack trace, which is a silent loss of debuggability rather than a * failure anyone would notice. * * If libakstdlib ever does define its own status codes, this file is where the * reservation gets asserted: replace test_reserves_no_consumer_range with one * that reserves the library's declared range and confirms its names registered. */ #include "aksl_capture.h" #include /* * Every status libakstdlib raises, and where from. All of them belong to * libakerror or to the host's errno space; none is defined here. */ static const struct { int status; const char *symbol; const char *raised_by; } aksl_raised_statuses[] = { { AKERR_NULLPOINTER, "AKERR_NULLPOINTER", "every NULL parameter guard" }, { AKERR_IO, "AKERR_IO", "aksl_fread / aksl_fwrite short count" }, { AKERR_EOF, "AKERR_EOF", "aksl_fread at end of stream" }, { AKERR_ITERATOR_BREAK, "AKERR_ITERATOR_BREAK", "aksl_list_iterate / aksl_tree_iterate" }, { AKERR_CIRCULAR_REFERENCE, "AKERR_CIRCULAR_REFERENCE", "aksl_list_append cycle guard" }, { AKERR_NOT_IMPLEMENTED, "AKERR_NOT_IMPLEMENTED", "unsupported tree search modes" }, { ENOENT, "ENOENT", "errno propagated by aksl_fopen" }, }; #define AKSL_RAISED_STATUS_COUNT \ ((int)(sizeof(aksl_raised_statuses) / sizeof(aksl_raised_statuses[0]))) /* * Ranges this file claims out of the consumer band. A reservation is * process-global and there is no way to give one back, so each test function * gets its own slice and no two overlap. * * +0 .. +15 test_reserves_no_consumer_range * +16 .. +31 test_range_ownership_is_enforced * +32 .. +47 test_naming_a_foreign_status_is_refused */ #define AKSL_TEST_RANGE_FREE (AKERR_FIRST_CONSUMER_STATUS + 0) #define AKSL_TEST_RANGE_OWNED (AKERR_FIRST_CONSUMER_STATUS + 16) #define AKSL_TEST_RANGE_FOREIGN (AKERR_FIRST_CONSUMER_STATUS + 32) #define AKSL_TEST_RANGE_SIZE 16 #define AKSL_OWNER_A "aksl-test-a" #define AKSL_OWNER_B "aksl-test-b" /* * The band boundary this library's "raises nothing of its own" claim rests on. * Asserted rather than assumed: if libakerror ever moves the boundary, the * status-band assertion below stops meaning what it says. */ static int test_consumer_band_starts_at_256(void) { AKSL_CHECK(AKERR_FIRST_CONSUMER_STATUS == 256); AKSL_CHECK(AKERR_RESERVED_STATUS_COUNT == 256); return 0; } /* * Every status libakstdlib raises falls inside libakerror's reserved band, so it * cannot collide with a consumer that allocates from 256 up. */ static int test_raised_statuses_are_in_the_reserved_band(void) { int i = 0; for ( i = 0; i < AKSL_RAISED_STATUS_COUNT; i++ ) { if ( aksl_raised_statuses[i].status >= AKERR_FIRST_CONSUMER_STATUS ) { fprintf(stderr, " CHECK FAILED: %s (%d) is outside libakerror's reserved band\n" " raised by %s\n" " at %s:%d\n", aksl_raised_statuses[i].symbol, aksl_raised_statuses[i].status, aksl_raised_statuses[i].raised_by, __FILE__, __LINE__); return 1; } } return 0; } /* * The registry actually holds a name for each of them. This is the check that * catches a build whose name table is too small: registration failures are * reported at akerr_init() time, but the visible symptom afterwards is only that * stack traces read "Unknown Error", which no other test would notice. */ static int test_raised_statuses_have_registered_names(void) { int i = 0; char *name = NULL; for ( i = 0; i < AKSL_RAISED_STATUS_COUNT; i++ ) { name = akerr_name_for_status(aksl_raised_statuses[i].status, NULL); if ( name == NULL || strcmp(name, "Unknown Error") == 0 ) { fprintf(stderr, " CHECK FAILED: %s (%d) has no registered name\n" " raised by %s\n" " reads back as \"%s\"\n" " at %s:%d\n", aksl_raised_statuses[i].symbol, aksl_raised_statuses[i].status, aksl_raised_statuses[i].raised_by, name == NULL ? "(NULL)" : name, __FILE__, __LINE__); return 1; } } return 0; } /* * libakstdlib reserves nothing in the consumer band, so an application is free * to allocate from AKERR_FIRST_CONSUMER_STATUS without coordinating with it. * The library is driven first so that anything it might do lazily has happened * before the range is claimed. */ static int test_reserves_no_consumer_range(void) { void *p = NULL; AKSL_CHECK_OK(aksl_malloc(16, &p)); AKSL_CHECK_OK(aksl_free(p)); AKSL_CHECK_OK(akerr_reserve_status_range(AKSL_TEST_RANGE_FREE, AKSL_TEST_RANGE_SIZE, AKSL_OWNER_A)); return 0; } /* * Ownership is enforced, which is what makes the assertion above meaningful -- * a reservation that succeeded against an already-claimed range would prove * nothing. libakerror's own 0-255 band is refused for the same reason. */ static int test_range_ownership_is_enforced(void) { AKSL_CHECK_OK(akerr_reserve_status_range(AKSL_TEST_RANGE_OWNED, AKSL_TEST_RANGE_SIZE, AKSL_OWNER_A)); /* An identical reservation by the same owner is a documented no-op. */ AKSL_CHECK_OK(akerr_reserve_status_range(AKSL_TEST_RANGE_OWNED, AKSL_TEST_RANGE_SIZE, AKSL_OWNER_A)); /* The same range under a different owner is not. */ AKSL_CHECK_STATUS(akerr_reserve_status_range(AKSL_TEST_RANGE_OWNED, AKSL_TEST_RANGE_SIZE, AKSL_OWNER_B), AKERR_STATUS_RANGE_OVERLAP); /* Nor is any part of libakerror's own band. */ AKSL_CHECK_STATUS(akerr_reserve_status_range(AKERR_NULLPOINTER, 1, AKSL_OWNER_B), AKERR_STATUS_RANGE_OVERLAP); return 0; } /* * Naming is enforced against the reservation too: a status nobody reserved and a * status somebody else reserved are both refused, with different codes. */ static int test_naming_a_foreign_status_is_refused(void) { AKSL_CHECK_STATUS(akerr_register_status_name(AKSL_OWNER_A, AKSL_TEST_RANGE_FOREIGN, "Unreserved"), AKERR_STATUS_NAME_UNRESERVED); AKSL_CHECK_OK(akerr_reserve_status_range(AKSL_TEST_RANGE_FOREIGN, AKSL_TEST_RANGE_SIZE, AKSL_OWNER_A)); AKSL_CHECK_OK(akerr_register_status_name(AKSL_OWNER_A, AKSL_TEST_RANGE_FOREIGN, "Mine")); AKSL_CHECK(strcmp(akerr_name_for_status(AKSL_TEST_RANGE_FOREIGN, NULL), "Mine") == 0); AKSL_CHECK_STATUS(akerr_register_status_name(AKSL_OWNER_B, AKSL_TEST_RANGE_FOREIGN, "Theirs"), AKERR_STATUS_NAME_FOREIGN); /* The refused registration left the owner's name in place. */ AKSL_CHECK(strcmp(akerr_name_for_status(AKSL_TEST_RANGE_FOREIGN, NULL), "Mine") == 0); return 0; } int main(void) { int failures = 0; akerr_init(); AKSL_RUN(failures, test_consumer_band_starts_at_256); AKSL_RUN(failures, test_raised_statuses_are_in_the_reserved_band); AKSL_RUN(failures, test_raised_statuses_have_registered_names); AKSL_RUN(failures, test_reserves_no_consumer_range); AKSL_RUN(failures, test_range_ownership_is_enforced); AKSL_RUN(failures, test_naming_a_foreign_status_is_refused); AKSL_REPORT(failures); }