Files
libakgl/tests/staticstring.c
Andrew Kesterson 9b124f2e27 Migrate to the libakerror 1.0.0 status registry
libakerror 1.0.0 replaced the consumer-sized status-name array with a
private registry and made status-code ownership explicit and enforced.
AKERR_MAX_ERR_VALUE and __AKERR_ERROR_NAMES are gone, and the registry
entry points raise akerr_ErrorContext * instead of returning int. See
deps/libakerror/UPGRADING.md.

The break was not only source-level. libakgl's codes sat at
AKERR_LAST_ERRNO_VALUE + 18 through + 22, and 1.0.0 claimed exactly
those five offsets for its own AKERR_STATUS_* registry codes, so every
AKGL_ERR_* was aliasing a libakerror status. HANDLE(e,
AKGL_ERR_LOGICINTERRUPT) at physics.c:222 would have swallowed a
foreign-name refusal.

- Move the band to AKERR_FIRST_CONSUMER_STATUS (256) as fixed offsets,
  so a libc that grows an errno cannot move the codes, and add
  AKGL_ERR_OWNER, AKGL_ERR_LIMIT and AKGL_ERR_COUNT to describe it.
- Reserve the range and register the names through the owned entry
  points, PASS-ing each: these are AKERR_NOIGNORE, and the old
  akerr_name_for_status calls discarded failure silently.
- Drop the AKERR_MAX_ERR_VALUE=256 compile definition.
- Guard on AKERR_FIRST_CONSUMER_STATUS in include/akgl/error.h, which
  now includes <akerror.h> so the guard is reliable. The embedded build
  is fine, but the find_package path can pick up a stale installed
  header, and 1.0.0 has an soname, so that pairing is an ABI mismatch
  rather than a compile problem. Same guard libakstdlib already carries.

Registration also moves out of akgl_heap_init into a new akgl_error_init
in src/error.c. It was in the heap pool's initializer only because that
was the first thing akgl_game_init called, and the upgrade turned five
fire-and-forget name calls into a library-wide ownership claim that can
fail. That placement was hiding a defect: game.c raises AKGL_ERR_SDL
when SDL_CreateMutex fails, five lines before akgl_heap_init ran, so the
earliest error path in the library was guaranteed to print "Unknown
Error". akgl_error_init is now the first statement in akgl_game_init.

Callers that drive subsystems directly must call akgl_error_init first;
it is idempotent, so ordering it precisely is not required. The eleven
test suites that relied on akgl_heap_init to name their statuses now
call it explicitly, or their failure messages would have degraded to
"Unknown Error".

Add tests/error.c: assert every code reads back its registered name,
that the name table and AKGL_ERR_COUNT agree, that a foreign owner is
refused with AKERR_STATUS_NAME_FOREIGN and AKERR_STATUS_RANGE_OVERLAP,
and that repeating the init is a no-op. That last one is a live
constraint, not a triviality -- libakerror treats only an identical
reservation as a repeat, so a subset or superset raises.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 22:20:28 -04:00

157 lines
4.2 KiB
C

