From e4aa6a50847a12c22544acf0b3b2953f5128c6af Mon Sep 17 00:00:00 2001 From: Andrew Kesterson Date: Sat, 1 Aug 2026 13:05:43 -0400 Subject: [PATCH] Take libakerror 2.0.1 and drop the workaround it makes obsolete Every libakgl test suite could report success while failing. libakerror's default unhandled-error handler ended in exit(errctx->status), an exit status is one byte wide, and libakgl's band starts at 256 -- so AKGL_ERR_SDL, the most common failure a library built on SDL can have, exited 0 and CTest recorded a pass. tests/character.c aborted at its second of four tests on a bad renderer and was green for months. 0.5.0 worked around that here with TEST_TRAP_UNHANDLED_ERRORS() in tests/testutil.h, and TODO.md ended the entry saying any consumer's suites have the same problem and it was worth raising upstream. It was. 2.0.1 fixes it at the source: akerr_exit() owns the mapping and the default handler calls it, so 0 exits 0, 1 through 255 exit themselves, and anything else exits AKERR_EXIT_STATUS_UNREPRESENTABLE (125). The trap and its 21 call sites are gone. Verified by putting the original failure back rather than by reading the release notes: a FAIL_BREAK(AKGL_ERR_SDL) in tests/character.c's main exits 125 and CTest reports a failure. A standalone consumer raising the same status unhandled exits 125 where it exited 0 before. tests/actor.c installs its own handler and called exit(errctx->status) from it, which is the same defect one layer up. It calls akerr_exit() now. 2.0.0 also makes the error pool and the status registry thread safe, which libakgl needs more than it knew: audio_stream_callback raises error contexts on SDL's audio thread. With an unlocked pool that callback and the main thread could scan AKERR_ARRAY_ERROR at the same time and be handed the same slot. The comment there says so. This is a hard dependency floor, not a preference. 2.0.0 moved __akerr_last_ignored to thread-local storage and made akerr_next_error() return a context that already holds its reference, and both expand at libakgl's call sites -- and at a consumer's, because akerror.h is part of libakgl's public interface. Mixing headers and libraries across that line double-counts every reference and never returns a pool slot. The soname moved to libakerror.so.2; include/akgl/error.h now also feature- tests AKERR_EXIT_STATUS_UNREPRESENTABLE, which is the narrowest probe for 2.0.1 since libakerror publishes no version macro. 0.7.0 for that reason: libakgl's own ABI is unchanged, but the one it re-exports through its headers is not. TODO.md records the pkg-config gap this makes sharper -- akgl.pc names no dependencies at all, so nothing tells a pkg-config consumer which libakerror it needs. Clean build, 26/26 ctest, memcheck clean, warning-clean at -Wall -Werror. libakgl.so.0.7 links libakerror.so.2. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01B8T5FAYXE8HEJqFLCYwNNc --- AGENTS.md | 18 ++++++++++------ CMakeLists.txt | 18 ++++++++++++---- TODO.md | 51 +++++++++++++++++++++++++++++++++----------- deps/libakerror | 2 +- include/akgl/error.h | 15 ++++++++++++- src/audio.c | 8 +++++++ tests/actor.c | 5 +++-- tests/audio.c | 1 - tests/character.c | 1 - tests/controller.c | 1 - tests/draw.c | 1 - tests/error.c | 7 ++---- tests/game.c | 1 - tests/heap.c | 1 - tests/json_helpers.c | 1 - tests/perf.c | 1 - tests/perf_render.c | 1 - tests/physics.c | 1 - tests/physics_sim.c | 1 - tests/registry.c | 1 - tests/renderer.c | 1 - tests/sprite.c | 1 - tests/staticstring.c | 1 - tests/testutil.h | 34 ----------------------------- tests/text.c | 1 - tests/tilemap.c | 1 - tests/util.c | 1 - tests/version.c | 1 - 28 files changed, 92 insertions(+), 85 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6888d6d..705a047 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -592,13 +592,17 @@ if ( defaulted != NULL ) { } ``` -**Every suite's `main()` must call `TEST_TRAP_UNHANDLED_ERRORS()` immediately -after `akgl_error_init()`.** libakerror's default unhandled-error handler ends -in `exit(errctx->status)`, `exit` keeps only the low byte, and libakgl's status -band starts at 256 — so a suite that failed with `AKGL_ERR_SDL` exited 0 and -CTest recorded a pass. That is not hypothetical: `tests/character.c` aborted at -its second of four tests on a bad renderer and was green until 0.5.0. The trap -in `tests/testutil.h` collapses any status a byte cannot carry onto 1. Add focused tests as `tests/.c`, create a matching `test_` target, and register it with `add_test`. Put reusable fixtures in `tests/assets/` and keep paths compatible with tests launched from the build tree. Coverage mode wraps the suite in a CTest fixture so counters are reset before tests and reports are generated afterward. +**Never `exit(3)` on an akerr status — call `akerr_exit()`.** An exit status is +one byte wide and libakgl's status band starts at 256, so `exit(AKGL_ERR_SDL)` +is a wait status of 0 and a shell sees a clean run. Every suite in this +directory once reported success on the most common failure a library built on +SDL can have: `tests/character.c` aborted at its second of four tests on a bad +renderer and was green until 0.5.0. `akerr_exit()` (libakerror 2.0.1) maps 0 to +0, 1–255 to themselves, and anything else to +`AKERR_EXIT_STATUS_UNREPRESENTABLE` (125). The default unhandled-error handler +calls it, so an ordinary suite needs no trap of its own — `tests/testutil.h` +carried one until 0.6.0 and it is gone. A suite that installs its *own* handler +still has to call `akerr_exit()` from it; `tests/actor.c` does. Add focused tests as `tests/.c`, create a matching `test_` target, and register it with `add_test`. Put reusable fixtures in `tests/assets/` and keep paths compatible with tests launched from the build tree. Coverage mode wraps the suite in a CTest fixture so counters are reset before tests and reports are generated afterward. ### Physics simulations diff --git a/CMakeLists.txt b/CMakeLists.txt index 539da58..cb1060f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10) # The single source of truth for the library version. It drives the generated # include/akgl/version.h, the shared library's VERSION and SOVERSION, and the # Version field in akgl.pc. Bump it here and nowhere else. -project(akgl VERSION 0.6.0 LANGUAGES C) +project(akgl VERSION 0.7.0 LANGUAGES C) # Memory checking reuses the suites that already exist -- `ctest -T memcheck` # runs every registered test under valgrind -- rather than adding programs of its @@ -135,8 +135,17 @@ akgl_add_vendored_dependency(SDL3_image::SDL3_image deps/SDL_image) akgl_add_vendored_dependency(SDL3_mixer::SDL3_mixer deps/SDL_mixer) akgl_add_vendored_dependency(SDL3_ttf::SDL3_ttf deps/SDL_ttf) -# libakerror 1.0.0 sizes its own status-name registry; consumers no longer do. -# libakgl claims its status codes at runtime in akgl_heap_init() instead. +# libakerror sizes its own status-name registry; consumers no longer do. libakgl +# claims its status codes at runtime in akgl_heap_init() instead. +# +# The floor is 2.0.1, and it is a hard one. 2.0.0 moved __akerr_last_ignored to +# thread-local storage and made akerr_next_error() return a context that already +# holds its reference -- both of which expand at *libakgl's* call sites through +# IGNORE and the FAIL macros, and at a *consumer's* too, because akerror.h is +# part of libakgl's public interface. A tree that mixes headers and libraries +# across that line double-counts every reference and never returns a pool slot. +# The soname moved to libakerror.so.2 to stop it happening by accident; the +# #error in include/akgl/error.h catches a stale header in an install tree. set(AKGL_SUPPRESS_DEPENDENCY_TESTS FALSE) @@ -165,7 +174,8 @@ endif() # No version here: libakerror ships no akerrorConfigVersion.cmake, so asking # for one makes find_package reject every install. The floor is enforced by # the #error in include/akgl/error.h instead, which feature-tests -# AKERR_FIRST_CONSUMER_STATUS. +# AKERR_FIRST_CONSUMER_STATUS and AKERR_EXIT_STATUS_UNREPRESENTABLE -- the +# latter arriving in 2.0.1, which is the version libakgl needs. if(NOT TARGET akerror::akerror) find_package(akerror REQUIRED) endif() diff --git a/TODO.md b/TODO.md index 2206986..75ac207 100644 --- a/TODO.md +++ b/TODO.md @@ -412,20 +412,25 @@ the symbol collision described in item 4 — and exited 0 because that status wa enough that `TODO.md` recorded the suite as passing and wondered which change had fixed it. Nothing had; it had stopped running. -**Fixed** in `tests/testutil.h`: `TEST_TRAP_UNHANDLED_ERRORS()` installs a -handler that collapses any status a byte cannot carry onto 1, and every suite's -`main()` calls it immediately after `akgl_error_init()`. `tests/error.c` calls -it after its first test instead, because that suite has no `akgl_error_init()` -in `main()` and libakerror's lazy `akerr_init()` would overwrite the handler. +**Fixed twice.** 0.5.0 worked around it here, with a +`TEST_TRAP_UNHANDLED_ERRORS()` in `tests/testutil.h` that installed a handler +collapsing any status a byte cannot carry onto 1. Once installed, no other suite +changed colour, so this was not masking anything beyond `character` — but it +could have been at any time, and nothing would have said so. -Once installed, no other suite changed colour, so this was not masking anything -beyond `character` — but it could have been at any time, and nothing would have -said so. +That entry ended "any consumer's test suites have this problem; that is worth +raising upstream", and it was. **libakerror 2.0.1 fixes it at the source**: +`akerr_exit()` owns the status-to-exit-code mapping and the default handler +calls it, so 0 exits 0, 1 through 255 exit themselves, and anything else exits +`AKERR_EXIT_STATUS_UNREPRESENTABLE` (125) rather than a low byte that is either +a lie or a claim of success. There is no wider `exit()` to reach for — +`_exit()`, `_Exit()`, `quick_exit()` and the raw `exit_group` syscall all +truncate the same way, and even `waitid()` reports the truncated value. -Worth knowing: the fix belongs here rather than in libakerror only because -`exit(status)` is a reasonable thing for a library to do when statuses fit in a -byte. libakerror's own band does. Consumers' bands start at 256 by construction, -so any consumer's test suites have this problem. That is worth raising upstream. +The workaround is gone as of 0.6.0, along with its 21 call sites. Verified by +putting the original failure back: a `FAIL_BREAK(AKGL_ERR_SDL)` in +`tests/character.c`'s `main` now exits 125 and CTest records a failure, where it +exited 0 and passed before. ## Coverage status @@ -1884,6 +1889,28 @@ currently promises the opposite. `aksl_strncpy` already reports exactly that status when the bytes do not fit, so the change is to stop capping `n` at `size - 1` and let it raise. +## akgl.pc names no dependencies + +`akgl.pc.in` carries `Libs: -L${libdir} -lakgl` and no `Requires:` or +`Requires.private:` line, so a consumer that finds libakgl through pkg-config is +told nothing about libakerror, libakstdlib, SDL3, SDL3_image, SDL3_mixer, +SDL3_ttf or jansson. + +That has always been wrong, and libakerror 2.x makes it sharper: `akerror.h` is +part of libakgl's *public* interface -- `IGNORE` and the `FAIL_*` macros expand +in the consumer's own translation units and reference `__akerr_last_ignored`, +which is thread-local as of 2.0.0. A consumer that builds against whatever +`akerror.h` happens to be on its include path and links whatever `libakerror.so` +the loader finds can get a mismatch that pkg-config had every opportunity to +prevent and did not mention. + +The fix is a `Requires:` line for the libraries whose headers libakgl's headers +include (akerror, akstdlib, SDL3) and `Requires.private:` for the rest, plus a +version floor of `akerror >= 2.0.1`. Not done here because it changes what +`pkg-config --libs akgl` emits for every existing consumer, and that wants to be +its own commit with an install-tree test behind it. The CMake path is not +affected -- `akglConfig.cmake` re-finds the targets. + ## libakstdlib wrappers not yet adopted `libakgl` links `libakstdlib` and uses ten of its wrappers. It calls the raw diff --git a/deps/libakerror b/deps/libakerror index 7993636..5eaa956 160000 --- a/deps/libakerror +++ b/deps/libakerror @@ -1 +1 @@ -Subproject commit 79936362d0773b1e5abfe4e6bdd4c3a1f073ad90 +Subproject commit 5eaa956f504611a08204dcaf288e1ad1a571ee21 diff --git a/include/akgl/error.h b/include/akgl/error.h index 4f19e5c..3dba6bc 100644 --- a/include/akgl/error.h +++ b/include/akgl/error.h @@ -24,7 +24,20 @@ * See deps/libakerror/UPGRADING.md. */ #ifndef AKERR_FIRST_CONSUMER_STATUS -#error "libakgl requires libakerror >= 1.0.0: the akerror.h on the include path predates the status registry. Rebuild and reinstall libakerror." +#error "libakgl requires libakerror >= 2.0.1: the akerror.h on the include path predates the status registry. Rebuild and reinstall libakerror." +#endif +// 2.0.0 is an ABI break -- akerr_next_error() returns a context that already +// holds its reference, and __akerr_last_ignored is thread-local -- and both of +// those expand at *this* library's call sites through IGNORE and the FAIL +// macros. A libakgl built against a 1.x header and linked against 2.x +// double-counts every reference and never returns a slot to the pool. The +// soname moved to libakerror.so.2 so the two cannot be mixed by accident, but +// the header can still be stale in an install tree, so check it here too. +// +// AKERR_EXIT_STATUS_UNREPRESENTABLE arrived in 2.0.1 and is the narrowest thing +// to probe for: libakerror publishes no version macro. +#ifndef AKERR_EXIT_STATUS_UNREPRESENTABLE +#error "libakgl requires libakerror >= 2.0.1: the akerror.h on the include path predates akerr_exit(). Rebuild and reinstall libakerror." #endif // Silences -Wformat-truncation on a string concatenation that genuinely may not diff --git a/src/audio.c b/src/audio.c index 9ffdf2f..f02d284 100644 --- a/src/audio.c +++ b/src/audio.c @@ -273,6 +273,14 @@ static void SDLCALL audio_stream_callback(void *userdata, SDL_AudioStream *strea if ( errctx != NULL ) { // There is nobody to return an error to on the audio thread, and // refusing to write leaves SDL underrunning. Report and go quiet. + // + // Raising an error here at all is only safe from libakerror 2.0.0 + // on. Before that the pool was unlocked, so this callback and the + // main thread could scan AKERR_ARRAY_ERROR at the same time and be + // handed the same slot -- two threads writing one context, and one + // of them releasing it out from under the other. 2.0.0 makes + // finding a free slot and claiming it one operation under a lock. + // libakgl requires it; see the guard in include/akgl/error.h. LOG_ERROR_WITH_MESSAGE(errctx, "** AUDIO CALLBACK **"); errctx->handled = true; errctx = akerr_release_error(errctx); diff --git a/tests/actor.c b/tests/actor.c index 7876cb0..41c81fc 100644 --- a/tests/actor.c +++ b/tests/actor.c @@ -30,7 +30,9 @@ void handle_unhandled_error_noexit(akerr_ErrorContext *errctx) return; } if ( UNHANDLED_ERROR_BEHAVIOR == UNHANDLED_ERROR_EXIT ) { - exit(errctx->status); + // akerr_exit rather than exit(3): a status past 255 does not survive a + // wait status, and this suite's are AKGL_* codes, which all are. + akerr_exit(errctx->status); } if ( UNHANDLED_ERROR_BEHAVIOR == UNHANDLED_ERROR_SET ) { unhandled_error_context = errctx; @@ -1037,7 +1039,6 @@ int main(void) ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); CATCH(errctx, akgl_registry_init_actor()); CATCH(errctx, akgl_registry_init_sprite()); CATCH(errctx, akgl_registry_init_spritesheet()); diff --git a/tests/audio.c b/tests/audio.c index 5c6b1cd..4a415df 100644 --- a/tests/audio.c +++ b/tests/audio.c @@ -602,7 +602,6 @@ int main(void) ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); FAIL_ZERO_BREAK( errctx, SDL_Init(SDL_INIT_AUDIO), diff --git a/tests/character.c b/tests/character.c index 40911e1..794cfc4 100644 --- a/tests/character.c +++ b/tests/character.c @@ -286,7 +286,6 @@ int main(void) PREPARE_ERROR(errctx); ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); akgl_renderer = &akgl_default_renderer; SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest"); diff --git a/tests/controller.c b/tests/controller.c index b787472..d5c4d19 100644 --- a/tests/controller.c +++ b/tests/controller.c @@ -858,7 +858,6 @@ int main(void) ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); FAIL_ZERO_BREAK( errctx, SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD), diff --git a/tests/draw.c b/tests/draw.c index 97eaf11..8088b4e 100644 --- a/tests/draw.c +++ b/tests/draw.c @@ -638,7 +638,6 @@ int main(void) ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); akgl_renderer = &akgl_default_renderer; FAIL_ZERO_BREAK( diff --git a/tests/error.c b/tests/error.c index e070e4b..379cbab 100644 --- a/tests/error.c +++ b/tests/error.c @@ -95,12 +95,9 @@ int main(void) ATTEMPT { // Unlike every other suite, this one has no akgl_error_init() in - // main() -- the first test is what brings the subsystem up. The trap - // therefore goes after it: libakerror installs its own default handler - // from the lazy akerr_init() inside that first call, and would - // overwrite anything set beforehand. + // main() -- the first test is what brings the subsystem up, and + // asserting that it does is the point of it. CATCH(errctx, test_error_init_owns_the_status_band()); - TEST_TRAP_UNHANDLED_ERRORS(); CATCH(errctx, test_error_init_is_idempotent()); } CLEANUP { } PROCESS(errctx) { diff --git a/tests/game.c b/tests/game.c index 814202b..47b7716 100644 --- a/tests/game.c +++ b/tests/game.c @@ -775,7 +775,6 @@ int main(void) ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_registry_init()); diff --git a/tests/heap.c b/tests/heap.c index 42ebf97..eb427f9 100644 --- a/tests/heap.c +++ b/tests/heap.c @@ -362,7 +362,6 @@ int main(void) ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_registry_init()); diff --git a/tests/json_helpers.c b/tests/json_helpers.c index ffd088b..f34b755 100644 --- a/tests/json_helpers.c +++ b/tests/json_helpers.c @@ -528,7 +528,6 @@ int main(void) ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_registry_init()); CATCH(errctx, load_fixture()); diff --git a/tests/perf.c b/tests/perf.c index 42a9690..7a6643a 100644 --- a/tests/perf.c +++ b/tests/perf.c @@ -901,7 +901,6 @@ int main(void) ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_registry_init()); CATCH(errctx, akgl_registry_init_properties()); diff --git a/tests/perf_render.c b/tests/perf_render.c index 6d6271c..1915b68 100644 --- a/tests/perf_render.c +++ b/tests/perf_render.c @@ -752,7 +752,6 @@ int main(void) ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); akgl_renderer = &akgl_default_renderer; akgl_physics = &akgl_default_physics; akgl_camera = &akgl_default_camera; diff --git a/tests/physics.c b/tests/physics.c index 09e15b0..3e2daf7 100644 --- a/tests/physics.c +++ b/tests/physics.c @@ -784,7 +784,6 @@ int main(void) ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_registry_init()); CATCH(errctx, akgl_registry_init_properties()); diff --git a/tests/physics_sim.c b/tests/physics_sim.c index ad70c17..fe80162 100644 --- a/tests/physics_sim.c +++ b/tests/physics_sim.c @@ -616,7 +616,6 @@ int main(void) ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); // SDL_GetTicksNS() counts from SDL's own initialization, and // akgl_physics_simulate measures dt against it. Without this the clock // epoch would be established by the first call inside the first diff --git a/tests/registry.c b/tests/registry.c index 8093129..41dddcc 100644 --- a/tests/registry.c +++ b/tests/registry.c @@ -288,7 +288,6 @@ int main(void) PREPARE_ERROR(errctx); ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); CATCH(errctx, test_akgl_registry_init_creation_failures()); CATCH(errctx, test_akgl_get_property_copies_only_the_value()); CATCH(errctx, test_actor_state_string_names()); diff --git a/tests/renderer.c b/tests/renderer.c index 72586e6..6d2ad1b 100644 --- a/tests/renderer.c +++ b/tests/renderer.c @@ -250,7 +250,6 @@ int main(void) ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); memset(&bound, 0x00, sizeof(akgl_RenderBackend)); FAIL_ZERO_BREAK( diff --git a/tests/sprite.c b/tests/sprite.c index f83977c..65147bf 100644 --- a/tests/sprite.c +++ b/tests/sprite.c @@ -242,7 +242,6 @@ int main(void) ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); akgl_renderer = &akgl_default_renderer; SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest"); diff --git a/tests/staticstring.c b/tests/staticstring.c index 5630540..8d2d70d 100644 --- a/tests/staticstring.c +++ b/tests/staticstring.c @@ -241,7 +241,6 @@ int main(void) PREPARE_ERROR(errctx); ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); printf("test_fresh_heap_gives_string ....\n"); test_fresh_heap_gives_strings(); reset_string_heap(); diff --git a/tests/testutil.h b/tests/testutil.h index 9896707..c84fcb7 100644 --- a/tests/testutil.h +++ b/tests/testutil.h @@ -43,40 +43,6 @@ static inline int test_string_pool_used(void) return used; } -/** - * @brief Exit with a status the shell can actually see. - * - * libakerror's default unhandled-error handler ends in `exit(errctx->status)`. - * `exit` keeps only the low byte of what it is given, and libakgl's own - * statuses start at `AKERR_FIRST_CONSUMER_STATUS`, which is 256 -- so - * #AKGL_ERR_SDL exits 0, and CTest records a pass. Every suite in this - * directory that failed for the most common reason in this library therefore - * reported success. `tests/character.c` did exactly that: it aborted at its - * second of four tests on a bad renderer and was green for months. - * - * This collapses any status that a byte cannot carry onto 1. It does not log -- - * `FINISH_NORETURN` has already logged the context and its stack by the time it - * calls the handler. - */ -static inline void test_handler_unhandled_error(akerr_ErrorContext *errctx) -{ - int status = ( errctx == NULL ) ? 1 : (errctx->status & 0xFF); - - if ( status == 0 ) { - status = 1; - } - exit(status); -} - -/** - * @brief Install test_handler_unhandled_error(). Call once, after akgl_error_init(). - * - * akgl_error_init() reaches akerr_init(), which installs libakerror's default - * handler, so this has to come after it rather than before. - */ -#define TEST_TRAP_UNHANDLED_ERRORS() \ - (akerr_handler_unhandled_error = &test_handler_unhandled_error) - /** @brief Fail the enclosing ATTEMPT block unless @p cond holds. */ #define TEST_ASSERT(e, cond, ...) \ if ( ! (cond) ) { \ diff --git a/tests/text.c b/tests/text.c index 2771ef0..576a27f 100644 --- a/tests/text.c +++ b/tests/text.c @@ -392,7 +392,6 @@ int main(void) ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); memset(&testbackend, 0x00, sizeof(akgl_RenderBackend)); FAIL_ZERO_BREAK( errctx, diff --git a/tests/tilemap.c b/tests/tilemap.c index 4e41119..233146f 100644 --- a/tests/tilemap.c +++ b/tests/tilemap.c @@ -742,7 +742,6 @@ int main(void) ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); akgl_gamemap = &akgl_default_gamemap; akgl_renderer = &akgl_default_renderer; SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest"); diff --git a/tests/util.c b/tests/util.c index 7203295..d39281e 100644 --- a/tests/util.c +++ b/tests/util.c @@ -457,7 +457,6 @@ int main(void) PREPARE_ERROR(errctx); ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); CATCH(errctx, test_akgl_rectangle_points_nullpointers()); CATCH(errctx, test_akgl_rectangle_points_math()); CATCH(errctx, test_akgl_collide_point_rectangle_nullpointers()); diff --git a/tests/version.c b/tests/version.c index ccff787..8872031 100644 --- a/tests/version.c +++ b/tests/version.c @@ -99,7 +99,6 @@ int main(void) ATTEMPT { CATCH(errctx, akgl_error_init()); - TEST_TRAP_UNHANDLED_ERRORS(); CATCH(errctx, test_version_string_matches_components()); CATCH(errctx, test_version_linked_matches_compiled()); CATCH(errctx, test_version_at_least_boundaries());