Files
libakgl/tests/game.c

501 lines
17 KiB
C
Raw Normal View History

Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
/**
* @file game.c
* @brief Unit tests for savegame serialization, version gating, and frame accounting.
*
* akgl_game_init() and akgl_game_update() need a window and a live frame loop,
* so they are out of scope here. Everything else in the game module is either
* pure logic or file IO and is covered below.
*/
#include <SDL3/SDL.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <akerror.h>
#include <akgl/error.h>
#include <akgl/game.h>
#include <akgl/actor.h>
#include <akgl/character.h>
#include <akgl/heap.h>
#include <akgl/registry.h>
#include <akgl/sprite.h>
#include <akgl/staticstring.h>
#include "testutil.h"
/** @brief Scratch savegame path, created and removed by the tests that use it. */
static char savepath[] = "akgl_test_savegame.bin";
/** @brief Scratch path for deliberately malformed savegames. */
static char truncatedpath[] = "akgl_test_truncated.bin";
/** @brief Populate the process-wide game record with a valid identity. */
static void set_game_identity(void)
{
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
memset(&akgl_game, 0x00, sizeof(akgl_Game));
strncpy((char *)&akgl_game.libversion, AKGL_VERSION, 31);
strncpy((char *)&akgl_game.version, "1.2.3", 31);
strncpy((char *)&akgl_game.name, "libakgl test game", 255);
strncpy((char *)&akgl_game.uri, "https://example.invalid/akgl-test", 255);
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
}
akerr_ErrorContext *test_game_load_versioncmp_matching(void)
{
PREPARE_ERROR(e);
ATTEMPT {
TEST_EXPECT_OK(e, akgl_game_load_versioncmp("library", "1.2.3", "1.2.3"),
"identical versions must be compatible");
TEST_EXPECT_OK(e, akgl_game_load_versioncmp("library", "0.1.0", "0.1.0"),
"identical zero-major versions must be compatible");
TEST_EXPECT_OK(e, akgl_game_load_versioncmp("game", "10.20.30", "10.20.30"),
"identical multi-digit versions must be compatible");
} CLEANUP {
} PROCESS(e) {
} FINISH(e, true);
SUCCEED_RETURN(e);
}
akerr_ErrorContext *test_game_load_versioncmp_mismatched(void)
{
PREPARE_ERROR(e);
ATTEMPT {
// A savegame from a different build is refused on any component.
TEST_EXPECT_STATUS(e, AKERR_API, akgl_game_load_versioncmp("library", "2.2.3", "1.2.3"),
"a differing major version must be refused");
TEST_EXPECT_STATUS(e, AKERR_API, akgl_game_load_versioncmp("library", "1.3.3", "1.2.3"),
"a differing minor version must be refused");
TEST_EXPECT_STATUS(e, AKERR_API, akgl_game_load_versioncmp("library", "1.2.4", "1.2.3"),
"a differing patch version must be refused");
// Unparseable versions are a value error, distinct from a mismatch.
TEST_EXPECT_STATUS(e, AKERR_VALUE, akgl_game_load_versioncmp("library", "1.2.3", "not-a-version"),
"an unparseable current version must be refused");
TEST_EXPECT_STATUS(e, AKERR_VALUE, akgl_game_load_versioncmp("library", "not-a-version", "1.2.3"),
"an unparseable savegame version must be refused");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_game_load_versioncmp(NULL, "1.2.3", "1.2.3"),
"versioncmp with a NULL version type");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_game_load_versioncmp("library", NULL, "1.2.3"),
"versioncmp with a NULL new version");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_game_load_versioncmp("library", "1.2.3", NULL),
"versioncmp with a NULL current version");
} CLEANUP {
} PROCESS(e) {
} FINISH(e, true);
SUCCEED_RETURN(e);
}
akerr_ErrorContext *test_game_load_versioncmp_releases_semver(void)
{
PREPARE_ERROR(e);
int i = 0;
bool leaked = false;
ATTEMPT {
// semver_parse allocates; the comparison must free both sides on the
// success and the failure path or a long session will drift.
for ( i = 0; i < 2000; i++ ) {
akerr_ErrorContext *result = akgl_game_load_versioncmp("library", "1.2.3", "1.2.3");
if ( result != NULL ) {
result->handled = true;
result = akerr_release_error(result);
leaked = true;
}
result = akgl_game_load_versioncmp("library", "9.9.9", "1.2.3");
if ( result != NULL ) {
result->handled = true;
result = akerr_release_error(result);
}
}
TEST_ASSERT(e, leaked == false,
"a matching version comparison started failing partway through a long run");
} CLEANUP {
} PROCESS(e) {
} FINISH(e, true);
SUCCEED_RETURN(e);
}
akerr_ErrorContext *test_game_save_roundtrip(void)
{
PREPARE_ERROR(e);
akgl_Game expected;
ATTEMPT {
CATCH(e, akgl_registry_init());
CATCH(e, akgl_heap_init());
set_game_identity();
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
akgl_game.fps = 60;
akgl_game.framesSinceUpdate = 7;
memcpy(&expected, &akgl_game, sizeof(akgl_Game));
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
TEST_EXPECT_OK(e, akgl_game_save((char *)&savepath), "saving a game");
// Scribble over the live state so a successful load has to restore it.
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
akgl_game.fps = 0;
akgl_game.framesSinceUpdate = 0;
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
TEST_EXPECT_OK(e, akgl_game_load((char *)&savepath), "loading the game back");
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
TEST_ASSERT(e, akgl_game.fps == 60, "fps restored as %d, expected 60", akgl_game.fps);
TEST_ASSERT(e, akgl_game.framesSinceUpdate == 7,
"framesSinceUpdate restored as %d, expected 7", akgl_game.framesSinceUpdate);
TEST_ASSERT(e, strncmp((char *)&akgl_game.name, (char *)&expected.name, 256) == 0,
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
"the game name was not preserved across a save and load");
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
TEST_ASSERT(e, strncmp((char *)&akgl_game.version, (char *)&expected.version, 32) == 0,
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
"the game version was not preserved across a save and load");
} CLEANUP {
unlink((char *)&savepath);
} PROCESS(e) {
} FINISH(e, true);
SUCCEED_RETURN(e);
}
akerr_ErrorContext *test_game_load_rejects_foreign_saves(void)
{
PREPARE_ERROR(e);
ATTEMPT {
CATCH(e, akgl_registry_init());
CATCH(e, akgl_heap_init());
// A save written by a different game must not load into this one.
set_game_identity();
CATCH(e, akgl_game_save((char *)&savepath));
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
strncpy((char *)&akgl_game.name, "a completely different game", 255);
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
TEST_EXPECT_STATUS(e, AKERR_API, akgl_game_load((char *)&savepath),
"a savegame with a foreign game name must be refused");
unlink((char *)&savepath);
// Same for a differing URI.
set_game_identity();
CATCH(e, akgl_game_save((char *)&savepath));
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
strncpy((char *)&akgl_game.uri, "https://example.invalid/other", 255);
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
TEST_EXPECT_STATUS(e, AKERR_API, akgl_game_load((char *)&savepath),
"a savegame with a foreign URI must be refused");
unlink((char *)&savepath);
// A save written against a different library version must be refused.
set_game_identity();
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
strncpy((char *)&akgl_game.libversion, "99.98.97", 31);
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
CATCH(e, akgl_game_save((char *)&savepath));
set_game_identity();
TEST_EXPECT_STATUS(e, AKERR_API, akgl_game_load((char *)&savepath),
"a savegame from a different library version must be refused");
unlink((char *)&savepath);
// And one written against a different game version.
set_game_identity();
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
strncpy((char *)&akgl_game.version, "4.5.6", 31);
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
CATCH(e, akgl_game_save((char *)&savepath));
set_game_identity();
TEST_EXPECT_STATUS(e, AKERR_API, akgl_game_load((char *)&savepath),
"a savegame from a different game version must be refused");
} CLEANUP {
unlink((char *)&savepath);
} PROCESS(e) {
} FINISH(e, true);
SUCCEED_RETURN(e);
}
akerr_ErrorContext *test_game_save_load_nullpointers(void)
{
PREPARE_ERROR(e);
ATTEMPT {
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_game_save(NULL),
"akgl_game_save(NULL)");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_game_load(NULL),
"akgl_game_load(NULL)");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_game_save_actors(NULL),
"akgl_game_save_actors(NULL)");
// A path under a directory that does not exist cannot be opened.
TEST_EXPECT_ANY_ERROR(e, akgl_game_save("no_such_directory/save.bin"),
"saving into a nonexistent directory");
TEST_EXPECT_ANY_ERROR(e, akgl_game_load("no_such_file_anywhere.bin"),
"loading a nonexistent savegame");
} CLEANUP {
} PROCESS(e) {
} FINISH(e, true);
SUCCEED_RETURN(e);
}
akerr_ErrorContext *test_game_load_truncated_table(void)
{
PREPARE_ERROR(e);
FILE *fp = NULL;
char partial[64];
ATTEMPT {
CATCH(e, akgl_registry_init());
CATCH(e, akgl_heap_init());
set_game_identity();
// A valid header followed by a table that ends before its sentinel. The
// name-map reader loops until it sees the sentinel, so it has to notice
// EOF instead of spinning.
memset(&partial, 0x00, sizeof(partial));
fp = fopen((char *)&truncatedpath, "wb");
FAIL_ZERO_BREAK(e, fp, AKERR_IO, "unable to create the truncated savegame fixture");
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
FAIL_ZERO_BREAK(e, fwrite(&akgl_game, 1, sizeof(akgl_Game), fp), AKERR_IO,
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
"unable to write the truncated savegame header");
FAIL_ZERO_BREAK(e, fwrite(&partial, 1, sizeof(partial), fp), AKERR_IO,
"unable to write the truncated savegame body");
fclose(fp);
fp = NULL;
TEST_EXPECT_ANY_ERROR(e, akgl_game_load((char *)&truncatedpath),
"loading a savegame whose name table is truncated");
} CLEANUP {
if ( fp != NULL ) {
fclose(fp);
}
unlink((char *)&truncatedpath);
} PROCESS(e) {
} FINISH(e, true);
SUCCEED_RETURN(e);
}
akerr_ErrorContext *test_game_save_writes_name_tables(void)
{
PREPARE_ERROR(e);
akgl_Actor *actor = NULL;
FILE *fp = NULL;
long filesize = 0;
long minimum = 0;
ATTEMPT {
CATCH(e, akgl_registry_init());
CATCH(e, akgl_heap_init());
set_game_identity();
// One registered actor, so the actor table has a real entry ahead of its
// terminating sentinel.
CATCH(e, akgl_heap_next_actor(&actor));
CATCH(e, akgl_actor_initialize(actor, "saved_actor"));
TEST_EXPECT_OK(e, akgl_game_save((char *)&savepath), "saving a game with one actor");
fp = fopen((char *)&savepath, "rb");
FAIL_ZERO_BREAK(e, fp, AKERR_IO, "unable to reopen the savegame");
fseek(fp, 0, SEEK_END);
filesize = ftell(fp);
// The header, then four name tables each ending in a name-sized and a
// pointer-sized sentinel, plus the one real actor entry.
minimum = (long)sizeof(akgl_Game)
+ (long)(AKGL_ACTOR_MAX_NAME_LENGTH + sizeof(akgl_Actor *)) * 2
+ (long)(AKGL_SPRITE_MAX_NAME_LENGTH + sizeof(akgl_Sprite *))
+ (long)(AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH + sizeof(akgl_SpriteSheet *))
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
+ (long)(AKGL_CHARACTER_MAX_NAME_LENGTH + sizeof(akgl_Character *));
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
TEST_ASSERT(e, filesize >= minimum,
"the savegame is %ld bytes, expected at least %ld for the header and four name tables",
filesize, minimum);
} CLEANUP {
if ( fp != NULL ) {
fclose(fp);
}
unlink((char *)&savepath);
} PROCESS(e) {
} FINISH(e, true);
SUCCEED_RETURN(e);
}
akerr_ErrorContext *test_game_state_lock(void)
{
PREPARE_ERROR(e);
ATTEMPT {
set_game_identity();
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
akgl_game.statelock = SDL_CreateMutex();
FAIL_ZERO_BREAK(e, akgl_game.statelock, AKGL_ERR_SDL, "unable to create the state mutex");
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
TEST_EXPECT_OK(e, akgl_game_state_lock(), "taking the state lock");
TEST_EXPECT_OK(e, akgl_game_state_unlock(), "releasing the state lock");
// The lock is reusable after a matched unlock.
TEST_EXPECT_OK(e, akgl_game_state_lock(), "retaking the state lock");
TEST_EXPECT_OK(e, akgl_game_state_unlock(), "releasing the state lock again");
} CLEANUP {
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
if ( akgl_game.statelock != NULL ) {
SDL_DestroyMutex(akgl_game.statelock);
akgl_game.statelock = NULL;
}
} 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;
/** @brief Takes the state mutex and sits on it until lockholder_release is set. */
static int SDLCALL lockholder_thread(void *userdata)
{
SDL_Mutex *statelock = (SDL_Mutex *)userdata;
SDL_LockMutex(statelock);
while ( SDL_GetAtomicInt(&lockholder_release) == 0 ) {
SDL_Delay(10);
}
SDL_UnlockMutex(statelock);
return 0;
}
/**
* @brief akgl_game_state_lock must give up on a contended mutex in about a second.
*
* The uncontended path above never reaches the retry loop, which is where the
* budget lives and where the defect was: the loop counted against a constant
* named "one second in milliseconds" that held 1000000, so it retried 10,000
* times at 100 ms and blocked for roughly sixteen minutes before reporting
* failure. The upper bound below is the assertion that matters. The lower bound
* is there so a build that gave up immediately -- reporting failure without
* waiting at all -- cannot pass either.
*/
akerr_ErrorContext *test_game_state_lock_budget(void)
{
PREPARE_ERROR(e);
SDL_Thread *holder = NULL;
Uint64 started = 0;
Uint64 elapsed = 0;
ATTEMPT {
set_game_identity();
akgl_game.statelock = SDL_CreateMutex();
FAIL_ZERO_BREAK(e, akgl_game.statelock, AKGL_ERR_SDL, "unable to create the state mutex");
SDL_SetAtomicInt(&lockholder_release, 0);
holder = SDL_CreateThread(lockholder_thread, "akgl_test_lockholder", (void *)akgl_game.statelock);
FAIL_ZERO_BREAK(e, holder, AKGL_ERR_SDL, "unable to start the lock-holding thread");
// Wait until the helper actually owns the mutex. Without this the
// measurement races the thread start and the lock is taken on the first
// try, which measures nothing.
while ( SDL_TryLockMutex(akgl_game.statelock) == true ) {
SDL_UnlockMutex(akgl_game.statelock);
SDL_Delay(1);
}
started = SDL_GetTicksNS();
TEST_EXPECT_STATUS(
e,
AKGL_ERR_SDL,
akgl_game_state_lock(),
"taking a state lock another thread is holding");
elapsed = SDL_GetTicksNS() - started;
TEST_ASSERT(
e,
elapsed >= (AKGL_TIME_ONESEC_NS / 2),
"state lock gave up after %" SDL_PRIu64 " ns without waiting out its budget",
elapsed);
TEST_ASSERT(
e,
elapsed < (5 * (Uint64)AKGL_TIME_ONESEC_NS),
"state lock waited %" SDL_PRIu64 " ns on a %d ms budget",
elapsed,
AKGL_GAME_STATE_LOCK_BUDGET_MS);
} CLEANUP {
SDL_SetAtomicInt(&lockholder_release, 1);
if ( holder != NULL ) {
SDL_WaitThread(holder, NULL);
}
if ( akgl_game.statelock != NULL ) {
SDL_DestroyMutex(akgl_game.statelock);
akgl_game.statelock = NULL;
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
}
} PROCESS(e) {
} FINISH(e, true);
SUCCEED_RETURN(e);
}
/** @brief Counts calls made to the low-FPS callback. */
static int lowfps_calls = 0;
/** @brief Low-FPS callback stub that only records that it fired. */
static void stub_lowfps(void)
{
lowfps_calls += 1;
}
akerr_ErrorContext *test_game_updateFPS(void)
{
PREPARE_ERROR(e);
int16_t framesbefore = 0;
ATTEMPT {
set_game_identity();
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
akgl_game.lowfpsfunc = &stub_lowfps;
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
// Below the 30 FPS floor, every update notifies the callback.
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
akgl_game.fps = 10;
akgl_game.lastFPSTime = SDL_GetTicksNS();
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
lowfps_calls = 0;
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
framesbefore = akgl_game.framesSinceUpdate;
akgl_game_update_fps();
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
TEST_ASSERT(e, lowfps_calls == 1,
"a sub-30 FPS update fired the low-FPS callback %d times, expected 1", lowfps_calls);
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
TEST_ASSERT(e, akgl_game.framesSinceUpdate == (framesbefore + 1),
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
"updateFPS did not count the frame (%d, expected %d)",
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
akgl_game.framesSinceUpdate, framesbefore + 1);
TEST_ASSERT(e, akgl_game.lastIterTime != 0, "updateFPS did not stamp lastIterTime");
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
// At or above the floor, the callback stays quiet.
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
akgl_game.fps = 60;
akgl_game.lastFPSTime = SDL_GetTicksNS();
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
lowfps_calls = 0;
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
akgl_game_update_fps();
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
TEST_ASSERT(e, lowfps_calls == 0,
"a 60 FPS update fired the low-FPS callback %d times, expected 0", lowfps_calls);
// Once a full second has elapsed, the frame counter rolls into fps.
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
akgl_game.fps = 60;
akgl_game.framesSinceUpdate = 45;
akgl_game.lastFPSTime = SDL_GetTicksNS() - (2 * (SDL_Time)AKGL_TIME_ONESEC_NS);
akgl_game_update_fps();
TEST_ASSERT(e, akgl_game.fps == 45,
"after a second elapsed, fps rolled over as %d, expected 45", akgl_game.fps);
TEST_ASSERT(e, akgl_game.framesSinceUpdate == 1,
"the frame counter restarted at %d, expected 1", akgl_game.framesSinceUpdate);
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
// The shipped default callback only logs, so it just has to not crash.
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
akgl_game.fps = 1;
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
akgl_game_lowfps();
} CLEANUP {
} PROCESS(e) {
} FINISH(e, true);
SUCCEED_RETURN(e);
}
int main(void)
{
PREPARE_ERROR(errctx);
SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "dummy");
SDL_SetHint(SDL_HINT_AUDIO_DRIVER, "dummy");
ATTEMPT {
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
CATCH(errctx, akgl_error_init());
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
TEST_TRAP_UNHANDLED_ERRORS();
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
CATCH(errctx, akgl_heap_init());
CATCH(errctx, akgl_registry_init());
CATCH(errctx, test_game_load_versioncmp_matching());
CATCH(errctx, test_game_load_versioncmp_mismatched());
CATCH(errctx, test_game_load_versioncmp_releases_semver());
CATCH(errctx, test_game_save_roundtrip());
CATCH(errctx, test_game_load_rejects_foreign_saves());
CATCH(errctx, test_game_save_load_nullpointers());
CATCH(errctx, test_game_load_truncated_table());
CATCH(errctx, test_game_save_writes_name_tables());
CATCH(errctx, test_game_state_lock());
Namespace every exported symbol, and bump to 0.5.0 Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
CATCH(errctx, test_game_state_lock_budget());
Add physics, heap, json_helpers, game, and actor test suites Raise line coverage from 39.6 to 61.8 percent with four new suites and an extension to the actor suite, and register every suite through a single CMake list so a new test file cannot be left out of the coverage fixture. Give the test targets a build-tree RPATH and prepend the build tree to LD_LIBRARY_PATH for CTest, so a developer with a previously installed libakgl.so exercises the library that was just compiled. Fix six defects the new tests exposed: - akgl_physics_simulate read self->gravity_time before its NULL check, so a NULL backend crashed instead of reporting AKERR_NULLPOINTER. - akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose inside the PROCESS switch. An ordinary save never flushed or closed its stream and produced an empty file. - akgl_game_save_actors wrote each name table terminator from the address of a single char, emitting stack contents into the save file and a sentinel the loader could not recognize. - akgl_game_load_objectnamemap used CATCH directly inside while(1), where the break leaves the loop rather than propagating, so a truncated name table loaded as a successful game. - akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no NULL check, unlike their left and right counterparts. - akgl_actor_logic_movement checked actor twice instead of checking actor->basechar before dereferencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
CATCH(errctx, test_game_updateFPS());
} CLEANUP {
} PROCESS(errctx) {
} FINISH_NORETURN(errctx);
}