#include <string.h>
#include <akerror.h>
#include <akgl/error.h>
#include <akgl/heap.h>
#include <akgl/staticstring.h>
void reset_string_heap(void);
akerr_ErrorContext *test_fresh_heap_gives_strings(void)
{
akgl_String *ptr = NULL;
PREPARE_ERROR(errctx);
for ( int i = 0; i < AKGL_MAX_HEAP_STRING - 1; i++ ) {
ATTEMPT {
CATCH(errctx, akgl_heap_next_string(&ptr));
} CLEANUP {
reset_string_heap();
} PROCESS(errctx) {
} FINISH(errctx, true);
}
return 0;
}
akerr_ErrorContext *test_string_heap_error_when_no_strings_left(void)
{
akgl_String *ptr;
PREPARE_ERROR(errctx);
for ( int i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) {
HEAP_STRING[i].refcount = 1;
}
for ( int i = 0; i < AKGL_MAX_HEAP_STRING - 1; i++ ) {
ATTEMPT {
CATCH(errctx, akgl_heap_next_string(&ptr));
} CLEANUP {
} PROCESS(errctx) {
} HANDLE(errctx, AKERR_NULLPOINTER) {
return 0;
} FINISH(errctx, true);
}
FAIL_RETURN(errctx, AKERR_OUTOFBOUNDS, "Expected AKERR_NULLPOINTER when accessing beyond string heap bounds");
SUCCEED_RETURN(errctx);
}
akerr_ErrorContext *test_string_heap_honors_refcount(void)
{
akgl_String *firstptr = &HEAP_STRING[0];
akgl_String *secondptr = &HEAP_STRING[1];
akgl_String *testptr = NULL;
PREPARE_ERROR(errctx);
ATTEMPT {
CATCH(errctx, akgl_heap_next_string(&testptr));
if ( testptr != firstptr ) {
FAIL_RETURN(
errctx,
AKERR_VALUE,
"Expected testptr to equal (HEAP_STRING[0] = %p) but got %p",
firstptr,
testptr
);
}
CATCH(errctx, akgl_string_initialize(testptr, NULL));
if ( testptr->refcount == 0 ) {
FAIL_RETURN(errctx, AKERR_VALUE, "Expected string reference count to be nonzero but got 0");
}
if ( testptr != firstptr ) {
FAIL_RETURN(
errctx,
AKERR_VALUE,
"Expected testptr to equal (HEAP_STRING[1] = %p) but got %p",
secondptr,
testptr
);
}
CATCH(errctx, akgl_heap_next_string(&testptr));
} CLEANUP {
} PROCESS(errctx) {
} FINISH(errctx, true);
SUCCEED_RETURN(errctx);
}
akerr_ErrorContext *test_strcpy_to_all_strings_no_segfault(void)
{
char copybuf[AKGL_MAX_STRING_LENGTH];
akgl_String *ptr;
memset((void *)&copybuf, 'a', AKGL_MAX_STRING_LENGTH);
PREPARE_ERROR(errctx);
ATTEMPT {
for ( int i = 0; i < AKGL_MAX_HEAP_STRING - 1; i++ ) {
CATCH(errctx, akgl_heap_next_string(&ptr));
strncpy(ptr->data, (char *)&copybuf, AKGL_MAX_STRING_LENGTH);
}
} CLEANUP {
} PROCESS(errctx) {
} FINISH(errctx, true);
SUCCEED_RETURN(errctx);
}
akerr_ErrorContext *test_akgl_string_initialize(void)
{
akgl_String *ptr;
PREPARE_ERROR(errctx);
ATTEMPT {
CATCH(errctx, akgl_heap_next_string(&ptr));
CATCH(errctx, akgl_string_initialize(ptr, NULL));
FAIL_NONZERO_BREAK(errctx, ptr->data[0], AKERR_VALUE, "Expected empty zero length string data");
CATCH(errctx, akgl_heap_release_string(ptr));
CATCH(errctx, akgl_heap_next_string(&ptr));
CATCH(errctx, akgl_string_initialize(ptr, "Test value"));
FAIL_NONZERO_BREAK(errctx, strcmp((char *)&ptr->data, "Test value"), AKERR_VALUE, "Expected 'Test value', got %s", (char *)&ptr->data);
CATCH(errctx, akgl_heap_release_string(NULL));
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Failure to properly handle NULL pointer");
} CLEANUP {
} PROCESS(errctx) {
} FINISH(errctx, true);
SUCCEED_RETURN(errctx);
}
void reset_string_heap(void)
{
for ( int i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) {
memset(&HEAP_STRING[i], 0x00, sizeof(akgl_String));
}
}
int main(void)
{
PREPARE_ERROR(errctx);
ATTEMPT {
CATCH(errctx, akgl_error_init());
printf("test_fresh_heap_gives_string ....\n");
test_fresh_heap_gives_strings();
reset_string_heap();
printf("test_string_heap_error_when_no_strings_left ...\n");
test_string_heap_error_when_no_strings_left();
reset_string_heap();
printf("test_string_heap_honors_refcount ...\n");
test_string_heap_honors_refcount();
reset_string_heap();
printf("test_strcpy_to_all_strings_no_segfault ...\n");
test_strcpy_to_all_strings_no_segfault();
reset_string_heap();
printf("test_akgl_string_initialize....\n");
test_akgl_string_initialize();
} CLEANUP {
} PROCESS(errctx) {
} FINISH_NORETURN(errctx);
return 0;
}