226 lines
8.1 KiB
C
226 lines
8.1 KiB
C
|
|
/*
|
||
|
|
* 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 <errno.h>
|
||
|
|
|
||
|
|
/*
|
||
|
|
* 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);
|
||
|
|
}
|