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>
This commit is contained in:
@@ -266,10 +266,10 @@ akerr_ErrorContext *test_actor_manage_children(void)
|
||||
CATCH(errctx, akgl_heap_release_actor(parent));
|
||||
// All actor objects on the heap should be empty now
|
||||
for ( i = 0; i < AKGL_MAX_HEAP_ACTOR; i++) {
|
||||
FAIL_NONZERO_BREAK(errctx, HEAP_ACTOR[i].refcount, AKERR_VALUE, "Actor not properly cleared");
|
||||
FAIL_NONZERO_BREAK(errctx, HEAP_ACTOR[i].parent, AKERR_VALUE, "Actor not properly cleared");
|
||||
FAIL_NONZERO_BREAK(errctx, akgl_heap_actors[i].refcount, AKERR_VALUE, "Actor not properly cleared");
|
||||
FAIL_NONZERO_BREAK(errctx, akgl_heap_actors[i].parent, AKERR_VALUE, "Actor not properly cleared");
|
||||
for ( j = 0 ; j < AKGL_ACTOR_MAX_CHILDREN; j++) {
|
||||
if ( HEAP_ACTOR[i].children[j] != NULL ) {
|
||||
if ( akgl_heap_actors[i].children[j] != NULL ) {
|
||||
FAIL(errctx, AKERR_VALUE, "Actor not properly cleared");
|
||||
goto _test_actor_addchild_heaprelease_cleanup;
|
||||
}
|
||||
@@ -375,7 +375,7 @@ akerr_ErrorContext *test_actor_control_handlers_on(void)
|
||||
memset(&actor, 0x00, sizeof(akgl_Actor));
|
||||
actor.basechar = &basechar;
|
||||
actor.state = (AKGL_ACTOR_STATE_FACE_RIGHT | AKGL_ACTOR_STATE_MOVING_RIGHT | AKGL_ACTOR_STATE_ALIVE);
|
||||
TEST_EXPECT_OK(e, akgl_Actor_cmhf_left_on(&actor, &event), "left on");
|
||||
TEST_EXPECT_OK(e, akgl_actor_cmhf_left_on(&actor, &event), "left on");
|
||||
TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_MOVING_LEFT),
|
||||
"left on did not set MOVING_LEFT (state %d)", actor.state);
|
||||
TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_FACE_LEFT),
|
||||
@@ -391,7 +391,7 @@ akerr_ErrorContext *test_actor_control_handlers_on(void)
|
||||
// Right: same, with the sign preserved.
|
||||
memset(&actor, 0x00, sizeof(akgl_Actor));
|
||||
actor.basechar = &basechar;
|
||||
TEST_EXPECT_OK(e, akgl_Actor_cmhf_right_on(&actor, &event), "right on");
|
||||
TEST_EXPECT_OK(e, akgl_actor_cmhf_right_on(&actor, &event), "right on");
|
||||
TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_MOVING_RIGHT),
|
||||
"right on did not set MOVING_RIGHT (state %d)", actor.state);
|
||||
TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_FACE_RIGHT),
|
||||
@@ -401,7 +401,7 @@ akerr_ErrorContext *test_actor_control_handlers_on(void)
|
||||
// Up reverses the Y acceleration, because Y grows downward.
|
||||
memset(&actor, 0x00, sizeof(akgl_Actor));
|
||||
actor.basechar = &basechar;
|
||||
TEST_EXPECT_OK(e, akgl_Actor_cmhf_up_on(&actor, &event), "up on");
|
||||
TEST_EXPECT_OK(e, akgl_actor_cmhf_up_on(&actor, &event), "up on");
|
||||
TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_MOVING_UP),
|
||||
"up on did not set MOVING_UP (state %d)", actor.state);
|
||||
TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_FACE_UP),
|
||||
@@ -410,7 +410,7 @@ akerr_ErrorContext *test_actor_control_handlers_on(void)
|
||||
|
||||
memset(&actor, 0x00, sizeof(akgl_Actor));
|
||||
actor.basechar = &basechar;
|
||||
TEST_EXPECT_OK(e, akgl_Actor_cmhf_down_on(&actor, &event), "down on");
|
||||
TEST_EXPECT_OK(e, akgl_actor_cmhf_down_on(&actor, &event), "down on");
|
||||
TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_MOVING_DOWN),
|
||||
"down on did not set MOVING_DOWN (state %d)", actor.state);
|
||||
TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_FACE_DOWN),
|
||||
@@ -420,8 +420,8 @@ akerr_ErrorContext *test_actor_control_handlers_on(void)
|
||||
// Each direction is exclusive: turning right after left leaves only right.
|
||||
memset(&actor, 0x00, sizeof(akgl_Actor));
|
||||
actor.basechar = &basechar;
|
||||
TEST_EXPECT_OK(e, akgl_Actor_cmhf_left_on(&actor, &event), "left on before turning");
|
||||
TEST_EXPECT_OK(e, akgl_Actor_cmhf_right_on(&actor, &event), "right on after left");
|
||||
TEST_EXPECT_OK(e, akgl_actor_cmhf_left_on(&actor, &event), "left on before turning");
|
||||
TEST_EXPECT_OK(e, akgl_actor_cmhf_right_on(&actor, &event), "right on after left");
|
||||
TEST_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_MOVING_LEFT),
|
||||
"turning right left MOVING_LEFT set (state %d)", actor.state);
|
||||
} CLEANUP {
|
||||
@@ -450,7 +450,7 @@ akerr_ErrorContext *test_actor_control_handlers_off(void)
|
||||
actor.ax = 5; actor.ex = 5; actor.tx = 5; actor.vx = 5;
|
||||
actor.ay = 5; actor.ey = 5; actor.ty = 5; actor.vy = 5;
|
||||
actor.state = (AKGL_ACTOR_STATE_MOVING_LEFT | AKGL_ACTOR_STATE_FACE_LEFT);
|
||||
TEST_EXPECT_OK(e, akgl_Actor_cmhf_left_off(&actor, &event), "left off");
|
||||
TEST_EXPECT_OK(e, akgl_actor_cmhf_left_off(&actor, &event), "left off");
|
||||
TEST_ASSERT_FEQ(e, actor.ax, 0.0f, "left off left ax at %f", actor.ax);
|
||||
TEST_ASSERT_FEQ(e, actor.ex, 0.0f, "left off left ex at %f", actor.ex);
|
||||
TEST_ASSERT_FEQ(e, actor.tx, 0.0f, "left off left tx at %f", actor.tx);
|
||||
@@ -466,7 +466,7 @@ akerr_ErrorContext *test_actor_control_handlers_off(void)
|
||||
memset(&actor, 0x00, sizeof(akgl_Actor));
|
||||
actor.ax = 5; actor.ex = 5; actor.tx = 5; actor.vx = 5;
|
||||
actor.state = AKGL_ACTOR_STATE_MOVING_RIGHT;
|
||||
TEST_EXPECT_OK(e, akgl_Actor_cmhf_right_off(&actor, &event), "right off");
|
||||
TEST_EXPECT_OK(e, akgl_actor_cmhf_right_off(&actor, &event), "right off");
|
||||
TEST_ASSERT_FEQ(e, actor.vx, 0.0f, "right off left vx at %f", actor.vx);
|
||||
TEST_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_MOVING_RIGHT),
|
||||
"right off did not clear MOVING_RIGHT (state %d)", actor.state);
|
||||
@@ -474,7 +474,7 @@ akerr_ErrorContext *test_actor_control_handlers_off(void)
|
||||
memset(&actor, 0x00, sizeof(akgl_Actor));
|
||||
actor.ay = 5; actor.ey = 5; actor.ty = 5; actor.vy = 5;
|
||||
actor.state = AKGL_ACTOR_STATE_MOVING_UP;
|
||||
TEST_EXPECT_OK(e, akgl_Actor_cmhf_up_off(&actor, &event), "up off");
|
||||
TEST_EXPECT_OK(e, akgl_actor_cmhf_up_off(&actor, &event), "up off");
|
||||
TEST_ASSERT_FEQ(e, actor.vy, 0.0f, "up off left vy at %f", actor.vy);
|
||||
TEST_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_MOVING_UP),
|
||||
"up off did not clear MOVING_UP (state %d)", actor.state);
|
||||
@@ -482,7 +482,7 @@ akerr_ErrorContext *test_actor_control_handlers_off(void)
|
||||
memset(&actor, 0x00, sizeof(akgl_Actor));
|
||||
actor.ay = 5; actor.ey = 5; actor.ty = 5; actor.vy = 5;
|
||||
actor.state = AKGL_ACTOR_STATE_MOVING_DOWN;
|
||||
TEST_EXPECT_OK(e, akgl_Actor_cmhf_down_off(&actor, &event), "down off");
|
||||
TEST_EXPECT_OK(e, akgl_actor_cmhf_down_off(&actor, &event), "down off");
|
||||
TEST_ASSERT_FEQ(e, actor.vy, 0.0f, "down off left vy at %f", actor.vy);
|
||||
TEST_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_MOVING_DOWN),
|
||||
"down off did not clear MOVING_DOWN (state %d)", actor.state);
|
||||
@@ -502,33 +502,33 @@ akerr_ErrorContext *test_actor_control_handlers_nullpointers(void)
|
||||
memset(&event, 0x00, sizeof(SDL_Event));
|
||||
memset(&actor, 0x00, sizeof(akgl_Actor));
|
||||
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_left_on(NULL, &event), "left on, NULL actor");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_left_off(NULL, &event), "left off, NULL actor");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_right_on(NULL, &event), "right on, NULL actor");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_right_off(NULL, &event), "right off, NULL actor");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_up_on(NULL, &event), "up on, NULL actor");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_up_off(NULL, &event), "up off, NULL actor");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_down_on(NULL, &event), "down on, NULL actor");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_down_off(NULL, &event), "down off, NULL actor");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_left_on(NULL, &event), "left on, NULL actor");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_left_off(NULL, &event), "left off, NULL actor");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_right_on(NULL, &event), "right on, NULL actor");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_right_off(NULL, &event), "right off, NULL actor");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_up_on(NULL, &event), "up on, NULL actor");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_up_off(NULL, &event), "up off, NULL actor");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_down_on(NULL, &event), "down on, NULL actor");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_down_off(NULL, &event), "down off, NULL actor");
|
||||
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_left_on(&actor, NULL), "left on, NULL event");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_left_off(&actor, NULL), "left off, NULL event");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_right_on(&actor, NULL), "right on, NULL event");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_right_off(&actor, NULL), "right off, NULL event");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_up_on(&actor, NULL), "up on, NULL event");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_up_off(&actor, NULL), "up off, NULL event");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_down_on(&actor, NULL), "down on, NULL event");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_down_off(&actor, NULL), "down off, NULL event");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_left_on(&actor, NULL), "left on, NULL event");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_left_off(&actor, NULL), "left off, NULL event");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_right_on(&actor, NULL), "right on, NULL event");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_right_off(&actor, NULL), "right off, NULL event");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_up_on(&actor, NULL), "up on, NULL event");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_up_off(&actor, NULL), "up off, NULL event");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_down_on(&actor, NULL), "down on, NULL event");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_down_off(&actor, NULL), "down off, NULL event");
|
||||
|
||||
// A movement handler reads acceleration off the base character, so an
|
||||
// actor without one has to be reported rather than dereferenced.
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_left_on(&actor, &event),
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_left_on(&actor, &event),
|
||||
"left on, actor with no base character");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_right_on(&actor, &event),
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_right_on(&actor, &event),
|
||||
"right on, actor with no base character");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_up_on(&actor, &event),
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_up_on(&actor, &event),
|
||||
"up on, actor with no base character");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_down_on(&actor, &event),
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_down_on(&actor, &event),
|
||||
"down on, actor with no base character");
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
@@ -879,12 +879,12 @@ akerr_ErrorContext *test_actor_sprite_sheet_coords(void)
|
||||
sprite.width = 32;
|
||||
sprite.height = 32;
|
||||
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_sprite_sheet_coords_for_frame(NULL, &coords, 0),
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_spritesheet_coords_for_frame(NULL, &coords, 0),
|
||||
"sheet coords with a NULL sprite");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_sprite_sheet_coords_for_frame(&sprite, NULL, 0),
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_spritesheet_coords_for_frame(&sprite, NULL, 0),
|
||||
"sheet coords with a NULL rectangle");
|
||||
// The sprite has no sheet attached, so there is no texture to measure against.
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_sprite_sheet_coords_for_frame(&sprite, &coords, 0),
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_spritesheet_coords_for_frame(&sprite, &coords, 0),
|
||||
"sheet coords with a NULL spritesheet");
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
@@ -904,6 +904,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, akgl_registry_init_actor());
|
||||
CATCH(errctx, akgl_registry_init_sprite());
|
||||
CATCH(errctx, akgl_registry_init_spritesheet());
|
||||
|
||||
@@ -602,6 +602,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
SDL_Init(SDL_INIT_AUDIO),
|
||||
|
||||
@@ -7,9 +7,21 @@
|
||||
#include <akgl/heap.h>
|
||||
#include <akgl/registry.h>
|
||||
#include <akgl/iterator.h>
|
||||
#include <akgl/game.h>
|
||||
#include <akgl/renderer.h>
|
||||
|
||||
SDL_Window *window;
|
||||
SDL_Renderer *renderer;
|
||||
#include "testutil.h"
|
||||
|
||||
/*
|
||||
* This suite used to declare its own file-scope `window` and `renderer`, back
|
||||
* when the library's globals carried those bare names too. Both definitions had
|
||||
* external linkage and the same spelling, so the executable's preempted the
|
||||
* shared library's: akgl_sprite_load_json read the test's `SDL_Renderer *`
|
||||
* through an `akgl_RenderBackend *` and happened not to crash. That is the
|
||||
* collision the akgl_ prefix on exported globals exists to prevent, and it was
|
||||
* invisible until they were renamed. The suite now binds a real backend, the
|
||||
* way tests/sprite.c and tests/text.c do.
|
||||
*/
|
||||
|
||||
akerr_ErrorContext *test_akgl_character_initialize()
|
||||
{
|
||||
@@ -196,15 +208,19 @@ int main(void)
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
|
||||
SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest");
|
||||
|
||||
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO )) {
|
||||
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't initialize SDL: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
if (!SDL_CreateWindowAndRenderer("net/aklabs/libakgl/test_character", 640, 480, SDL_WINDOW_HIDDEN, &window, &renderer)) {
|
||||
if (!SDL_CreateWindowAndRenderer("net/aklabs/libakgl/test_character", 640, 480, SDL_WINDOW_HIDDEN, &akgl_window, &akgl_renderer->sdl_renderer)) {
|
||||
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't create window/renderer: %s", SDL_GetError());
|
||||
}
|
||||
CATCH(errctx, akgl_render_2d_bind(akgl_renderer));
|
||||
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
CATCH(errctx, akgl_registry_init());
|
||||
|
||||
@@ -76,7 +76,7 @@ int main(void)
|
||||
actorptr->visible = true;
|
||||
|
||||
// set up the control map
|
||||
controlmap = &GAME_ControlMaps[0];
|
||||
controlmap = &akgl_controlmaps[0];
|
||||
controlmap->kbid = 0;
|
||||
controlmap->target = SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, "player", NULL);
|
||||
// Move down
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include <akerror.h>
|
||||
|
||||
#include <akgl/error.h>
|
||||
// For the akgl_Actor_cmhf_* handlers these tests bind. akgl/controller.h pulls
|
||||
// For the akgl_actor_cmhf_* handlers these tests bind. akgl/controller.h pulls
|
||||
// actor.h in for itself now; tests/headers.c is what keeps it doing so.
|
||||
#include <akgl/actor.h>
|
||||
#include <akgl/character.h>
|
||||
@@ -52,7 +52,7 @@ static void reset_control_maps(void)
|
||||
{
|
||||
int i = 0;
|
||||
for ( i = 0; i < AKGL_MAX_CONTROL_MAPS; i++ ) {
|
||||
memset(&GAME_ControlMaps[i], 0x00, sizeof(akgl_ControlMap));
|
||||
memset(&akgl_controlmaps[i], 0x00, sizeof(akgl_ControlMap));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,30 +129,30 @@ akerr_ErrorContext *test_controller_pushmap(void)
|
||||
control.key = SDLK_SPACE;
|
||||
control.event_on = SDL_EVENT_KEY_DOWN;
|
||||
control.event_off = SDL_EVENT_KEY_UP;
|
||||
control.handler_on = &akgl_Actor_cmhf_left_on;
|
||||
control.handler_off = &akgl_Actor_cmhf_left_off;
|
||||
control.handler_on = &akgl_actor_cmhf_left_on;
|
||||
control.handler_off = &akgl_actor_cmhf_left_off;
|
||||
|
||||
TEST_EXPECT_OK(e, akgl_controller_pushmap(0, &control), "pushing one control");
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].nextMap == 1,
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].nextMap == 1,
|
||||
"pushing one control left nextMap at %d, expected 1",
|
||||
GAME_ControlMaps[0].nextMap);
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].controls[0].key == SDLK_SPACE,
|
||||
akgl_controlmaps[0].nextMap);
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].controls[0].key == SDLK_SPACE,
|
||||
"the pushed control did not land in slot 0");
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].controls[0].handler_on == &akgl_Actor_cmhf_left_on,
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].controls[0].handler_on == &akgl_actor_cmhf_left_on,
|
||||
"the pushed control lost its press handler");
|
||||
|
||||
// Pushes accumulate rather than overwrite.
|
||||
TEST_EXPECT_OK(e, akgl_controller_pushmap(0, &control), "pushing a second control");
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].nextMap == 2,
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].nextMap == 2,
|
||||
"pushing a second control left nextMap at %d, expected 2",
|
||||
GAME_ControlMaps[0].nextMap);
|
||||
akgl_controlmaps[0].nextMap);
|
||||
|
||||
// Maps are independent of each other.
|
||||
TEST_EXPECT_OK(e, akgl_controller_pushmap(3, &control), "pushing into a different map");
|
||||
TEST_ASSERT(e, GAME_ControlMaps[3].nextMap == 1,
|
||||
"map 3 nextMap is %d, expected 1", GAME_ControlMaps[3].nextMap);
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].nextMap == 2,
|
||||
"pushing into map 3 disturbed map 0 (nextMap %d)", GAME_ControlMaps[0].nextMap);
|
||||
TEST_ASSERT(e, akgl_controlmaps[3].nextMap == 1,
|
||||
"map 3 nextMap is %d, expected 1", akgl_controlmaps[3].nextMap);
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].nextMap == 2,
|
||||
"pushing into map 3 disturbed map 0 (nextMap %d)", akgl_controlmaps[0].nextMap);
|
||||
|
||||
// Filling a map exactly to capacity still succeeds.
|
||||
reset_control_maps();
|
||||
@@ -164,9 +164,9 @@ akerr_ErrorContext *test_controller_pushmap(void)
|
||||
FAIL_BREAK(e, AKGL_ERR_BEHAVIOR, "pushing control %d of %d failed", i, AKGL_MAX_CONTROLS);
|
||||
}
|
||||
}
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].nextMap == AKGL_MAX_CONTROLS,
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].nextMap == AKGL_MAX_CONTROLS,
|
||||
"a full map reports nextMap %d, expected %d",
|
||||
GAME_ControlMaps[0].nextMap, AKGL_MAX_CONTROLS);
|
||||
akgl_controlmaps[0].nextMap, AKGL_MAX_CONTROLS);
|
||||
|
||||
// One past capacity is refused.
|
||||
TEST_EXPECT_STATUS(e, AKERR_OUTOFBOUNDS, akgl_controller_pushmap(0, &control),
|
||||
@@ -198,38 +198,38 @@ akerr_ErrorContext *test_controller_default_bindings(void)
|
||||
TEST_EXPECT_OK(e, akgl_controller_default(0, "player", TEST_KBID, TEST_JSID),
|
||||
"installing the default control map");
|
||||
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].target == player,
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].target == player,
|
||||
"the default map did not target the player actor");
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].kbid == TEST_KBID,
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].kbid == TEST_KBID,
|
||||
"the default map recorded keyboard %d, expected %d",
|
||||
GAME_ControlMaps[0].kbid, TEST_KBID);
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].jsid == TEST_JSID,
|
||||
akgl_controlmaps[0].kbid, TEST_KBID);
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].jsid == TEST_JSID,
|
||||
"the default map recorded gamepad %d, expected %d",
|
||||
GAME_ControlMaps[0].jsid, TEST_JSID);
|
||||
akgl_controlmaps[0].jsid, TEST_JSID);
|
||||
|
||||
// Four keyboard bindings then four gamepad bindings.
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].nextMap == 8,
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].nextMap == 8,
|
||||
"the default map installed %d controls, expected 8",
|
||||
GAME_ControlMaps[0].nextMap);
|
||||
akgl_controlmaps[0].nextMap);
|
||||
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].controls[0].key == SDLK_DOWN,
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].controls[0].key == SDLK_DOWN,
|
||||
"the first default binding is not the down arrow");
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].controls[0].handler_on == &akgl_Actor_cmhf_down_on,
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].controls[0].handler_on == &akgl_actor_cmhf_down_on,
|
||||
"the down arrow is not bound to the down handler");
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].controls[1].key == SDLK_UP,
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].controls[1].key == SDLK_UP,
|
||||
"the second default binding is not the up arrow");
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].controls[2].key == SDLK_LEFT,
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].controls[2].key == SDLK_LEFT,
|
||||
"the third default binding is not the left arrow");
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].controls[3].key == SDLK_RIGHT,
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].controls[3].key == SDLK_RIGHT,
|
||||
"the fourth default binding is not the right arrow");
|
||||
|
||||
// The gamepad half binds buttons and leaves the keycode clear, so a
|
||||
// keyboard event cannot accidentally match a dpad binding.
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].controls[4].button == SDL_GAMEPAD_BUTTON_DPAD_DOWN,
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].controls[4].button == SDL_GAMEPAD_BUTTON_DPAD_DOWN,
|
||||
"the fifth default binding is not the dpad down button");
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].controls[4].key == 0,
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].controls[4].key == 0,
|
||||
"a gamepad binding also carries a keycode");
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].controls[7].button == SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].controls[7].button == SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
|
||||
"the eighth default binding is not the dpad right button");
|
||||
|
||||
// An unknown actor name is a registry error, not a crash.
|
||||
@@ -856,6 +856,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD),
|
||||
|
||||
95
tests/draw.c
95
tests/draw.c
@@ -38,13 +38,13 @@ static akerr_ErrorContext *clear_target(void)
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(
|
||||
errctx,
|
||||
SDL_SetRenderDrawColor(renderer->sdl_renderer, 0x00, 0x00, 0x00, 0xff),
|
||||
SDL_SetRenderDrawColor(akgl_renderer->sdl_renderer, 0x00, 0x00, 0x00, 0xff),
|
||||
AKGL_ERR_SDL,
|
||||
"%s",
|
||||
SDL_GetError());
|
||||
FAIL_ZERO_RETURN(
|
||||
errctx,
|
||||
SDL_RenderClear(renderer->sdl_renderer),
|
||||
SDL_RenderClear(akgl_renderer->sdl_renderer),
|
||||
AKGL_ERR_SDL,
|
||||
"%s",
|
||||
SDL_GetError());
|
||||
@@ -80,10 +80,10 @@ akerr_ErrorContext *test_draw_point(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, clear_target());
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_point(renderer, 10.0f, 20.0f, testred),
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_point(akgl_renderer, 10.0f, 20.0f, testred),
|
||||
"plotting one pixel");
|
||||
|
||||
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL);
|
||||
shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
|
||||
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
|
||||
TEST_ASSERT(errctx, pixel_is(shot, 10, 20, testred),
|
||||
"the plotted pixel at 10,20 is not the color it was drawn with");
|
||||
@@ -115,10 +115,10 @@ akerr_ErrorContext *test_draw_line(void)
|
||||
CATCH(errctx, clear_target());
|
||||
// A vertical line, so every pixel of it is known without reasoning
|
||||
// about how SDL rasterises a diagonal.
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_line(renderer, 5.0f, 4.0f, 5.0f, 12.0f, testred),
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_line(akgl_renderer, 5.0f, 4.0f, 5.0f, 12.0f, testred),
|
||||
"drawing a vertical line");
|
||||
|
||||
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL);
|
||||
shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
|
||||
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
|
||||
for ( i = 4; i <= 12; i++ ) {
|
||||
TEST_ASSERT_FLAG(onthe_line, pixel_is(shot, 5, i, testred));
|
||||
@@ -158,8 +158,8 @@ akerr_ErrorContext *test_draw_rects(void)
|
||||
|
||||
// The outline touches the border and leaves the middle alone.
|
||||
CATCH(errctx, clear_target());
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_rect(renderer, &box, testred), "outlining a rectangle");
|
||||
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL);
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_rect(akgl_renderer, &box, testred), "outlining a rectangle");
|
||||
shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
|
||||
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
|
||||
TEST_ASSERT(errctx, pixel_is(shot, 8, 8, testred), "the outline is missing its top left corner");
|
||||
TEST_ASSERT(errctx, pixel_is(shot, 23, 23, testred),
|
||||
@@ -171,8 +171,8 @@ akerr_ErrorContext *test_draw_rects(void)
|
||||
|
||||
// The filled form covers the interior as well.
|
||||
CATCH(errctx, clear_target());
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_filled_rect(renderer, &box, testred), "filling a rectangle");
|
||||
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL);
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_filled_rect(akgl_renderer, &box, testred), "filling a rectangle");
|
||||
shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
|
||||
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
|
||||
TEST_ASSERT(errctx, pixel_is(shot, 16, 16, testred), "the fill left its interior empty");
|
||||
TEST_ASSERT(errctx, pixel_is(shot, 8, 8, testred), "the fill missed its top left corner");
|
||||
@@ -181,11 +181,11 @@ akerr_ErrorContext *test_draw_rects(void)
|
||||
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_draw_rect(NULL, &box, testred),
|
||||
"outlining through a NULL backend");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_draw_rect(renderer, NULL, testred),
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_draw_rect(akgl_renderer, NULL, testred),
|
||||
"outlining a NULL rectangle");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_draw_filled_rect(NULL, &box, testred),
|
||||
"filling through a NULL backend");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_draw_filled_rect(renderer, NULL, testred),
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_draw_filled_rect(akgl_renderer, NULL, testred),
|
||||
"filling a NULL rectangle");
|
||||
} CLEANUP {
|
||||
if ( shot != NULL ) {
|
||||
@@ -206,10 +206,10 @@ akerr_ErrorContext *test_draw_circle(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, clear_target());
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_circle(renderer, 32.0f, 32.0f, 10.0f, testred),
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_circle(akgl_renderer, 32.0f, 32.0f, 10.0f, testred),
|
||||
"drawing a circle");
|
||||
|
||||
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL);
|
||||
shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
|
||||
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
|
||||
// The four axis points are exact for any correct midpoint circle.
|
||||
TEST_ASSERT(errctx, pixel_is(shot, 42, 32, testred), "the circle is missing its rightmost pixel");
|
||||
@@ -243,15 +243,15 @@ akerr_ErrorContext *test_draw_circle(void)
|
||||
|
||||
// A zero radius is the degenerate case, not an error: one pixel.
|
||||
CATCH(errctx, clear_target());
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_circle(renderer, 5.0f, 5.0f, 0.0f, testred),
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_circle(akgl_renderer, 5.0f, 5.0f, 0.0f, testred),
|
||||
"drawing a circle of radius zero");
|
||||
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL);
|
||||
shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
|
||||
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
|
||||
TEST_ASSERT(errctx, pixel_is(shot, 5, 5, testred),
|
||||
"a circle of radius zero did not plot its center");
|
||||
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
|
||||
akgl_draw_circle(renderer, 5.0f, 5.0f, -1.0f, testred),
|
||||
akgl_draw_circle(akgl_renderer, 5.0f, 5.0f, -1.0f, testred),
|
||||
"drawing a circle of negative radius");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
|
||||
akgl_draw_circle(NULL, 5.0f, 5.0f, 4.0f, testred),
|
||||
@@ -279,12 +279,12 @@ akerr_ErrorContext *test_draw_flood_fill(void)
|
||||
box.w = 20.0f;
|
||||
box.h = 20.0f;
|
||||
CATCH(errctx, clear_target());
|
||||
CATCH(errctx, akgl_draw_rect(renderer, &box, testred));
|
||||
CATCH(errctx, akgl_draw_rect(akgl_renderer, &box, testred));
|
||||
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_flood_fill(renderer, 20, 20, testgreen),
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_flood_fill(akgl_renderer, 20, 20, testgreen),
|
||||
"flooding the inside of a box");
|
||||
|
||||
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL);
|
||||
shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
|
||||
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
|
||||
TEST_ASSERT(errctx, pixel_is(shot, 20, 20, testgreen), "the seed pixel was not filled");
|
||||
TEST_ASSERT(errctx, pixel_is(shot, 11, 11, testgreen),
|
||||
@@ -301,9 +301,9 @@ akerr_ErrorContext *test_draw_flood_fill(void)
|
||||
|
||||
// Filling a region that is already the requested color changes nothing
|
||||
// and is not an error.
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_flood_fill(renderer, 20, 20, testgreen),
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_flood_fill(akgl_renderer, 20, 20, testgreen),
|
||||
"flooding a region that is already that color");
|
||||
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL);
|
||||
shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
|
||||
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
|
||||
TEST_ASSERT(errctx, pixel_is(shot, 20, 20, testgreen),
|
||||
"refilling a region disturbed it");
|
||||
@@ -313,9 +313,9 @@ akerr_ErrorContext *test_draw_flood_fill(void)
|
||||
shot = NULL;
|
||||
|
||||
// Flooding the outside reaches every pixel that is not the box.
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_flood_fill(renderer, 0, 0, testgreen),
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_flood_fill(akgl_renderer, 0, 0, testgreen),
|
||||
"flooding the area around a box");
|
||||
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL);
|
||||
shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
|
||||
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
|
||||
TEST_ASSERT(errctx, pixel_is(shot, 0, 0, testgreen), "the seed pixel was not filled");
|
||||
TEST_ASSERT(errctx, pixel_is(shot, TEST_TARGET_SIZE - 1, TEST_TARGET_SIZE - 1, testgreen),
|
||||
@@ -324,10 +324,10 @@ akerr_ErrorContext *test_draw_flood_fill(void)
|
||||
"the fill from outside overwrote the boundary");
|
||||
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
|
||||
akgl_draw_flood_fill(renderer, -1, 0, testred),
|
||||
akgl_draw_flood_fill(akgl_renderer, -1, 0, testred),
|
||||
"flooding from a seed left of the target");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
|
||||
akgl_draw_flood_fill(renderer, 0, TEST_TARGET_SIZE, testred),
|
||||
akgl_draw_flood_fill(akgl_renderer, 0, TEST_TARGET_SIZE, testred),
|
||||
"flooding from a seed below the target");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
|
||||
akgl_draw_flood_fill(NULL, 0, 0, testred),
|
||||
@@ -357,13 +357,13 @@ akerr_ErrorContext *test_draw_copy_and_paste_region(void)
|
||||
box.w = 8.0f;
|
||||
box.h = 8.0f;
|
||||
CATCH(errctx, clear_target());
|
||||
CATCH(errctx, akgl_draw_filled_rect(renderer, &box, testred));
|
||||
CATCH(errctx, akgl_draw_filled_rect(akgl_renderer, &box, testred));
|
||||
|
||||
region.x = 0;
|
||||
region.y = 0;
|
||||
region.w = 8;
|
||||
region.h = 8;
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_copy_region(renderer, ®ion, &saved),
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_copy_region(akgl_renderer, ®ion, &saved),
|
||||
"saving a region of the target");
|
||||
TEST_ASSERT(errctx, saved != NULL, "akgl_draw_copy_region did not allocate a surface");
|
||||
TEST_ASSERT(errctx, saved->w == 8 && saved->h == 8,
|
||||
@@ -371,9 +371,9 @@ akerr_ErrorContext *test_draw_copy_and_paste_region(void)
|
||||
|
||||
// Wipe the screen and put it back somewhere else.
|
||||
CATCH(errctx, clear_target());
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_paste_region(renderer, saved, 32.0f, 32.0f),
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_paste_region(akgl_renderer, saved, 32.0f, 32.0f),
|
||||
"pasting a saved region");
|
||||
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL);
|
||||
shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
|
||||
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
|
||||
TEST_ASSERT(errctx, pixel_is(shot, 32, 32, testred),
|
||||
"the pasted region did not land at its destination");
|
||||
@@ -392,7 +392,7 @@ akerr_ErrorContext *test_draw_copy_and_paste_region(void)
|
||||
FAIL_ZERO_BREAK(errctx, reused, AKGL_ERR_SDL, "%s", SDL_GetError());
|
||||
region.x = 32;
|
||||
region.y = 32;
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_copy_region(renderer, ®ion, &reused),
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_copy_region(akgl_renderer, ®ion, &reused),
|
||||
"saving into a caller-owned surface");
|
||||
TEST_ASSERT(errctx, pixel_is(reused, 0, 0, testred),
|
||||
"the caller-owned surface did not receive the region");
|
||||
@@ -401,35 +401,35 @@ akerr_ErrorContext *test_draw_copy_and_paste_region(void)
|
||||
// refused rather than silently clipped.
|
||||
region.w = 4;
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
|
||||
akgl_draw_copy_region(renderer, ®ion, &reused),
|
||||
akgl_draw_copy_region(akgl_renderer, ®ion, &reused),
|
||||
"saving into a destination of the wrong size");
|
||||
region.x = TEST_TARGET_SIZE - 4;
|
||||
region.y = 0;
|
||||
region.w = 8;
|
||||
region.h = 8;
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
|
||||
akgl_draw_copy_region(renderer, ®ion, &reused),
|
||||
akgl_draw_copy_region(akgl_renderer, ®ion, &reused),
|
||||
"saving a region that runs off the right edge");
|
||||
region.x = 0;
|
||||
region.w = 0;
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
|
||||
akgl_draw_copy_region(renderer, ®ion, &reused),
|
||||
akgl_draw_copy_region(akgl_renderer, ®ion, &reused),
|
||||
"saving a region with no area");
|
||||
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
|
||||
akgl_draw_copy_region(NULL, ®ion, &reused),
|
||||
"saving through a NULL backend");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
|
||||
akgl_draw_copy_region(renderer, NULL, &reused),
|
||||
akgl_draw_copy_region(akgl_renderer, NULL, &reused),
|
||||
"saving a NULL region");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
|
||||
akgl_draw_copy_region(renderer, ®ion, NULL),
|
||||
akgl_draw_copy_region(akgl_renderer, ®ion, NULL),
|
||||
"saving into a NULL destination");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
|
||||
akgl_draw_paste_region(NULL, saved, 0.0f, 0.0f),
|
||||
"pasting through a NULL backend");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
|
||||
akgl_draw_paste_region(renderer, NULL, 0.0f, 0.0f),
|
||||
akgl_draw_paste_region(akgl_renderer, NULL, 0.0f, 0.0f),
|
||||
"pasting a NULL surface");
|
||||
} CLEANUP {
|
||||
if ( shot != NULL ) {
|
||||
@@ -465,19 +465,19 @@ akerr_ErrorContext *test_draw_preserves_render_draw_color(void)
|
||||
// with, or the host's next SDL_RenderClear() paints the wrong color.
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
SDL_SetRenderDrawColor(renderer->sdl_renderer, 0x11, 0x22, 0x33, 0x44),
|
||||
SDL_SetRenderDrawColor(akgl_renderer->sdl_renderer, 0x11, 0x22, 0x33, 0x44),
|
||||
AKGL_ERR_SDL,
|
||||
"%s",
|
||||
SDL_GetError());
|
||||
CATCH(errctx, akgl_draw_point(renderer, 1.0f, 1.0f, testred));
|
||||
CATCH(errctx, akgl_draw_line(renderer, 0.0f, 0.0f, 3.0f, 3.0f, testred));
|
||||
CATCH(errctx, akgl_draw_rect(renderer, &box, testred));
|
||||
CATCH(errctx, akgl_draw_filled_rect(renderer, &box, testred));
|
||||
CATCH(errctx, akgl_draw_circle(renderer, 20.0f, 20.0f, 4.0f, testred));
|
||||
CATCH(errctx, akgl_draw_point(akgl_renderer, 1.0f, 1.0f, testred));
|
||||
CATCH(errctx, akgl_draw_line(akgl_renderer, 0.0f, 0.0f, 3.0f, 3.0f, testred));
|
||||
CATCH(errctx, akgl_draw_rect(akgl_renderer, &box, testred));
|
||||
CATCH(errctx, akgl_draw_filled_rect(akgl_renderer, &box, testred));
|
||||
CATCH(errctx, akgl_draw_circle(akgl_renderer, 20.0f, 20.0f, 4.0f, testred));
|
||||
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
SDL_GetRenderDrawColor(renderer->sdl_renderer, &r, &g, &b, &a),
|
||||
SDL_GetRenderDrawColor(akgl_renderer->sdl_renderer, &r, &g, &b, &a),
|
||||
AKGL_ERR_SDL,
|
||||
"%s",
|
||||
SDL_GetError());
|
||||
@@ -564,7 +564,8 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
renderer = &_akgl_renderer;
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
@@ -579,8 +580,8 @@ int main(void)
|
||||
TEST_TARGET_SIZE,
|
||||
TEST_TARGET_SIZE,
|
||||
0,
|
||||
&window,
|
||||
&renderer->sdl_renderer),
|
||||
&akgl_window,
|
||||
&akgl_renderer->sdl_renderer),
|
||||
AKGL_ERR_SDL,
|
||||
"Couldn't create window/renderer: %s",
|
||||
SDL_GetError());
|
||||
|
||||
@@ -94,7 +94,13 @@ int main(void)
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
ATTEMPT {
|
||||
// Unlike every other suite, this one has no akgl_error_init() in
|
||||
// main() -- the first test is what brings the subsystem up. The trap
|
||||
// therefore goes after it: libakerror installs its own default handler
|
||||
// from the lazy akerr_init() inside that first call, and would
|
||||
// overwrite anything set beforehand.
|
||||
CATCH(errctx, test_error_init_owns_the_status_band());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, test_error_init_is_idempotent());
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
|
||||
178
tests/game.c
178
tests/game.c
@@ -40,11 +40,11 @@ static char truncatedpath[] = "akgl_test_truncated.bin";
|
||||
/** @brief Populate the process-wide game record with a valid identity. */
|
||||
static void set_game_identity(void)
|
||||
{
|
||||
memset(&game, 0x00, sizeof(akgl_Game));
|
||||
strncpy((char *)&game.libversion, AKGL_VERSION, 31);
|
||||
strncpy((char *)&game.version, "1.2.3", 31);
|
||||
strncpy((char *)&game.name, "libakgl test game", 255);
|
||||
strncpy((char *)&game.uri, "https://example.invalid/akgl-test", 255);
|
||||
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);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *test_game_load_versioncmp_matching(void)
|
||||
@@ -134,23 +134,23 @@ akerr_ErrorContext *test_game_save_roundtrip(void)
|
||||
CATCH(e, akgl_registry_init());
|
||||
CATCH(e, akgl_heap_init());
|
||||
set_game_identity();
|
||||
game.fps = 60;
|
||||
game.framesSinceUpdate = 7;
|
||||
memcpy(&expected, &game, sizeof(akgl_Game));
|
||||
akgl_game.fps = 60;
|
||||
akgl_game.framesSinceUpdate = 7;
|
||||
memcpy(&expected, &akgl_game, sizeof(akgl_Game));
|
||||
|
||||
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.
|
||||
game.fps = 0;
|
||||
game.framesSinceUpdate = 0;
|
||||
akgl_game.fps = 0;
|
||||
akgl_game.framesSinceUpdate = 0;
|
||||
|
||||
TEST_EXPECT_OK(e, akgl_game_load((char *)&savepath), "loading the game back");
|
||||
TEST_ASSERT(e, game.fps == 60, "fps restored as %d, expected 60", game.fps);
|
||||
TEST_ASSERT(e, game.framesSinceUpdate == 7,
|
||||
"framesSinceUpdate restored as %d, expected 7", game.framesSinceUpdate);
|
||||
TEST_ASSERT(e, strncmp((char *)&game.name, (char *)&expected.name, 256) == 0,
|
||||
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,
|
||||
"the game name was not preserved across a save and load");
|
||||
TEST_ASSERT(e, strncmp((char *)&game.version, (char *)&expected.version, 32) == 0,
|
||||
TEST_ASSERT(e, strncmp((char *)&akgl_game.version, (char *)&expected.version, 32) == 0,
|
||||
"the game version was not preserved across a save and load");
|
||||
} CLEANUP {
|
||||
unlink((char *)&savepath);
|
||||
@@ -170,7 +170,7 @@ akerr_ErrorContext *test_game_load_rejects_foreign_saves(void)
|
||||
// A save written by a different game must not load into this one.
|
||||
set_game_identity();
|
||||
CATCH(e, akgl_game_save((char *)&savepath));
|
||||
strncpy((char *)&game.name, "a completely different game", 255);
|
||||
strncpy((char *)&akgl_game.name, "a completely different game", 255);
|
||||
TEST_EXPECT_STATUS(e, AKERR_API, akgl_game_load((char *)&savepath),
|
||||
"a savegame with a foreign game name must be refused");
|
||||
unlink((char *)&savepath);
|
||||
@@ -178,14 +178,14 @@ akerr_ErrorContext *test_game_load_rejects_foreign_saves(void)
|
||||
// Same for a differing URI.
|
||||
set_game_identity();
|
||||
CATCH(e, akgl_game_save((char *)&savepath));
|
||||
strncpy((char *)&game.uri, "https://example.invalid/other", 255);
|
||||
strncpy((char *)&akgl_game.uri, "https://example.invalid/other", 255);
|
||||
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();
|
||||
strncpy((char *)&game.libversion, "99.98.97", 31);
|
||||
strncpy((char *)&akgl_game.libversion, "99.98.97", 31);
|
||||
CATCH(e, akgl_game_save((char *)&savepath));
|
||||
set_game_identity();
|
||||
TEST_EXPECT_STATUS(e, AKERR_API, akgl_game_load((char *)&savepath),
|
||||
@@ -194,7 +194,7 @@ akerr_ErrorContext *test_game_load_rejects_foreign_saves(void)
|
||||
|
||||
// And one written against a different game version.
|
||||
set_game_identity();
|
||||
strncpy((char *)&game.version, "4.5.6", 31);
|
||||
strncpy((char *)&akgl_game.version, "4.5.6", 31);
|
||||
CATCH(e, akgl_game_save((char *)&savepath));
|
||||
set_game_identity();
|
||||
TEST_EXPECT_STATUS(e, AKERR_API, akgl_game_load((char *)&savepath),
|
||||
@@ -246,7 +246,7 @@ akerr_ErrorContext *test_game_load_truncated_table(void)
|
||||
memset(&partial, 0x00, sizeof(partial));
|
||||
fp = fopen((char *)&truncatedpath, "wb");
|
||||
FAIL_ZERO_BREAK(e, fp, AKERR_IO, "unable to create the truncated savegame fixture");
|
||||
FAIL_ZERO_BREAK(e, fwrite(&game, 1, sizeof(akgl_Game), fp), AKERR_IO,
|
||||
FAIL_ZERO_BREAK(e, fwrite(&akgl_game, 1, sizeof(akgl_Game), fp), AKERR_IO,
|
||||
"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");
|
||||
@@ -296,7 +296,7 @@ akerr_ErrorContext *test_game_save_writes_name_tables(void)
|
||||
+ (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 *))
|
||||
+ (long)(AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH + sizeof(akgl_Character *));
|
||||
+ (long)(AKGL_CHARACTER_MAX_NAME_LENGTH + sizeof(akgl_Character *));
|
||||
|
||||
TEST_ASSERT(e, filesize >= minimum,
|
||||
"the savegame is %ld bytes, expected at least %ld for the header and four name tables",
|
||||
@@ -317,8 +317,8 @@ akerr_ErrorContext *test_game_state_lock(void)
|
||||
|
||||
ATTEMPT {
|
||||
set_game_identity();
|
||||
game.statelock = SDL_CreateMutex();
|
||||
FAIL_ZERO_BREAK(e, game.statelock, AKGL_ERR_SDL, "unable to create the state mutex");
|
||||
akgl_game.statelock = SDL_CreateMutex();
|
||||
FAIL_ZERO_BREAK(e, akgl_game.statelock, AKGL_ERR_SDL, "unable to create the state mutex");
|
||||
|
||||
TEST_EXPECT_OK(e, akgl_game_state_lock(), "taking the state lock");
|
||||
TEST_EXPECT_OK(e, akgl_game_state_unlock(), "releasing the state lock");
|
||||
@@ -327,9 +327,93 @@ akerr_ErrorContext *test_game_state_lock(void)
|
||||
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 {
|
||||
if ( game.statelock != NULL ) {
|
||||
SDL_DestroyMutex(game.statelock);
|
||||
game.statelock = NULL;
|
||||
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;
|
||||
}
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
@@ -352,41 +436,41 @@ akerr_ErrorContext *test_game_updateFPS(void)
|
||||
|
||||
ATTEMPT {
|
||||
set_game_identity();
|
||||
game.lowfpsfunc = &stub_lowfps;
|
||||
akgl_game.lowfpsfunc = &stub_lowfps;
|
||||
|
||||
// Below the 30 FPS floor, every update notifies the callback.
|
||||
game.fps = 10;
|
||||
game.lastFPSTime = SDL_GetTicksNS();
|
||||
akgl_game.fps = 10;
|
||||
akgl_game.lastFPSTime = SDL_GetTicksNS();
|
||||
lowfps_calls = 0;
|
||||
framesbefore = game.framesSinceUpdate;
|
||||
akgl_game_updateFPS();
|
||||
framesbefore = akgl_game.framesSinceUpdate;
|
||||
akgl_game_update_fps();
|
||||
TEST_ASSERT(e, lowfps_calls == 1,
|
||||
"a sub-30 FPS update fired the low-FPS callback %d times, expected 1", lowfps_calls);
|
||||
TEST_ASSERT(e, game.framesSinceUpdate == (framesbefore + 1),
|
||||
TEST_ASSERT(e, akgl_game.framesSinceUpdate == (framesbefore + 1),
|
||||
"updateFPS did not count the frame (%d, expected %d)",
|
||||
game.framesSinceUpdate, framesbefore + 1);
|
||||
TEST_ASSERT(e, game.lastIterTime != 0, "updateFPS did not stamp lastIterTime");
|
||||
akgl_game.framesSinceUpdate, framesbefore + 1);
|
||||
TEST_ASSERT(e, akgl_game.lastIterTime != 0, "updateFPS did not stamp lastIterTime");
|
||||
|
||||
// At or above the floor, the callback stays quiet.
|
||||
game.fps = 60;
|
||||
game.lastFPSTime = SDL_GetTicksNS();
|
||||
akgl_game.fps = 60;
|
||||
akgl_game.lastFPSTime = SDL_GetTicksNS();
|
||||
lowfps_calls = 0;
|
||||
akgl_game_updateFPS();
|
||||
akgl_game_update_fps();
|
||||
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.
|
||||
game.fps = 60;
|
||||
game.framesSinceUpdate = 45;
|
||||
game.lastFPSTime = SDL_GetTicksNS() - (2 * (SDL_Time)AKGL_TIME_ONESEC_NS);
|
||||
akgl_game_updateFPS();
|
||||
TEST_ASSERT(e, game.fps == 45,
|
||||
"after a second elapsed, fps rolled over as %d, expected 45", game.fps);
|
||||
TEST_ASSERT(e, game.framesSinceUpdate == 1,
|
||||
"the frame counter restarted at %d, expected 1", game.framesSinceUpdate);
|
||||
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);
|
||||
|
||||
// The shipped default callback only logs, so it just has to not crash.
|
||||
game.fps = 1;
|
||||
akgl_game.fps = 1;
|
||||
akgl_game_lowfps();
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
@@ -403,6 +487,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
CATCH(errctx, akgl_registry_init());
|
||||
|
||||
@@ -415,6 +500,7 @@ int main(void)
|
||||
CATCH(errctx, test_game_load_truncated_table());
|
||||
CATCH(errctx, test_game_save_writes_name_tables());
|
||||
CATCH(errctx, test_game_state_lock());
|
||||
CATCH(errctx, test_game_state_lock_budget());
|
||||
CATCH(errctx, test_game_updateFPS());
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
|
||||
@@ -10,7 +10,13 @@
|
||||
*
|
||||
* One header per file, deliberately. A second `#include` after the first proves
|
||||
* nothing about the second: by then the first has already dragged its
|
||||
* dependencies in. Covering another header means another file like this one.
|
||||
* dependencies in. So every other public header gets its own generated
|
||||
* translation unit -- `CMakeLists.txt` writes one per entry in
|
||||
* `AKGL_PUBLIC_HEADERS` and links them all into this program. That list also
|
||||
* drives `install()`, so a header that ships is a header that is checked.
|
||||
*
|
||||
* This file stays hand-written because it asserts something the generated ones
|
||||
* cannot: that `akgl_Actor` is *complete* here rather than merely declared.
|
||||
*/
|
||||
|
||||
#include <akgl/controller.h>
|
||||
@@ -26,11 +32,11 @@ int main(void)
|
||||
// one by value.
|
||||
memset(&control, 0x00, sizeof(akgl_Control));
|
||||
control.key = SDLK_LEFT;
|
||||
control.handler_on = &akgl_Actor_cmhf_left_on;
|
||||
control.handler_on = &akgl_actor_cmhf_left_on;
|
||||
if ( control.handler_on == NULL ) {
|
||||
return 1;
|
||||
}
|
||||
if ( sizeof(GAME_ControlMaps) != (sizeof(akgl_ControlMap) * AKGL_MAX_CONTROL_MAPS) ) {
|
||||
if ( sizeof(akgl_controlmaps) != (sizeof(akgl_ControlMap) * AKGL_MAX_CONTROL_MAPS) ) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
|
||||
45
tests/heap.c
45
tests/heap.c
@@ -50,57 +50,57 @@ akerr_ErrorContext *test_heap_init_clears_every_pool(void)
|
||||
ATTEMPT {
|
||||
// Dirty every pool, then require that init scrubs all of them.
|
||||
for ( i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) {
|
||||
HEAP_ACTOR[i].refcount = 5;
|
||||
akgl_heap_actors[i].refcount = 5;
|
||||
}
|
||||
for ( i = 0; i < AKGL_MAX_HEAP_SPRITE; i++ ) {
|
||||
HEAP_SPRITE[i].refcount = 5;
|
||||
akgl_heap_sprites[i].refcount = 5;
|
||||
}
|
||||
for ( i = 0; i < AKGL_MAX_HEAP_SPRITESHEET; i++ ) {
|
||||
HEAP_SPRITESHEET[i].refcount = 5;
|
||||
akgl_heap_spritesheets[i].refcount = 5;
|
||||
}
|
||||
for ( i = 0; i < AKGL_MAX_HEAP_CHARACTER; i++ ) {
|
||||
HEAP_CHARACTER[i].refcount = 5;
|
||||
akgl_heap_characters[i].refcount = 5;
|
||||
}
|
||||
for ( i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) {
|
||||
HEAP_STRING[i].refcount = 5;
|
||||
akgl_heap_strings[i].refcount = 5;
|
||||
}
|
||||
|
||||
CATCH(e, akgl_heap_init());
|
||||
|
||||
for ( i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) {
|
||||
TEST_ASSERT_FLAG(clean, HEAP_ACTOR[i].refcount == 0);
|
||||
TEST_ASSERT_FLAG(clean, akgl_heap_actors[i].refcount == 0);
|
||||
}
|
||||
TEST_ASSERT(e, clean, "akgl_heap_init left a nonzero refcount in the actor pool");
|
||||
|
||||
for ( i = 0; i < AKGL_MAX_HEAP_SPRITE; i++ ) {
|
||||
TEST_ASSERT_FLAG(clean, HEAP_SPRITE[i].refcount == 0);
|
||||
TEST_ASSERT_FLAG(clean, akgl_heap_sprites[i].refcount == 0);
|
||||
}
|
||||
TEST_ASSERT(e, clean, "akgl_heap_init left a nonzero refcount in the sprite pool");
|
||||
|
||||
for ( i = 0; i < AKGL_MAX_HEAP_SPRITESHEET; i++ ) {
|
||||
TEST_ASSERT_FLAG(clean, HEAP_SPRITESHEET[i].refcount == 0);
|
||||
TEST_ASSERT_FLAG(clean, akgl_heap_spritesheets[i].refcount == 0);
|
||||
}
|
||||
TEST_ASSERT(e, clean, "akgl_heap_init left a nonzero refcount in the spritesheet pool");
|
||||
|
||||
for ( i = 0; i < AKGL_MAX_HEAP_CHARACTER; i++ ) {
|
||||
TEST_ASSERT_FLAG(clean, HEAP_CHARACTER[i].refcount == 0);
|
||||
TEST_ASSERT_FLAG(clean, akgl_heap_characters[i].refcount == 0);
|
||||
}
|
||||
TEST_ASSERT(e, clean, "akgl_heap_init left a nonzero refcount in the character pool");
|
||||
|
||||
for ( i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) {
|
||||
TEST_ASSERT_FLAG(clean, HEAP_STRING[i].refcount == 0);
|
||||
TEST_ASSERT_FLAG(clean, akgl_heap_strings[i].refcount == 0);
|
||||
}
|
||||
TEST_ASSERT(e, clean, "akgl_heap_init left a nonzero refcount in the string pool");
|
||||
|
||||
// akgl_heap_init_actor clears only the actor pool.
|
||||
HEAP_ACTOR[0].refcount = 9;
|
||||
HEAP_SPRITE[0].refcount = 9;
|
||||
akgl_heap_actors[0].refcount = 9;
|
||||
akgl_heap_sprites[0].refcount = 9;
|
||||
CATCH(e, akgl_heap_init_actor());
|
||||
TEST_ASSERT(e, HEAP_ACTOR[0].refcount == 0,
|
||||
TEST_ASSERT(e, akgl_heap_actors[0].refcount == 0,
|
||||
"akgl_heap_init_actor did not clear the actor pool");
|
||||
TEST_ASSERT(e, HEAP_SPRITE[0].refcount == 9,
|
||||
TEST_ASSERT(e, akgl_heap_sprites[0].refcount == 9,
|
||||
"akgl_heap_init_actor cleared the sprite pool as well");
|
||||
HEAP_SPRITE[0].refcount = 0;
|
||||
akgl_heap_sprites[0].refcount = 0;
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
@@ -151,28 +151,28 @@ akerr_ErrorContext *test_heap_exhaustion(void)
|
||||
// Actors: the acquire function does not claim the slot, so the test does.
|
||||
CATCH(e, reset_all_heaps());
|
||||
for ( i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) {
|
||||
HEAP_ACTOR[i].refcount = 1;
|
||||
akgl_heap_actors[i].refcount = 1;
|
||||
}
|
||||
TEST_EXPECT_STATUS(e, AKGL_ERR_HEAP, akgl_heap_next_actor(&actor),
|
||||
"akgl_heap_next_actor with every slot claimed");
|
||||
|
||||
CATCH(e, reset_all_heaps());
|
||||
for ( i = 0; i < AKGL_MAX_HEAP_SPRITE; i++ ) {
|
||||
HEAP_SPRITE[i].refcount = 1;
|
||||
akgl_heap_sprites[i].refcount = 1;
|
||||
}
|
||||
TEST_EXPECT_STATUS(e, AKGL_ERR_HEAP, akgl_heap_next_sprite(&sprite),
|
||||
"akgl_heap_next_sprite with every slot claimed");
|
||||
|
||||
CATCH(e, reset_all_heaps());
|
||||
for ( i = 0; i < AKGL_MAX_HEAP_SPRITESHEET; i++ ) {
|
||||
HEAP_SPRITESHEET[i].refcount = 1;
|
||||
akgl_heap_spritesheets[i].refcount = 1;
|
||||
}
|
||||
TEST_EXPECT_STATUS(e, AKGL_ERR_HEAP, akgl_heap_next_spritesheet(&sheet),
|
||||
"akgl_heap_next_spritesheet with every slot claimed");
|
||||
|
||||
CATCH(e, reset_all_heaps());
|
||||
for ( i = 0; i < AKGL_MAX_HEAP_CHARACTER; i++ ) {
|
||||
HEAP_CHARACTER[i].refcount = 1;
|
||||
akgl_heap_characters[i].refcount = 1;
|
||||
}
|
||||
TEST_EXPECT_STATUS(e, AKGL_ERR_HEAP, akgl_heap_next_character(&basechar),
|
||||
"akgl_heap_next_character with every slot claimed");
|
||||
@@ -186,10 +186,10 @@ akerr_ErrorContext *test_heap_exhaustion(void)
|
||||
"akgl_heap_next_string with the pool drained");
|
||||
|
||||
// A single release makes exactly one slot available again.
|
||||
CATCH(e, akgl_heap_release_string(&HEAP_STRING[0]));
|
||||
CATCH(e, akgl_heap_release_string(&akgl_heap_strings[0]));
|
||||
TEST_EXPECT_OK(e, akgl_heap_next_string(&str),
|
||||
"akgl_heap_next_string after freeing one slot");
|
||||
TEST_ASSERT(e, str == &HEAP_STRING[0],
|
||||
TEST_ASSERT(e, str == &akgl_heap_strings[0],
|
||||
"the reclaimed string was not the slot that was released");
|
||||
|
||||
CATCH(e, reset_all_heaps());
|
||||
@@ -264,7 +264,7 @@ akerr_ErrorContext *test_heap_release_actor_children(void)
|
||||
// Each child was initialized to 1 and incremented to 2 by add_child, so
|
||||
// the recursive release should have brought every one of them back to 1.
|
||||
for ( i = 1; i <= AKGL_ACTOR_MAX_CHILDREN; i++ ) {
|
||||
TEST_ASSERT_FLAG(released, HEAP_ACTOR[i].refcount == 1);
|
||||
TEST_ASSERT_FLAG(released, akgl_heap_actors[i].refcount == 1);
|
||||
}
|
||||
TEST_ASSERT(e, released,
|
||||
"releasing a parent did not decrement every child exactly once");
|
||||
@@ -362,6 +362,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
CATCH(errctx, akgl_registry_init());
|
||||
|
||||
|
||||
@@ -348,6 +348,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
CATCH(errctx, akgl_registry_init());
|
||||
CATCH(errctx, load_fixture());
|
||||
|
||||
27
tests/perf.c
27
tests/perf.c
@@ -69,21 +69,21 @@ static void bench_report_footprint(void)
|
||||
{
|
||||
size_t total = 0;
|
||||
|
||||
total = sizeof(HEAP_ACTOR) + sizeof(HEAP_SPRITE) + sizeof(HEAP_SPRITESHEET) +
|
||||
sizeof(HEAP_CHARACTER) + sizeof(HEAP_STRING);
|
||||
total = sizeof(akgl_heap_actors) + sizeof(akgl_heap_sprites) + sizeof(akgl_heap_spritesheets) +
|
||||
sizeof(akgl_heap_characters) + sizeof(akgl_heap_strings);
|
||||
|
||||
printf("\n");
|
||||
printf("static footprint, fixed at compile time:\n");
|
||||
printf(" %-24s %5d x %7zu = %10zu bytes\n",
|
||||
"HEAP_ACTOR", AKGL_MAX_HEAP_ACTOR, sizeof(akgl_Actor), sizeof(HEAP_ACTOR));
|
||||
"akgl_heap_actors", AKGL_MAX_HEAP_ACTOR, sizeof(akgl_Actor), sizeof(akgl_heap_actors));
|
||||
printf(" %-24s %5d x %7zu = %10zu bytes\n",
|
||||
"HEAP_SPRITE", AKGL_MAX_HEAP_SPRITE, sizeof(akgl_Sprite), sizeof(HEAP_SPRITE));
|
||||
"akgl_heap_sprites", AKGL_MAX_HEAP_SPRITE, sizeof(akgl_Sprite), sizeof(akgl_heap_sprites));
|
||||
printf(" %-24s %5d x %7zu = %10zu bytes\n",
|
||||
"HEAP_SPRITESHEET", AKGL_MAX_HEAP_SPRITESHEET, sizeof(akgl_SpriteSheet), sizeof(HEAP_SPRITESHEET));
|
||||
"akgl_heap_spritesheets", AKGL_MAX_HEAP_SPRITESHEET, sizeof(akgl_SpriteSheet), sizeof(akgl_heap_spritesheets));
|
||||
printf(" %-24s %5d x %7zu = %10zu bytes\n",
|
||||
"HEAP_CHARACTER", AKGL_MAX_HEAP_CHARACTER, sizeof(akgl_Character), sizeof(HEAP_CHARACTER));
|
||||
"akgl_heap_characters", AKGL_MAX_HEAP_CHARACTER, sizeof(akgl_Character), sizeof(akgl_heap_characters));
|
||||
printf(" %-24s %5d x %7zu = %10zu bytes\n",
|
||||
"HEAP_STRING", AKGL_MAX_HEAP_STRING, sizeof(akgl_String), sizeof(HEAP_STRING));
|
||||
"akgl_heap_strings", AKGL_MAX_HEAP_STRING, sizeof(akgl_String), sizeof(akgl_heap_strings));
|
||||
printf(" %-24s %5s %7s %10zu bytes\n", "pools, total", "", "", total);
|
||||
printf(" %-24s %5d x %7s = %10zu bytes\n",
|
||||
"akgl_Tilemap", 1, "", sizeof(akgl_Tilemap));
|
||||
@@ -263,7 +263,7 @@ static akerr_ErrorContext *bench_heap_actor_claim(void)
|
||||
PASS(errctx, inner);
|
||||
|
||||
for ( i = 0; i < (AKGL_MAX_HEAP_ACTOR - 1); i++ ) {
|
||||
HEAP_ACTOR[i].refcount = 1;
|
||||
akgl_heap_actors[i].refcount = 1;
|
||||
}
|
||||
bench_start("heap_next_actor, one slot left", "call", 400.0);
|
||||
BENCH_LOOP(inner, i, count, akgl_heap_next_actor(&actor));
|
||||
@@ -328,7 +328,7 @@ static akerr_ErrorContext *bench_heap_string(void)
|
||||
// The same claim with 255 of the 256 slots taken: a megabyte walked, one
|
||||
// reference count read per 4 KiB page.
|
||||
for ( i = 0; i < (AKGL_MAX_HEAP_STRING - 1); i++ ) {
|
||||
HEAP_STRING[i].refcount = 1;
|
||||
akgl_heap_strings[i].refcount = 1;
|
||||
}
|
||||
bench_start("heap_next_string, one slot left", "call", 2500.0);
|
||||
for ( i = 0; i < count; i++ ) {
|
||||
@@ -470,7 +470,7 @@ static akerr_ErrorContext *bench_registry_lookup(void)
|
||||
PASS(errctx, akgl_registry_init());
|
||||
PASS(errctx, bench_make_character(&basechar, "benchlookupchar", 0));
|
||||
PASS(errctx, bench_fill_actor_pool(basechar, BENCH_ACTOR_COUNT));
|
||||
actor = &HEAP_ACTOR[0];
|
||||
actor = &akgl_heap_actors[0];
|
||||
|
||||
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
|
||||
bench_start("actor_set_character, registry lookup", "call", 400.0);
|
||||
@@ -569,7 +569,7 @@ static akerr_ErrorContext *bench_actor_update(void)
|
||||
PASS(errctx, akgl_registry_init());
|
||||
PASS(errctx, bench_make_character(&basechar, "benchupdatechar", 0));
|
||||
PASS(errctx, bench_fill_actor_pool(basechar, 1));
|
||||
actor = &HEAP_ACTOR[0];
|
||||
actor = &akgl_heap_actors[0];
|
||||
AKGL_BITMASK_CLEAR(actor->state);
|
||||
|
||||
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
|
||||
@@ -674,7 +674,7 @@ static akerr_ErrorContext *bench_logic_frame(void)
|
||||
bench_start("logic frame, 64 actors updated + simulated", "frame", 60000.0);
|
||||
for ( i = 0; i < count; i++ ) {
|
||||
for ( j = 0; j < AKGL_MAX_HEAP_ACTOR; j++ ) {
|
||||
actor = &HEAP_ACTOR[j];
|
||||
actor = &akgl_heap_actors[j];
|
||||
if ( actor->refcount == 0 ) {
|
||||
continue;
|
||||
}
|
||||
@@ -709,7 +709,7 @@ static akerr_ErrorContext *bench_geometry(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akerr_ErrorContext *inner = NULL;
|
||||
RectanglePoints points;
|
||||
akgl_RectanglePoints points;
|
||||
SDL_FRect rects[BENCH_ACTOR_COUNT];
|
||||
SDL_FRect overlapping = { .x = 8.0, .y = 8.0, .w = 32.0, .h = 32.0 };
|
||||
SDL_FRect disjoint = { .x = 900.0, .y = 900.0, .w = 32.0, .h = 32.0 };
|
||||
@@ -901,6 +901,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
CATCH(errctx, akgl_registry_init());
|
||||
CATCH(errctx, akgl_registry_init_properties());
|
||||
|
||||
@@ -91,7 +91,7 @@ static SDL_Texture *benchtiles = NULL;
|
||||
*/
|
||||
#define BENCH_FLUSH_STOP(e, ops) \
|
||||
{ \
|
||||
bool __bench_flushed = SDL_FlushRenderer(renderer->sdl_renderer); \
|
||||
bool __bench_flushed = SDL_FlushRenderer(akgl_renderer->sdl_renderer); \
|
||||
bench_stop(ops); \
|
||||
FAIL_ZERO_RETURN(e, __bench_flushed, AKGL_ERR_SDL, "SDL_FlushRenderer: %s", SDL_GetError()); \
|
||||
}
|
||||
@@ -233,11 +233,11 @@ static akerr_ErrorContext *bench_frame(void)
|
||||
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
|
||||
bench_start("frame_start + frame_end, 640x480", "frame", 230000.0);
|
||||
for ( i = 0; i < count; i++ ) {
|
||||
inner = renderer->frame_start(renderer);
|
||||
inner = akgl_renderer->frame_start(akgl_renderer);
|
||||
if ( inner != NULL ) {
|
||||
break;
|
||||
}
|
||||
inner = renderer->frame_end(renderer);
|
||||
inner = akgl_renderer->frame_end(akgl_renderer);
|
||||
if ( inner != NULL ) {
|
||||
break;
|
||||
}
|
||||
@@ -268,30 +268,30 @@ static akerr_ErrorContext *bench_primitives(void)
|
||||
int i = 0;
|
||||
int rep = 0;
|
||||
|
||||
PASS(errctx, renderer->frame_start(renderer));
|
||||
PASS(errctx, akgl_renderer->frame_start(akgl_renderer));
|
||||
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
|
||||
bench_start("draw_point", "call", 1400.0);
|
||||
BENCH_LOOP(inner, i, count, akgl_draw_point(renderer, 320.0, 240.0, red));
|
||||
BENCH_LOOP(inner, i, count, akgl_draw_point(akgl_renderer, 320.0, 240.0, red));
|
||||
BENCH_FLUSH_STOP(errctx, count);
|
||||
PASS(errctx, inner);
|
||||
|
||||
bench_start("draw_line, screen diagonal", "call", 6200.0);
|
||||
BENCH_LOOP(inner, i, count, akgl_draw_line(renderer, 0.0, 0.0, 639.0, 479.0, red));
|
||||
BENCH_LOOP(inner, i, count, akgl_draw_line(akgl_renderer, 0.0, 0.0, 639.0, 479.0, red));
|
||||
BENCH_FLUSH_STOP(errctx, count);
|
||||
PASS(errctx, inner);
|
||||
|
||||
bench_start("draw_rect, 200x150 outline", "call", 5000.0);
|
||||
BENCH_LOOP(inner, i, count, akgl_draw_rect(renderer, &rect, red));
|
||||
BENCH_LOOP(inner, i, count, akgl_draw_rect(akgl_renderer, &rect, red));
|
||||
BENCH_FLUSH_STOP(errctx, count);
|
||||
PASS(errctx, inner);
|
||||
|
||||
bench_start("draw_filled_rect, 200x150", "call", 440000.0);
|
||||
BENCH_LOOP(inner, i, filled, akgl_draw_filled_rect(renderer, &rect, red));
|
||||
BENCH_LOOP(inner, i, filled, akgl_draw_filled_rect(akgl_renderer, &rect, red));
|
||||
BENCH_FLUSH_STOP(errctx, filled);
|
||||
PASS(errctx, inner);
|
||||
|
||||
bench_start("draw_circle, radius 64", "call", 25000.0);
|
||||
BENCH_LOOP(inner, i, circles, akgl_draw_circle(renderer, 320.0, 240.0, 64.0, red));
|
||||
BENCH_LOOP(inner, i, circles, akgl_draw_circle(akgl_renderer, 320.0, 240.0, 64.0, red));
|
||||
BENCH_FLUSH_STOP(errctx, circles);
|
||||
PASS(errctx, inner);
|
||||
}
|
||||
@@ -325,14 +325,14 @@ static akerr_ErrorContext *bench_framebuffer(void)
|
||||
int i = 0;
|
||||
int rep = 0;
|
||||
|
||||
PASS(errctx, renderer->frame_start(renderer));
|
||||
PASS(errctx, akgl_draw_copy_region(renderer, ®ion, &saved));
|
||||
PASS(errctx, akgl_renderer->frame_start(akgl_renderer));
|
||||
PASS(errctx, akgl_draw_copy_region(akgl_renderer, ®ion, &saved));
|
||||
|
||||
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
|
||||
bench_start("draw_copy_region, 64x64 readback", "call", 10000.0);
|
||||
for ( i = 0; i < count; i++ ) {
|
||||
SDL_Surface *scratch = NULL;
|
||||
inner = akgl_draw_copy_region(renderer, ®ion, &scratch);
|
||||
inner = akgl_draw_copy_region(akgl_renderer, ®ion, &scratch);
|
||||
if ( inner != NULL ) {
|
||||
break;
|
||||
}
|
||||
@@ -342,20 +342,20 @@ static akerr_ErrorContext *bench_framebuffer(void)
|
||||
PASS(errctx, inner);
|
||||
|
||||
bench_start("draw_paste_region, 64x64 upload", "call", 55000.0);
|
||||
BENCH_LOOP(inner, i, count, akgl_draw_paste_region(renderer, saved, 64.0, 64.0));
|
||||
BENCH_LOOP(inner, i, count, akgl_draw_paste_region(akgl_renderer, saved, 64.0, 64.0));
|
||||
BENCH_FLUSH_STOP(errctx, count);
|
||||
PASS(errctx, inner);
|
||||
|
||||
bench_start("draw_flood_fill, full 640x480 target", "call", 20000000.0);
|
||||
for ( i = 0; i < fills; i++ ) {
|
||||
inner = akgl_draw_flood_fill(renderer, 320, 240, ( (i % 2) == 0 ) ? red : blue);
|
||||
inner = akgl_draw_flood_fill(akgl_renderer, 320, 240, ( (i % 2) == 0 ) ? red : blue);
|
||||
if ( inner != NULL ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
BENCH_FLUSH_STOP(errctx, fills);
|
||||
PASS(errctx, inner);
|
||||
PASS(errctx, renderer->frame_start(renderer));
|
||||
PASS(errctx, akgl_renderer->frame_start(akgl_renderer));
|
||||
}
|
||||
SDL_DestroySurface(saved);
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -381,7 +381,7 @@ static akerr_ErrorContext *bench_text(void)
|
||||
int i = 0;
|
||||
int rep = 0;
|
||||
|
||||
PASS(errctx, renderer->frame_start(renderer));
|
||||
PASS(errctx, akgl_renderer->frame_start(akgl_renderer));
|
||||
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
|
||||
bench_start("text_measure, 15 characters", "call", 400.0);
|
||||
BENCH_LOOP(inner, i, count, akgl_text_measure(benchfont, BENCH_TEXT, &w, &h));
|
||||
@@ -486,13 +486,13 @@ static akerr_ErrorContext *bench_asset_load(void)
|
||||
// pointer it never set. Both defects are filed in TODO.md; this loop
|
||||
// works around the first so it never reaches the second.
|
||||
for ( j = 0; j < AKGL_MAX_HEAP_STRING; j++ ) {
|
||||
HEAP_STRING[j].refcount = 0;
|
||||
akgl_heap_strings[j].refcount = 0;
|
||||
}
|
||||
inner = akgl_tilemap_load("assets/testmap.tmj", gamemap);
|
||||
inner = akgl_tilemap_load("assets/testmap.tmj", akgl_gamemap);
|
||||
if ( inner != NULL ) {
|
||||
break;
|
||||
}
|
||||
inner = akgl_tilemap_release(gamemap);
|
||||
inner = akgl_tilemap_release(akgl_gamemap);
|
||||
if ( inner != NULL ) {
|
||||
break;
|
||||
}
|
||||
@@ -523,7 +523,7 @@ static akerr_ErrorContext *bench_map_zero(void)
|
||||
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
|
||||
bench_start("zeroing one akgl_Tilemap", "call", 16000000.0);
|
||||
for ( i = 0; i < count; i++ ) {
|
||||
memset(gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
memset(akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
}
|
||||
BENCH_FLUSH_STOP(errctx, count);
|
||||
}
|
||||
@@ -546,10 +546,10 @@ static akerr_ErrorContext *bench_tileset_offsets(void)
|
||||
int i = 0;
|
||||
int rep = 0;
|
||||
|
||||
PASS(errctx, bench_build_map(gamemap, 1));
|
||||
PASS(errctx, bench_build_map(akgl_gamemap, 1));
|
||||
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
|
||||
bench_start("tilemap_compute_tileset_offsets, 1728 tiles", "call", 23000.0);
|
||||
BENCH_LOOP(inner, i, count, akgl_tilemap_compute_tileset_offsets(gamemap, 0));
|
||||
BENCH_LOOP(inner, i, count, akgl_tilemap_compute_tileset_offsets(akgl_gamemap, 0));
|
||||
BENCH_FLUSH_STOP(errctx, count);
|
||||
PASS(errctx, inner);
|
||||
}
|
||||
@@ -573,19 +573,19 @@ static akerr_ErrorContext *bench_tilemap_draw(void)
|
||||
int i = 0;
|
||||
int rep = 0;
|
||||
|
||||
PASS(errctx, renderer->frame_start(renderer));
|
||||
PASS(errctx, bench_build_map(gamemap, 1));
|
||||
PASS(errctx, akgl_renderer->frame_start(akgl_renderer));
|
||||
PASS(errctx, bench_build_map(akgl_gamemap, 1));
|
||||
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
|
||||
bench_start("tilemap_draw, 40x30 tiles, 1 tileset", "frame", 170000000.0);
|
||||
BENCH_LOOP(inner, i, count, akgl_tilemap_draw(gamemap, camera, 0));
|
||||
BENCH_LOOP(inner, i, count, akgl_tilemap_draw(akgl_gamemap, akgl_camera, 0));
|
||||
BENCH_FLUSH_STOP(errctx, count);
|
||||
PASS(errctx, inner);
|
||||
}
|
||||
|
||||
PASS(errctx, bench_build_map(gamemap, BENCH_MAP_TILESETS));
|
||||
PASS(errctx, bench_build_map(akgl_gamemap, BENCH_MAP_TILESETS));
|
||||
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
|
||||
bench_start("tilemap_draw, 40x30 tiles, 8 tilesets", "frame", 170000000.0);
|
||||
BENCH_LOOP(inner, i, count, akgl_tilemap_draw(gamemap, camera, 0));
|
||||
BENCH_LOOP(inner, i, count, akgl_tilemap_draw(akgl_gamemap, akgl_camera, 0));
|
||||
BENCH_FLUSH_STOP(errctx, count);
|
||||
PASS(errctx, inner);
|
||||
}
|
||||
@@ -627,7 +627,7 @@ static akerr_ErrorContext *bench_raw_blit_control(void)
|
||||
columns = benchtiles->w / BENCH_TILE_SIZE;
|
||||
tilecount = columns * (benchtiles->h / BENCH_TILE_SIZE);
|
||||
|
||||
PASS(errctx, renderer->frame_start(renderer));
|
||||
PASS(errctx, akgl_renderer->frame_start(akgl_renderer));
|
||||
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
|
||||
// One source tile, blitted 1200 times. The floor: 1200 SDL calls and
|
||||
// 1200 x 256 pixels of copying, with the source rectangle staying in
|
||||
@@ -640,7 +640,7 @@ static akerr_ErrorContext *bench_raw_blit_control(void)
|
||||
for ( x = 0; x < across; x++ ) {
|
||||
dest.x = (float32_t)(x * BENCH_TILE_SIZE);
|
||||
dest.y = (float32_t)(y * BENCH_TILE_SIZE);
|
||||
SDL_RenderTexture(renderer->sdl_renderer, benchtiles, &src, &dest);
|
||||
SDL_RenderTexture(akgl_renderer->sdl_renderer, benchtiles, &src, &dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -662,7 +662,7 @@ static akerr_ErrorContext *bench_raw_blit_control(void)
|
||||
src.y = (float32_t)((tile / columns) * BENCH_TILE_SIZE);
|
||||
dest.x = (float32_t)(x * BENCH_TILE_SIZE);
|
||||
dest.y = (float32_t)(y * BENCH_TILE_SIZE);
|
||||
SDL_RenderTexture(renderer->sdl_renderer, benchtiles, &src, &dest);
|
||||
SDL_RenderTexture(akgl_renderer->sdl_renderer, benchtiles, &src, &dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -690,9 +690,9 @@ static akerr_ErrorContext *bench_scene(void)
|
||||
int rep = 0;
|
||||
|
||||
PASS(errctx, bench_populate_scene());
|
||||
PASS(errctx, bench_build_map(gamemap, 1));
|
||||
PASS(errctx, renderer->frame_start(renderer));
|
||||
actor = &HEAP_ACTOR[0];
|
||||
PASS(errctx, bench_build_map(akgl_gamemap, 1));
|
||||
PASS(errctx, akgl_renderer->frame_start(akgl_renderer));
|
||||
actor = &akgl_heap_actors[0];
|
||||
|
||||
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
|
||||
bench_start("actor_render, on camera", "actor", 31000.0);
|
||||
@@ -701,7 +701,7 @@ static akerr_ErrorContext *bench_scene(void)
|
||||
PASS(errctx, inner);
|
||||
|
||||
bench_start("draw_world, 1200 tiles + 64 actors", "frame", 170000000.0);
|
||||
BENCH_LOOP(inner, i, frames, renderer->draw_world(renderer, NULL));
|
||||
BENCH_LOOP(inner, i, frames, akgl_renderer->draw_world(akgl_renderer, NULL));
|
||||
BENCH_FLUSH_STOP(errctx, frames);
|
||||
PASS(errctx, inner);
|
||||
}
|
||||
@@ -727,8 +727,8 @@ static akerr_ErrorContext *bench_game_update(void)
|
||||
int rep = 0;
|
||||
|
||||
PASS(errctx, bench_populate_scene());
|
||||
PASS(errctx, bench_build_map(gamemap, 1));
|
||||
PASS(errctx, akgl_physics_init_null(physics));
|
||||
PASS(errctx, bench_build_map(akgl_gamemap, 1));
|
||||
PASS(errctx, akgl_physics_init_null(akgl_physics));
|
||||
|
||||
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
|
||||
bench_start("game_update, full frame", "frame", 170000000.0);
|
||||
@@ -750,10 +750,11 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
renderer = &_akgl_renderer;
|
||||
physics = &_akgl_physics;
|
||||
camera = &_akgl_camera;
|
||||
gamemap = &_akgl_gamemap;
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
akgl_physics = &akgl_default_physics;
|
||||
akgl_camera = &akgl_default_camera;
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
@@ -774,36 +775,36 @@ int main(void)
|
||||
BENCH_SCREEN_W,
|
||||
BENCH_SCREEN_H,
|
||||
0,
|
||||
&window,
|
||||
&renderer->sdl_renderer),
|
||||
&akgl_window,
|
||||
&akgl_renderer->sdl_renderer),
|
||||
AKGL_ERR_SDL,
|
||||
"Couldn't create window/renderer: %s",
|
||||
SDL_GetError());
|
||||
CATCH(errctx, akgl_render_bind2d(renderer));
|
||||
CATCH(errctx, akgl_render_2d_bind(akgl_renderer));
|
||||
|
||||
camera->x = 0.0;
|
||||
camera->y = 0.0;
|
||||
camera->w = BENCH_SCREEN_W;
|
||||
camera->h = BENCH_SCREEN_H;
|
||||
akgl_camera->x = 0.0;
|
||||
akgl_camera->y = 0.0;
|
||||
akgl_camera->w = BENCH_SCREEN_W;
|
||||
akgl_camera->h = BENCH_SCREEN_H;
|
||||
|
||||
// akgl_game_update takes this before it does anything else. Normally
|
||||
// akgl_game_init creates it; this suite brings the library up piece by
|
||||
// piece so it can stay headless, so it creates the mutex itself.
|
||||
game.statelock = SDL_CreateMutex();
|
||||
FAIL_ZERO_BREAK(errctx, game.statelock, AKGL_ERR_SDL, "%s", SDL_GetError());
|
||||
akgl_game.statelock = SDL_CreateMutex();
|
||||
FAIL_ZERO_BREAK(errctx, akgl_game.statelock, AKGL_ERR_SDL, "%s", SDL_GetError());
|
||||
|
||||
// And this one is not optional either. akgl_game_updateFPS calls
|
||||
// And this one is not optional either. akgl_game_update_fps calls
|
||||
// game.lowfpsfunc through the pointer, unguarded, on every frame under
|
||||
// 30 fps -- which includes the first frame, before there is a frame rate
|
||||
// to compare. A host that brings the library up the way renderer.h
|
||||
// documents for an embedder, akgl_render_bind2d over its own window,
|
||||
// documents for an embedder, akgl_render_2d_bind over its own window,
|
||||
// segfaults on its first akgl_game_update. Filed in TODO.md; installing
|
||||
// the default by hand is the workaround.
|
||||
game.lowfpsfunc = &akgl_game_lowfps;
|
||||
akgl_game.lowfpsfunc = &akgl_game_lowfps;
|
||||
|
||||
benchfont = TTF_OpenFont(BENCH_FONT_PATH, BENCH_FONT_SIZE);
|
||||
FAIL_ZERO_BREAK(errctx, benchfont, AKGL_ERR_SDL, "Couldn't open %s: %s", BENCH_FONT_PATH, SDL_GetError());
|
||||
benchtiles = IMG_LoadTexture(renderer->sdl_renderer, BENCH_TILESET_IMAGE);
|
||||
benchtiles = IMG_LoadTexture(akgl_renderer->sdl_renderer, BENCH_TILESET_IMAGE);
|
||||
FAIL_ZERO_BREAK(errctx, benchtiles, AKGL_ERR_SDL, "Couldn't load %s: %s", BENCH_TILESET_IMAGE, SDL_GetError());
|
||||
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @brief Unit tests for the physics backends and the simulation loop.
|
||||
*
|
||||
* None of these tests need a renderer or a window. The simulation loop walks
|
||||
* HEAP_ACTOR directly, so each test rebuilds the actor heap rather than relying
|
||||
* akgl_heap_actors directly, so each test rebuilds the actor heap rather than relying
|
||||
* on state left behind by an earlier one.
|
||||
*/
|
||||
|
||||
@@ -723,6 +723,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
CATCH(errctx, akgl_registry_init());
|
||||
CATCH(errctx, akgl_registry_init_properties());
|
||||
|
||||
@@ -163,6 +163,7 @@ int main(void)
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, test_akgl_registry_init_creation_failures());
|
||||
CATCH(errctx, test_akgl_get_property_copies_only_the_value());
|
||||
} CLEANUP {
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
* @file renderer.c
|
||||
* @brief Unit tests for the 2D render backend's vtable and its entry points.
|
||||
*
|
||||
* None of this needs a display. akgl_render_bind2d() installs the backend's
|
||||
* None of this needs a display. akgl_render_2d_bind() installs the backend's
|
||||
* methods and nothing else, so it can be checked against a backend that has no
|
||||
* SDL_Renderer at all; the entry points that do draw are checked against a
|
||||
* software renderer under the dummy video driver, the same way tests/draw.c
|
||||
* does it.
|
||||
*
|
||||
* akgl_render_init2d() is not covered here: it creates a window from the
|
||||
* akgl_render_2d_init() is not covered here: it creates a window from the
|
||||
* property registry and writes the `camera` global, which is what the offscreen
|
||||
* harness described in TODO.md exists to make testable.
|
||||
*/
|
||||
@@ -42,7 +42,7 @@ akerr_ErrorContext *test_render_bind2d(void)
|
||||
memset(&backend, 0x00, sizeof(akgl_RenderBackend));
|
||||
backend.sdl_renderer = (SDL_Renderer *)&bound;
|
||||
|
||||
TEST_EXPECT_OK(errctx, akgl_render_bind2d(&backend), "binding the 2D backend");
|
||||
TEST_EXPECT_OK(errctx, akgl_render_2d_bind(&backend), "binding the 2D backend");
|
||||
|
||||
TEST_ASSERT(errctx, backend.sdl_renderer == (SDL_Renderer *)&bound,
|
||||
"binding replaced the caller's SDL_Renderer");
|
||||
@@ -63,12 +63,12 @@ akerr_ErrorContext *test_render_bind2d(void)
|
||||
// are separable in both directions -- and leaves it NULL rather than
|
||||
// inventing one.
|
||||
memset(&backend, 0x00, sizeof(akgl_RenderBackend));
|
||||
TEST_EXPECT_OK(errctx, akgl_render_bind2d(&backend),
|
||||
TEST_EXPECT_OK(errctx, akgl_render_2d_bind(&backend),
|
||||
"binding a backend with no SDL renderer");
|
||||
TEST_ASSERT(errctx, backend.sdl_renderer == NULL,
|
||||
"binding invented an SDL_Renderer");
|
||||
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_render_bind2d(NULL),
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_render_2d_bind(NULL),
|
||||
"binding a NULL backend");
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
@@ -85,7 +85,7 @@ akerr_ErrorContext *test_render_backend_without_a_renderer(void)
|
||||
// Bound but never given an SDL_Renderer. Every entry point that draws
|
||||
// has to report that rather than dereference it.
|
||||
memset(&empty, 0x00, sizeof(akgl_RenderBackend));
|
||||
CATCH(errctx, akgl_render_bind2d(&empty));
|
||||
CATCH(errctx, akgl_render_2d_bind(&empty));
|
||||
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, empty.frame_start(&empty),
|
||||
"starting a frame on a backend with no renderer");
|
||||
@@ -187,6 +187,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
memset(&bound, 0x00, sizeof(akgl_RenderBackend));
|
||||
|
||||
FAIL_ZERO_BREAK(
|
||||
@@ -202,14 +203,14 @@ int main(void)
|
||||
TEST_TARGET_SIZE,
|
||||
TEST_TARGET_SIZE,
|
||||
0,
|
||||
&window,
|
||||
&akgl_window,
|
||||
&bound.sdl_renderer),
|
||||
AKGL_ERR_SDL,
|
||||
"Couldn't create window/renderer: %s",
|
||||
SDL_GetError());
|
||||
// The host owns the window; libakgl only ever binds to it. This is the
|
||||
// arrangement akgl_render_bind2d exists for.
|
||||
CATCH(errctx, akgl_render_bind2d(&bound));
|
||||
// arrangement akgl_render_2d_bind exists for.
|
||||
CATCH(errctx, akgl_render_2d_bind(&bound));
|
||||
|
||||
CATCH(errctx, test_render_bind2d());
|
||||
CATCH(errctx, test_render_backend_without_a_renderer());
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include <akgl/game.h>
|
||||
#include <akgl/renderer.h>
|
||||
|
||||
#include "testutil.h"
|
||||
|
||||
|
||||
akerr_ErrorContext *test_akgl_spritesheet_initialize(void)
|
||||
{
|
||||
@@ -37,7 +39,7 @@ akerr_ErrorContext *test_akgl_spritesheet_initialize(void)
|
||||
"Loaded texture was not the correct size");
|
||||
|
||||
snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/spritesheet.png");
|
||||
image = IMG_LoadTexture(renderer->sdl_renderer, (char *)&tmpstr->data);
|
||||
image = IMG_LoadTexture(akgl_renderer->sdl_renderer, (char *)&tmpstr->data);
|
||||
FAIL_ZERO_BREAK(errctx, image, AKGL_ERR_SDL, "Failed to load comparison image");
|
||||
|
||||
CATCH(
|
||||
@@ -138,7 +140,7 @@ akerr_ErrorContext *test_akgl_sprite_load_json(void)
|
||||
// Is it using the right spritesheet?
|
||||
|
||||
snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/spritesheet.png");
|
||||
image = IMG_LoadTexture(renderer->sdl_renderer, (char *)&tmpstr->data);
|
||||
image = IMG_LoadTexture(akgl_renderer->sdl_renderer, (char *)&tmpstr->data);
|
||||
FAIL_ZERO_BREAK(errctx, image, AKGL_ERR_SDL, "Failed to load comparison image");
|
||||
|
||||
CATCH(
|
||||
@@ -188,7 +190,8 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
renderer = &_akgl_renderer;
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
|
||||
SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest");
|
||||
|
||||
@@ -196,10 +199,10 @@ int main(void)
|
||||
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't initialize SDL: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
if (!SDL_CreateWindowAndRenderer("net/aklabs/libakgl/test_sprite", 640, 480, 0, &window, &renderer->sdl_renderer)) {
|
||||
if (!SDL_CreateWindowAndRenderer("net/aklabs/libakgl/test_sprite", 640, 480, 0, &akgl_window, &akgl_renderer->sdl_renderer)) {
|
||||
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't create window/renderer: %s", SDL_GetError());
|
||||
}
|
||||
renderer->draw_texture = &akgl_render_2d_draw_texture;
|
||||
akgl_renderer->draw_texture = &akgl_render_2d_draw_texture;
|
||||
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
CATCH(errctx, akgl_registry_init_sprite());
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <akgl/heap.h>
|
||||
#include <akgl/staticstring.h>
|
||||
|
||||
#include "testutil.h"
|
||||
|
||||
void reset_string_heap(void);
|
||||
|
||||
akerr_ErrorContext *test_fresh_heap_gives_strings(void)
|
||||
@@ -28,7 +30,7 @@ akerr_ErrorContext *test_string_heap_error_when_no_strings_left(void)
|
||||
akgl_String *ptr;
|
||||
PREPARE_ERROR(errctx);
|
||||
for ( int i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) {
|
||||
HEAP_STRING[i].refcount = 1;
|
||||
akgl_heap_strings[i].refcount = 1;
|
||||
}
|
||||
for ( int i = 0; i < AKGL_MAX_HEAP_STRING - 1; i++ ) {
|
||||
ATTEMPT {
|
||||
@@ -45,8 +47,8 @@ akerr_ErrorContext *test_string_heap_error_when_no_strings_left(void)
|
||||
|
||||
akerr_ErrorContext *test_string_heap_honors_refcount(void)
|
||||
{
|
||||
akgl_String *firstptr = &HEAP_STRING[0];
|
||||
akgl_String *secondptr = &HEAP_STRING[1];
|
||||
akgl_String *firstptr = &akgl_heap_strings[0];
|
||||
akgl_String *secondptr = &akgl_heap_strings[1];
|
||||
akgl_String *testptr = NULL;
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
@@ -55,7 +57,7 @@ akerr_ErrorContext *test_string_heap_honors_refcount(void)
|
||||
FAIL_RETURN(
|
||||
errctx,
|
||||
AKERR_VALUE,
|
||||
"Expected testptr to equal (HEAP_STRING[0] = %p) but got %p",
|
||||
"Expected testptr to equal (akgl_heap_strings[0] = %p) but got %p",
|
||||
firstptr,
|
||||
testptr
|
||||
);
|
||||
@@ -68,7 +70,7 @@ akerr_ErrorContext *test_string_heap_honors_refcount(void)
|
||||
FAIL_RETURN(
|
||||
errctx,
|
||||
AKERR_VALUE,
|
||||
"Expected testptr to equal (HEAP_STRING[1] = %p) but got %p",
|
||||
"Expected testptr to equal (akgl_heap_strings[1] = %p) but got %p",
|
||||
secondptr,
|
||||
testptr
|
||||
);
|
||||
@@ -124,7 +126,7 @@ akerr_ErrorContext *test_akgl_string_initialize(void)
|
||||
void reset_string_heap(void)
|
||||
{
|
||||
for ( int i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) {
|
||||
memset(&HEAP_STRING[i], 0x00, sizeof(akgl_String));
|
||||
memset(&akgl_heap_strings[i], 0x00, sizeof(akgl_String));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +136,7 @@ int main(void)
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
printf("test_fresh_heap_gives_string ....\n");
|
||||
test_fresh_heap_gives_strings();
|
||||
reset_string_heap();
|
||||
|
||||
@@ -16,10 +16,45 @@
|
||||
#define _AKGL_TESTUTIL_H_
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <akerror.h>
|
||||
#include <akgl/error.h>
|
||||
|
||||
/**
|
||||
* @brief Exit with a status the shell can actually see.
|
||||
*
|
||||
* libakerror's default unhandled-error handler ends in `exit(errctx->status)`.
|
||||
* `exit` keeps only the low byte of what it is given, and libakgl's own
|
||||
* statuses start at `AKERR_FIRST_CONSUMER_STATUS`, which is 256 -- so
|
||||
* #AKGL_ERR_SDL exits 0, and CTest records a pass. Every suite in this
|
||||
* directory that failed for the most common reason in this library therefore
|
||||
* reported success. `tests/character.c` did exactly that: it aborted at its
|
||||
* second of four tests on a bad renderer and was green for months.
|
||||
*
|
||||
* This collapses any status that a byte cannot carry onto 1. It does not log --
|
||||
* `FINISH_NORETURN` has already logged the context and its stack by the time it
|
||||
* calls the handler.
|
||||
*/
|
||||
static inline void test_handler_unhandled_error(akerr_ErrorContext *errctx)
|
||||
{
|
||||
int status = ( errctx == NULL ) ? 1 : (errctx->status & 0xFF);
|
||||
|
||||
if ( status == 0 ) {
|
||||
status = 1;
|
||||
}
|
||||
exit(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Install test_handler_unhandled_error(). Call once, after akgl_error_init().
|
||||
*
|
||||
* akgl_error_init() reaches akerr_init(), which installs libakerror's default
|
||||
* handler, so this has to come after it rather than before.
|
||||
*/
|
||||
#define TEST_TRAP_UNHANDLED_ERRORS() \
|
||||
(akerr_handler_unhandled_error = &test_handler_unhandled_error)
|
||||
|
||||
/** @brief Fail the enclosing ATTEMPT block unless @p cond holds. */
|
||||
#define TEST_ASSERT(e, cond, ...) \
|
||||
if ( ! (cond) ) { \
|
||||
|
||||
23
tests/text.c
23
tests/text.c
@@ -256,7 +256,7 @@ akerr_ErrorContext *test_text_render_backend_guards(void)
|
||||
ATTEMPT {
|
||||
// Nothing initialized the renderer at all, which is where the global
|
||||
// starts and where a host that has not called akgl_game_init leaves it.
|
||||
renderer = NULL;
|
||||
akgl_renderer = NULL;
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
|
||||
akgl_text_rendertextat(testfont, "hello", white, 0, 0, 0),
|
||||
"drawing text with no renderer backend");
|
||||
@@ -264,13 +264,13 @@ akerr_ErrorContext *test_text_render_backend_guards(void)
|
||||
// A backend that exists but was never given an SDL_Renderer -- the
|
||||
// state a host is in between allocating one and initializing it.
|
||||
memset(&backend, 0x00, sizeof(akgl_RenderBackend));
|
||||
renderer = &backend;
|
||||
akgl_renderer = &backend;
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
|
||||
akgl_text_rendertextat(testfont, "hello", white, 0, 0, 0),
|
||||
"drawing text through a backend with no SDL renderer");
|
||||
|
||||
// A live SDL_Renderer but no methods: a host that created its own
|
||||
// renderer and has not called akgl_render_bind2d() yet. This is the one
|
||||
// renderer and has not called akgl_render_2d_bind() yet. This is the one
|
||||
// that used to be a segfault rather than an error -- with a real
|
||||
// renderer behind it the call gets all the way to a NULL function
|
||||
// pointer, which is why the check has to be here and not left to SDL.
|
||||
@@ -279,7 +279,7 @@ akerr_ErrorContext *test_text_render_backend_guards(void)
|
||||
akgl_text_rendertextat(testfont, "hello", white, 0, 0, 0),
|
||||
"drawing text through a backend that was never bound");
|
||||
|
||||
renderer = &testbackend;
|
||||
akgl_renderer = &testbackend;
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
|
||||
akgl_text_rendertextat(NULL, "hello", white, 0, 0, 0),
|
||||
"drawing text with a NULL font");
|
||||
@@ -287,7 +287,7 @@ akerr_ErrorContext *test_text_render_backend_guards(void)
|
||||
akgl_text_rendertextat(testfont, NULL, white, 0, 0, 0),
|
||||
"drawing a NULL string");
|
||||
} CLEANUP {
|
||||
renderer = &testbackend;
|
||||
akgl_renderer = &testbackend;
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -302,8 +302,8 @@ akerr_ErrorContext *test_text_rendertextat(void)
|
||||
// A bound backend over a software renderer is enough to draw through:
|
||||
// no display, no window shown, and the whole path from rasterizing to
|
||||
// blitting runs.
|
||||
renderer = &testbackend;
|
||||
TEST_EXPECT_OK(errctx, renderer->frame_start(renderer), "starting a frame");
|
||||
akgl_renderer = &testbackend;
|
||||
TEST_EXPECT_OK(errctx, akgl_renderer->frame_start(akgl_renderer), "starting a frame");
|
||||
TEST_EXPECT_OK(errctx, akgl_text_rendertextat(testfont, "hello", white, 0, 4, 4),
|
||||
"drawing a line of text");
|
||||
// Wrapping takes the other rasterizing path.
|
||||
@@ -314,7 +314,7 @@ akerr_ErrorContext *test_text_rendertextat(void)
|
||||
// has zero width" on both paths, so drawing an empty line reports a
|
||||
// failure rather than drawing nothing. That is filed rather than pinned
|
||||
// -- see TODO.md, "Known and still open".
|
||||
TEST_EXPECT_OK(errctx, renderer->frame_end(renderer), "ending the frame");
|
||||
TEST_EXPECT_OK(errctx, akgl_renderer->frame_end(akgl_renderer), "ending the frame");
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
@@ -331,6 +331,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
memset(&testbackend, 0x00, sizeof(akgl_RenderBackend));
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
@@ -355,7 +356,7 @@ int main(void)
|
||||
SDL_GetError());
|
||||
|
||||
// The host owns the window and libakgl binds to it, which is the
|
||||
// arrangement akgl_render_bind2d exists for and the one an embedded
|
||||
// arrangement akgl_render_2d_bind exists for and the one an embedded
|
||||
// interpreter drawing text is in.
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
@@ -364,12 +365,12 @@ int main(void)
|
||||
TEST_TARGET_SIZE,
|
||||
TEST_TARGET_SIZE,
|
||||
0,
|
||||
&window,
|
||||
&akgl_window,
|
||||
&testbackend.sdl_renderer),
|
||||
AKGL_ERR_SDL,
|
||||
"Couldn't create window/renderer: %s",
|
||||
SDL_GetError());
|
||||
CATCH(errctx, akgl_render_bind2d(&testbackend));
|
||||
CATCH(errctx, akgl_render_2d_bind(&testbackend));
|
||||
|
||||
CATCH(errctx, test_text_loadfont());
|
||||
CATCH(errctx, test_text_measure());
|
||||
|
||||
181
tests/tilemap.c
181
tests/tilemap.c
@@ -11,6 +11,8 @@
|
||||
#include <akgl/game.h>
|
||||
#include <akgl/json_helpers.h>
|
||||
|
||||
#include "testutil.h"
|
||||
|
||||
akerr_ErrorContext *test_tilemap_akgl_get_json_tilemap_property(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
@@ -20,8 +22,8 @@ akerr_ErrorContext *test_tilemap_akgl_get_json_tilemap_property(void)
|
||||
int propnum;
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
CATCH(errctx, akgl_heap_next_string(&tmpstr));
|
||||
snprintf(
|
||||
(char *)&tmpstr->data,
|
||||
@@ -87,34 +89,34 @@ akerr_ErrorContext *test_akgl_tilemap_compute_tileset_offsets(void)
|
||||
10 // Tile 2 Y
|
||||
};
|
||||
int *comparison_ptrs[8] = {
|
||||
&gamemap->tilesets[0].tile_offsets[0][0], // Tile 0 X
|
||||
&gamemap->tilesets[0].tile_offsets[0][1], // Tile 0 Y
|
||||
&gamemap->tilesets[0].tile_offsets[1][0], // Tile 1 X
|
||||
&gamemap->tilesets[0].tile_offsets[1][1], // Tile 0 Y
|
||||
&gamemap->tilesets[0].tile_offsets[2][0], // Tile 2 X
|
||||
&gamemap->tilesets[0].tile_offsets[2][1], // Tile 2 Y
|
||||
&gamemap->tilesets[0].tile_offsets[3][0], // Tile 3 X
|
||||
&gamemap->tilesets[0].tile_offsets[3][1], // Tile 3 Y
|
||||
&akgl_gamemap->tilesets[0].tile_offsets[0][0], // Tile 0 X
|
||||
&akgl_gamemap->tilesets[0].tile_offsets[0][1], // Tile 0 Y
|
||||
&akgl_gamemap->tilesets[0].tile_offsets[1][0], // Tile 1 X
|
||||
&akgl_gamemap->tilesets[0].tile_offsets[1][1], // Tile 0 Y
|
||||
&akgl_gamemap->tilesets[0].tile_offsets[2][0], // Tile 2 X
|
||||
&akgl_gamemap->tilesets[0].tile_offsets[2][1], // Tile 2 Y
|
||||
&akgl_gamemap->tilesets[0].tile_offsets[3][0], // Tile 3 X
|
||||
&akgl_gamemap->tilesets[0].tile_offsets[3][1], // Tile 3 Y
|
||||
};
|
||||
int i = 0;
|
||||
|
||||
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
gamemap->tilesets[0].tilecount = 4;
|
||||
gamemap->tilesets[0].columns = 2;
|
||||
gamemap->tilesets[0].firstgid = 1;
|
||||
gamemap->tilesets[0].imageheight = 20;
|
||||
gamemap->tilesets[0].imagewidth = 20;
|
||||
gamemap->tilesets[0].tilecount = 4;
|
||||
gamemap->tilesets[0].tileheight = 10;
|
||||
gamemap->tilesets[0].tilewidth = 10;
|
||||
gamemap->tilesets[0].spacing = 0;
|
||||
gamemap->tilesets[0].margin = 0;
|
||||
memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
akgl_gamemap->tilesets[0].tilecount = 4;
|
||||
akgl_gamemap->tilesets[0].columns = 2;
|
||||
akgl_gamemap->tilesets[0].firstgid = 1;
|
||||
akgl_gamemap->tilesets[0].imageheight = 20;
|
||||
akgl_gamemap->tilesets[0].imagewidth = 20;
|
||||
akgl_gamemap->tilesets[0].tilecount = 4;
|
||||
akgl_gamemap->tilesets[0].tileheight = 10;
|
||||
akgl_gamemap->tilesets[0].tilewidth = 10;
|
||||
akgl_gamemap->tilesets[0].spacing = 0;
|
||||
akgl_gamemap->tilesets[0].margin = 0;
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
CATCH(errctx, akgl_tilemap_compute_tileset_offsets(gamemap, 0));
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
CATCH(errctx, akgl_tilemap_compute_tileset_offsets(akgl_gamemap, 0));
|
||||
// Tile 0 X offset
|
||||
for ( i = 0; i < 8; i++ ) {
|
||||
FAIL_NONZERO_BREAK(
|
||||
@@ -143,10 +145,10 @@ akerr_ErrorContext *test_akgl_tilemap_load_layer_objects(void)
|
||||
json_error_t errdata;
|
||||
akgl_Actor *testactor;
|
||||
|
||||
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
CATCH(errctx, akgl_heap_next_string(&pathstr));
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj");
|
||||
doc = json_load_file((char *)&pathstr->data, 0, &errdata);
|
||||
@@ -154,7 +156,7 @@ akerr_ErrorContext *test_akgl_tilemap_load_layer_objects(void)
|
||||
FAIL_ZERO_BREAK(errctx, doc, AKERR_NULLPOINTER, "Failed to load testmap: %s", (char *)&errdata.text);
|
||||
CATCH(errctx, akgl_get_json_array_value(doc, "layers", &layers));
|
||||
CATCH(errctx, akgl_get_json_array_index_object(layers, 1, &objectlayer));
|
||||
CATCH(errctx, akgl_tilemap_load_layer_objects(gamemap, objectlayer, 1, pathstr));
|
||||
CATCH(errctx, akgl_tilemap_load_layer_objects(akgl_gamemap, objectlayer, 1, pathstr));
|
||||
|
||||
testactor = SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, "testactor", NULL);
|
||||
FAIL_ZERO_BREAK(
|
||||
@@ -193,11 +195,11 @@ akerr_ErrorContext *test_akgl_tilemap_load_layer_tile(void)
|
||||
json_t *tiledata = NULL;
|
||||
json_error_t errdata;
|
||||
|
||||
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
CATCH(errctx, akgl_heap_next_string(&pathstr));
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj");
|
||||
doc = json_load_file((char *)&pathstr->data, 0, &errdata);
|
||||
@@ -206,11 +208,11 @@ akerr_ErrorContext *test_akgl_tilemap_load_layer_tile(void)
|
||||
CATCH(errctx, akgl_get_json_array_value(doc, "layers", &layers));
|
||||
CATCH(errctx, akgl_get_json_array_index_object(layers, 0, &tilelayer));
|
||||
CATCH(errctx, akgl_get_json_array_value(tilelayer, "data", &tiledata));
|
||||
CATCH(errctx, akgl_tilemap_load_layer_tile(gamemap, tilelayer, 0, pathstr));
|
||||
if ( (gamemap->layers[0].data[0] != 1) ||
|
||||
(gamemap->layers[0].data[1] != 2) ||
|
||||
(gamemap->layers[0].data[2] != 3) ||
|
||||
(gamemap->layers[0].data[3] != 4) ) {
|
||||
CATCH(errctx, akgl_tilemap_load_layer_tile(akgl_gamemap, tilelayer, 0, pathstr));
|
||||
if ( (akgl_gamemap->layers[0].data[0] != 1) ||
|
||||
(akgl_gamemap->layers[0].data[1] != 2) ||
|
||||
(akgl_gamemap->layers[0].data[2] != 3) ||
|
||||
(akgl_gamemap->layers[0].data[3] != 4) ) {
|
||||
FAIL_BREAK(errctx, AKERR_VALUE, "Test tilemap layer 0 tiles loaded with incorrect values (check gdb)");
|
||||
}
|
||||
} CLEANUP {
|
||||
@@ -233,61 +235,61 @@ akerr_ErrorContext *test_akgl_tilemap_load_layers(void)
|
||||
json_error_t errdata;
|
||||
int i = 0;
|
||||
|
||||
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
CATCH(errctx, akgl_heap_next_string(&pathstr));
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj");
|
||||
doc = json_load_file((char *)&pathstr->data, 0, &errdata);
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets");
|
||||
FAIL_ZERO_BREAK(errctx, doc, AKERR_NULLPOINTER, "Failed to load testmap: %s", (char *)&errdata.text);
|
||||
CATCH(errctx, akgl_tilemap_load_layers(gamemap, doc, pathstr));
|
||||
CATCH(errctx, akgl_tilemap_load_layers(akgl_gamemap, doc, pathstr));
|
||||
FAIL_NONZERO_BREAK(
|
||||
errctx,
|
||||
(gamemap->numlayers != 3),
|
||||
(akgl_gamemap->numlayers != 3),
|
||||
AKERR_VALUE,
|
||||
"Map layer count incorrect"
|
||||
);
|
||||
for ( i = 0; i < gamemap->numlayers; i++ ) {
|
||||
if ( (gamemap->layers[i].opacity != 1) ||
|
||||
(gamemap->layers[i].visible != true) ||
|
||||
(gamemap->layers[i].id != (i + 1)) ||
|
||||
(gamemap->layers[i].x != 0) ||
|
||||
(gamemap->layers[i].y != 0) ) {
|
||||
for ( i = 0; i < akgl_gamemap->numlayers; i++ ) {
|
||||
if ( (akgl_gamemap->layers[i].opacity != 1) ||
|
||||
(akgl_gamemap->layers[i].visible != true) ||
|
||||
(akgl_gamemap->layers[i].id != (i + 1)) ||
|
||||
(akgl_gamemap->layers[i].x != 0) ||
|
||||
(akgl_gamemap->layers[i].y != 0) ) {
|
||||
FAIL(errctx, AKERR_VALUE, "Map layer data loaded incorrectly (see gdb)");
|
||||
goto _test_akgl_tilemap_load_layers_cleanup;
|
||||
}
|
||||
}
|
||||
// Layer 2 should have 1 object loaded
|
||||
if ( (gamemap->layers[1].objects[0].actorptr != SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, "testactor", NULL)) ||
|
||||
(gamemap->layers[1].objects[1].name[0] != '\0' ) ||
|
||||
(gamemap->layers[1].objects[1].id != 0) ) {
|
||||
if ( (akgl_gamemap->layers[1].objects[0].actorptr != SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, "testactor", NULL)) ||
|
||||
(akgl_gamemap->layers[1].objects[1].name[0] != '\0' ) ||
|
||||
(akgl_gamemap->layers[1].objects[1].id != 0) ) {
|
||||
FAIL_BREAK(errctx, AKERR_VALUE, "Map layer 2 should have 1 loaded object (testactor) and nothing else (see gdb)");
|
||||
}
|
||||
// Layer 1 and 3 should have no objects
|
||||
for ( i = 0; i < AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER ; i++ ) {
|
||||
if ( gamemap->layers[0].objects[i].id != 0 ) {
|
||||
if ( akgl_gamemap->layers[0].objects[i].id != 0 ) {
|
||||
FAIL(errctx, AKERR_VALUE, "Map layers 1 and 3 should have no objects loaded but found objects");
|
||||
goto _test_akgl_tilemap_load_layers_cleanup;
|
||||
}
|
||||
}
|
||||
for ( i = 0; i < AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER ; i++ ) {
|
||||
if ( gamemap->layers[2].objects[i].id != 0 ) {
|
||||
if ( akgl_gamemap->layers[2].objects[i].id != 0 ) {
|
||||
FAIL(errctx, AKERR_VALUE, "Map layers 1 and 3 should have no objects loaded but found objects");
|
||||
goto _test_akgl_tilemap_load_layers_cleanup;
|
||||
}
|
||||
}
|
||||
// Layers 1 and 3 should have tile data
|
||||
if ( (gamemap->layers[0].data[0] != 1) ||
|
||||
(gamemap->layers[0].data[1] != 2) ||
|
||||
(gamemap->layers[0].data[2] != 3) ||
|
||||
(gamemap->layers[0].data[3] != 4) ||
|
||||
(gamemap->layers[2].data[0] != 0) ||
|
||||
(gamemap->layers[2].data[1] != 5) ||
|
||||
(gamemap->layers[2].data[2] != 0) ||
|
||||
(gamemap->layers[2].data[3] != 6)
|
||||
if ( (akgl_gamemap->layers[0].data[0] != 1) ||
|
||||
(akgl_gamemap->layers[0].data[1] != 2) ||
|
||||
(akgl_gamemap->layers[0].data[2] != 3) ||
|
||||
(akgl_gamemap->layers[0].data[3] != 4) ||
|
||||
(akgl_gamemap->layers[2].data[0] != 0) ||
|
||||
(akgl_gamemap->layers[2].data[1] != 5) ||
|
||||
(akgl_gamemap->layers[2].data[2] != 0) ||
|
||||
(akgl_gamemap->layers[2].data[3] != 6)
|
||||
) {
|
||||
FAIL_BREAK(errctx, AKERR_VALUE, "Map layers 1 and 3 should have tile data but it is incorrect");
|
||||
}
|
||||
@@ -312,43 +314,43 @@ akerr_ErrorContext *test_akgl_tilemap_load_tilesets(void)
|
||||
json_error_t errdata;
|
||||
SDL_Texture *image = NULL;
|
||||
|
||||
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
CATCH(errctx, akgl_heap_next_string(&pathstr));
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj");
|
||||
doc = json_load_file((char *)&pathstr->data, 0, &errdata);
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets");
|
||||
FAIL_ZERO_BREAK(errctx, doc, AKERR_NULLPOINTER, "Failed to load testmap: %s", (char *)&errdata.text);
|
||||
CATCH(errctx, akgl_tilemap_load_tilesets(gamemap, doc, pathstr));
|
||||
FAIL_NONZERO_BREAK(errctx, (gamemap->numtilesets != 1), AKERR_VALUE, "Incorrect number of tilesets loaded for map");
|
||||
if ( (gamemap->tilesets[0].columns != 48 ) ||
|
||||
(gamemap->tilesets[0].firstgid != 1) ||
|
||||
(gamemap->tilesets[0].imageheight != 576) ||
|
||||
(gamemap->tilesets[0].imagewidth != 768) ||
|
||||
(gamemap->tilesets[0].margin != 0) ||
|
||||
(gamemap->tilesets[0].spacing != 0) ||
|
||||
(gamemap->tilesets[0].tilecount != 1728) ||
|
||||
(gamemap->tilesets[0].tileheight != 16) ||
|
||||
(gamemap->tilesets[0].tilewidth != 16) ) {
|
||||
CATCH(errctx, akgl_tilemap_load_tilesets(akgl_gamemap, doc, pathstr));
|
||||
FAIL_NONZERO_BREAK(errctx, (akgl_gamemap->numtilesets != 1), AKERR_VALUE, "Incorrect number of tilesets loaded for map");
|
||||
if ( (akgl_gamemap->tilesets[0].columns != 48 ) ||
|
||||
(akgl_gamemap->tilesets[0].firstgid != 1) ||
|
||||
(akgl_gamemap->tilesets[0].imageheight != 576) ||
|
||||
(akgl_gamemap->tilesets[0].imagewidth != 768) ||
|
||||
(akgl_gamemap->tilesets[0].margin != 0) ||
|
||||
(akgl_gamemap->tilesets[0].spacing != 0) ||
|
||||
(akgl_gamemap->tilesets[0].tilecount != 1728) ||
|
||||
(akgl_gamemap->tilesets[0].tileheight != 16) ||
|
||||
(akgl_gamemap->tilesets[0].tilewidth != 16) ) {
|
||||
FAIL_BREAK(errctx, AKERR_VALUE, "Tileset loaded with incorrect values");
|
||||
}
|
||||
FAIL_NONZERO_BREAK(
|
||||
errctx,
|
||||
strcmp((char *)&gamemap->tilesets[0].name, "World_A1"),
|
||||
strcmp((char *)&akgl_gamemap->tilesets[0].name, "World_A1"),
|
||||
AKERR_VALUE,
|
||||
"Tileset loaded with incorrect name");
|
||||
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/World_A1.png");
|
||||
image = IMG_LoadTexture(renderer->sdl_renderer, (char *)&pathstr->data);
|
||||
image = IMG_LoadTexture(akgl_renderer->sdl_renderer, (char *)&pathstr->data);
|
||||
FAIL_ZERO_BREAK(errctx, image, AKGL_ERR_SDL, "Failed to load comparison image");
|
||||
|
||||
CATCH(
|
||||
errctx,
|
||||
akgl_render_and_compare(
|
||||
gamemap->tilesets[0].texture,
|
||||
akgl_gamemap->tilesets[0].texture,
|
||||
image,
|
||||
0, 0, 768, 576,
|
||||
"test_akgl_tilemap_loaded_tileset.png")
|
||||
@@ -370,12 +372,12 @@ akerr_ErrorContext *test_akgl_tilemap_load(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
CATCH(errctx, akgl_tilemap_load("assets/testmap.tmj", gamemap));
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
CATCH(errctx, akgl_tilemap_load("assets/testmap.tmj", akgl_gamemap));
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
@@ -387,8 +389,8 @@ akerr_ErrorContext *test_akgl_tilemap_draw(void)
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
@@ -401,8 +403,8 @@ akerr_ErrorContext *test_akgl_tilemap_draw_tileset(void)
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
@@ -415,18 +417,19 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest");
|
||||
|
||||
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO )) {
|
||||
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't initialize SDL: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
if (!SDL_CreateWindowAndRenderer("net/aklabs/libakgl/test_sprite", 768, 576, 0, &window, &renderer->sdl_renderer)) {
|
||||
if (!SDL_CreateWindowAndRenderer("net/aklabs/libakgl/test_sprite", 768, 576, 0, &akgl_window, &akgl_renderer->sdl_renderer)) {
|
||||
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't create window/renderer: %s", SDL_GetError());
|
||||
}
|
||||
renderer->draw_texture = &akgl_render_2d_draw_texture;
|
||||
akgl_renderer->draw_texture = &akgl_render_2d_draw_texture;
|
||||
|
||||
CATCH(errctx, akgl_registry_init());
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
|
||||
23
tests/util.c
23
tests/util.c
@@ -6,6 +6,8 @@
|
||||
#include <akgl/staticstring.h>
|
||||
#include <akgl/util.h>
|
||||
|
||||
#include "testutil.h"
|
||||
|
||||
/**
|
||||
* @brief How many entries of AKERR_ARRAY_ERROR are currently held by somebody.
|
||||
*
|
||||
@@ -28,14 +30,14 @@ static int live_error_contexts(void)
|
||||
|
||||
akerr_ErrorContext *test_akgl_rectangle_points_nullpointers(void)
|
||||
{
|
||||
RectanglePoints points;
|
||||
akgl_RectanglePoints points;
|
||||
// Zeroed for the same reason as the fixtures in
|
||||
// test_akgl_collide_point_rectangle_nullpointers: the last case here is a
|
||||
// real call, and feeding it stack garbage is noise under `memcheck`.
|
||||
SDL_FRect testrect = {.x = 0, .y = 0, .w = 0, .h = 0};
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
memset((void *)&points, 0x00, sizeof(RectanglePoints));
|
||||
memset((void *)&points, 0x00, sizeof(akgl_RectanglePoints));
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_rectangle_points(NULL, NULL));
|
||||
@@ -57,7 +59,7 @@ akerr_ErrorContext *test_akgl_rectangle_points_nullpointers(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_rectangle_points(&points, NULL));
|
||||
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_rectangle_points fails to FAIL with NULL RectanglePoints pointer");
|
||||
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_rectangle_points fails to FAIL with NULL akgl_RectanglePoints pointer");
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
@@ -75,9 +77,9 @@ akerr_ErrorContext *test_akgl_rectangle_points_nullpointers(void)
|
||||
|
||||
akerr_ErrorContext *test_akgl_rectangle_points_math(void)
|
||||
{
|
||||
RectanglePoints points;
|
||||
akgl_RectanglePoints points;
|
||||
SDL_FRect testrect = {.x = 0, .y = 0, .w = 32, .h = 32};
|
||||
memset((void *)&points, 0x00, sizeof(RectanglePoints));
|
||||
memset((void *)&points, 0x00, sizeof(akgl_RectanglePoints));
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
@@ -112,13 +114,13 @@ akerr_ErrorContext *test_akgl_collide_point_rectangle_nullpointers(void)
|
||||
// function is a real call with real arguments, and reading uninitialised
|
||||
// floats out of it is sixteen findings under `memcheck` for a test that is
|
||||
// not about coordinates at all.
|
||||
point testpoint = { .x = 0, .y = 0 };
|
||||
RectanglePoints testrectpoints;
|
||||
akgl_Point testpoint = { .x = 0, .y = 0 };
|
||||
akgl_RectanglePoints testrectpoints;
|
||||
bool testcollide = false;
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
memset(&testrectpoints, 0x00, sizeof(RectanglePoints));
|
||||
memset(&testrectpoints, 0x00, sizeof(akgl_RectanglePoints));
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&testpoint, &testrectpoints, NULL));
|
||||
@@ -167,9 +169,9 @@ akerr_ErrorContext *test_akgl_collide_point_rectangle_nullpointers(void)
|
||||
|
||||
akerr_ErrorContext *test_akgl_collide_point_rectangle_logic(void)
|
||||
{
|
||||
point testpoint = {.x = 16, .y = 16};
|
||||
akgl_Point testpoint = {.x = 16, .y = 16};
|
||||
SDL_FRect testrect = { .x = 0, .y = 0, .w = 32, .h = 32};
|
||||
RectanglePoints testrectpoints;
|
||||
akgl_RectanglePoints testrectpoints;
|
||||
bool testcollide = false;
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
@@ -392,6 +394,7 @@ int main(void)
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, test_akgl_rectangle_points_nullpointers());
|
||||
CATCH(errctx, test_akgl_rectangle_points_math());
|
||||
CATCH(errctx, test_akgl_collide_point_rectangle_nullpointers());
|
||||
|
||||
@@ -99,6 +99,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, test_version_string_matches_components());
|
||||
CATCH(errctx, test_version_linked_matches_compiled());
|
||||
CATCH(errctx, test_version_at_least_boundaries());
|
||||
|
||||
Reference in New Issue
Block a user