Report the failures that used to be crashes
Closes Defects items 30 and 31 and Known-and-still-open items 1, 2, 5, 9 and 11. Both string accessors in json_helpers.c ended their ATTEMPT block with FINISH(errctx, false), which swallows the failure, and then strncpy'd through the pointer akgl_heap_next_string never set. So the one condition the pool exists to report -- it is full, which in practice means something is not releasing -- arrived as a segfault somewhere else entirely. It is FINISH(errctx, true) now, and tests/json_helpers.c claims every slot and asserts AKGL_ERR_HEAP comes back out of both. That test segfaults against the old code, which is also how the tilemap leak test in the previous commit confirmed this one. akgl_tilemap_release tested layers[i].texture and destroyed tilesets[i].texture, so every tileset texture was freed twice on one release and no image layer's texture was freed at all. Pointers are cleared as they go, so a second release is safe instead of a use-after-free. akgl_game_update_fps called game.lowfpsfunc() unguarded, on a path taken on frame one because fps is 0 for the first second. Only akgl_game_init installs it, and renderer.h documents the other path deliberately -- a host that owns its window binds a backend instead. It installs the default when it finds NULL. akgl_controller_pushmap and akgl_controller_default checked only the upper bound, so a negative id indexed before akgl_controlmaps. The two test-harness helpers were quietly worthless. akgl_render_and_compare drew t1 on both passes, so it always reported a match and every image assertion built on it asserted nothing; and akgl_compare_sdl_surfaces memcmp'd s1->pitch * s1->h bytes out of both surfaces without checking that the second was the same size, so a smaller one was read past its end. Both fixed, both tested. tests/util.c also now calls the collide-point test it has defined and never run. 25/25 pass, memcheck clean, reindent --check and check_error_protocol clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
41
tests/game.c
41
tests/game.c
@@ -328,6 +328,46 @@ akerr_ErrorContext *test_game_state_lock(void)
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief akgl_game_update_fps must not call a lowfpsfunc nobody installed.
|
||||
*
|
||||
* `game.fps` is 0 for the first second of the process, which is under the
|
||||
* threshold, so this fires on frame one. Only akgl_game_init installs the
|
||||
* default -- and renderer.h documents the other path deliberately: a host that
|
||||
* owns its own window calls akgl_render_2d_bind instead. Such an embedder
|
||||
* crashed here on its first frame, through a NULL function pointer.
|
||||
*/
|
||||
akerr_ErrorContext *test_game_updateFPS_without_a_lowfps_handler(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
|
||||
ATTEMPT {
|
||||
set_game_identity();
|
||||
// Exactly the state a host that never called akgl_game_init is in.
|
||||
akgl_game.lowfpsfunc = NULL;
|
||||
akgl_game.fps = 0;
|
||||
akgl_game.framesSinceUpdate = 0;
|
||||
akgl_game.lastFPSTime = SDL_GetTicksNS();
|
||||
|
||||
akgl_game_update_fps();
|
||||
|
||||
TEST_ASSERT(e, akgl_game.lowfpsfunc != NULL,
|
||||
"akgl_game_update_fps left lowfpsfunc NULL");
|
||||
TEST_ASSERT(e, akgl_game.lowfpsfunc == &akgl_game_lowfps,
|
||||
"akgl_game_update_fps installed something other than the default");
|
||||
|
||||
// And it keeps working on the frames after.
|
||||
akgl_game_update_fps();
|
||||
TEST_ASSERT(e, akgl_game.framesSinceUpdate == 2,
|
||||
"frames counted as %d over two updates, expected 2",
|
||||
akgl_game.framesSinceUpdate);
|
||||
} CLEANUP {
|
||||
akgl_game.lowfpsfunc = &akgl_game_lowfps;
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/** @brief Cleared while the helper thread should keep holding the state mutex. */
|
||||
static SDL_AtomicInt lockholder_release;
|
||||
|
||||
@@ -494,6 +534,7 @@ int main(void)
|
||||
CATCH(errctx, test_game_state_lock());
|
||||
CATCH(errctx, test_game_state_lock_budget());
|
||||
CATCH(errctx, test_game_updateFPS());
|
||||
CATCH(errctx, test_game_updateFPS_without_a_lowfps_handler());
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH_NORETURN(errctx);
|
||||
|
||||
Reference in New Issue
Block a user