Files
libakgl/tests/registry.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

98 lines
2.2 KiB
C

#include <SDL3/SDL.h>
#include <stdlib.h>
#include <akerror.h>
#include <akgl/registry.h>
typedef akerr_ErrorContext *(*RegistryFuncPtr)(void);
void *sdl_calloc_always_fails(size_t a, size_t b)
{
// This forces SDL to simulate an out of memory condition
return NULL;
}
akerr_ErrorContext *test_akgl_registry_init(RegistryFuncPtr funcptr)
{
SDL_malloc_func malloc_func;
SDL_calloc_func calloc_func;
SDL_realloc_func realloc_func;
SDL_free_func free_func;
SDL_GetMemoryFunctions(
&malloc_func,
&calloc_func,
&realloc_func,
&free_func
);
PREPARE_ERROR(errctx);
ATTEMPT {
SDL_SetMemoryFunctions(
malloc_func,
(SDL_calloc_func)&sdl_calloc_always_fails,
realloc_func,
free_func
);
CATCH(errctx, funcptr());
} CLEANUP {
SDL_SetMemoryFunctions(
malloc_func,
calloc_func,
realloc_func,
free_func
);
} PROCESS(errctx) {
} FINISH(errctx, true);
FAIL_RETURN(errctx, AKGL_ERR_BEHAVIOR, "SDL memory allocator fails but registry reports successful property creation");
}
akerr_ErrorContext *test_akgl_registry_init_creation_failures(void)
{
PREPARE_ERROR(errctx);
ATTEMPT {
CATCH(errctx, test_akgl_registry_init(&akgl_registry_init_actor));
} CLEANUP {
} PROCESS(errctx) {
} HANDLE(errctx, AKERR_NULLPOINTER) {
printf("Sucess\n");
} FINISH(errctx, true);
ATTEMPT {
CATCH(errctx, test_akgl_registry_init(&akgl_registry_init_sprite));
} CLEANUP {
} PROCESS(errctx) {
} HANDLE(errctx, AKERR_NULLPOINTER) {
printf("Sucess\n");
} FINISH(errctx, true);
ATTEMPT {
CATCH(errctx, test_akgl_registry_init(&akgl_registry_init_spritesheet));
} CLEANUP {
} PROCESS(errctx) {
} HANDLE(errctx, AKERR_NULLPOINTER) {
printf("Sucess\n");
} FINISH(errctx, true);
ATTEMPT {
CATCH(errctx, test_akgl_registry_init(&akgl_registry_init_character));
} CLEANUP {
} PROCESS(errctx) {
} HANDLE(errctx, AKERR_NULLPOINTER) {
printf("Sucess\n");
} FINISH(errctx, true);
SUCCEED_RETURN(errctx);
}
int main(void)
{
PREPARE_ERROR(errctx);
ATTEMPT {
CATCH(errctx, akgl_error_init());
CATCH(errctx, test_akgl_registry_init_creation_failures());
} CLEANUP {
} PROCESS(errctx) {
} FINISH_NORETURN(errctx);
}