#define UNHANDLED_ERROR_TERMINATION_BEHAVIOR \ handle_unhandled_error(errctx); #include #include #define UNHANDLED_ERROR_EXIT 0 #define UNHANDLED_ERROR_SET 1 #include #include #include #include #include #include #include #include #include #include #include "testutil.h" int UNHANDLED_ERROR_BEHAVIOR; akerr_ErrorContext *unhandled_error_context; void handle_unhandled_error_noexit(akerr_ErrorContext *errctx) { if ( errctx == NULL ) { return; } if ( UNHANDLED_ERROR_BEHAVIOR == UNHANDLED_ERROR_EXIT ) { exit(errctx->status); } if ( UNHANDLED_ERROR_BEHAVIOR == UNHANDLED_ERROR_SET ) { unhandled_error_context = errctx; errctx->refcount += 1; return; } } int akgl_actor_updated; akerr_ErrorContext *akgl_actor_update_noop(akgl_Actor *obj) { PREPARE_ERROR(errctx); akgl_actor_updated = 1; SUCCEED_RETURN(errctx); } // Currently the renderer assumes there is a global variable named `renderer` int akgl_actor_rendered; akerr_ErrorContext *akgl_actor_render_noop(akgl_Actor *obj) { PREPARE_ERROR(errctx); akgl_actor_rendered = 1; SUCCEED_RETURN(errctx); } akerr_ErrorContext *test_registry_actor_iterator_nullpointers(void) { PREPARE_ERROR(errctx); akerr_ErrorUnhandledErrorHandler defaulthandler = akerr_handler_unhandled_error; akerr_handler_unhandled_error = handle_unhandled_error_noexit; ATTEMPT { UNHANDLED_ERROR_BEHAVIOR = UNHANDLED_ERROR_SET; DETECT(unhandled_error_context, akgl_registry_iterate_actor(NULL, AKGL_REGISTRY_ACTOR, "")); } CLEANUP { UNHANDLED_ERROR_BEHAVIOR = UNHANDLED_ERROR_EXIT; } PROCESS(unhandled_error_context) { } HANDLE(unhandled_error_context, AKERR_NULLPOINTER) { printf("Handled\n"); } FINISH(unhandled_error_context, true); akerr_handler_unhandled_error = defaulthandler; SUCCEED_RETURN(errctx); } akerr_ErrorContext *test_registry_actor_iterator_missingactor(void) { PREPARE_ERROR(errctx); akerr_ErrorUnhandledErrorHandler defaulthandler = akerr_handler_unhandled_error; akgl_Iterator iter = { .layerid = 0, .flags = 0 }; akerr_handler_unhandled_error = handle_unhandled_error_noexit; ATTEMPT { UNHANDLED_ERROR_BEHAVIOR = UNHANDLED_ERROR_SET; DETECT( unhandled_error_context, akgl_registry_iterate_actor( &iter, AKGL_REGISTRY_ACTOR, "Actor who doesn't exist") ); } CLEANUP { UNHANDLED_ERROR_BEHAVIOR = UNHANDLED_ERROR_EXIT; } PROCESS(unhandled_error_context) { } HANDLE(unhandled_error_context, AKERR_KEY) { printf("Handled\n"); } FINISH(unhandled_error_context, true); akerr_handler_unhandled_error = defaulthandler; SUCCEED_RETURN(errctx); } akerr_ErrorContext *test_registry_actor_iterator_updaterender(void) { akgl_Actor *testactor; akgl_Iterator iter = { .layerid = 0, .flags = AKGL_ITERATOR_OP_UPDATE | AKGL_ITERATOR_OP_RENDER }; akerr_ErrorUnhandledErrorHandler defaulthandler = akerr_handler_unhandled_error; PREPARE_ERROR(errctx); akerr_handler_unhandled_error = handle_unhandled_error_noexit; ATTEMPT { UNHANDLED_ERROR_BEHAVIOR = UNHANDLED_ERROR_SET; CATCH(unhandled_error_context, akgl_heap_next_actor(&testactor)); CATCH(unhandled_error_context, akgl_actor_initialize(testactor, "test")); testactor->layer = 0; testactor->updatefunc = &akgl_actor_update_noop; testactor->renderfunc = &akgl_actor_render_noop; DETECT( unhandled_error_context, akgl_registry_iterate_actor( &iter, AKGL_REGISTRY_ACTOR, "test") ); FAIL_ZERO_BREAK( unhandled_error_context, akgl_actor_updated, AKGL_ERR_BEHAVIOR, "actor->updatefunc not called by the iterator" ); FAIL_ZERO_BREAK( unhandled_error_context, akgl_actor_rendered, AKGL_ERR_BEHAVIOR, "actor->renderfunc not called by the iterator" ); } CLEANUP { UNHANDLED_ERROR_BEHAVIOR = UNHANDLED_ERROR_EXIT; IGNORE(akgl_heap_release_actor(testactor)); } PROCESS(unhandled_error_context) { } FINISH(unhandled_error_context, true); akerr_handler_unhandled_error = defaulthandler; SUCCEED_RETURN(errctx); } akerr_ErrorContext *test_akgl_actor_set_character(void) { akgl_Actor *testactor = NULL; akgl_Character *testchar = NULL; PREPARE_ERROR(errctx); ATTEMPT { CATCH(errctx, akgl_actor_set_character(NULL, "test")); } CLEANUP { } PROCESS(errctx) { } HANDLE(errctx, AKERR_NULLPOINTER) { printf("Handled\n"); } FINISH(errctx, true); ATTEMPT { CATCH(errctx, akgl_heap_next_actor(&testactor)); CATCH(errctx, akgl_actor_initialize(testactor, "test")); testactor->layer = 0; testactor->updatefunc = &akgl_actor_update_noop; testactor->renderfunc = &akgl_actor_render_noop; CATCH(errctx, akgl_actor_set_character(testactor, "test")); } CLEANUP { IGNORE(akgl_heap_release_actor(testactor)); } PROCESS(errctx) { } HANDLE(errctx, AKERR_NULLPOINTER) { printf("Handled\n"); } FINISH(errctx, true); ATTEMPT { CATCH(errctx, akgl_heap_next_actor(&testactor)); CATCH(errctx, akgl_heap_next_character(&testchar)); CATCH(errctx, akgl_actor_initialize(testactor, "test")); testactor->layer = 0; testactor->updatefunc = &akgl_actor_update_noop; testactor->renderfunc = &akgl_actor_render_noop; CATCH(errctx, akgl_character_initialize(testchar, "test")); CATCH(errctx, akgl_actor_set_character(testactor, "test")); } CLEANUP { IGNORE(akgl_heap_release_actor(testactor)); IGNORE(akgl_heap_release_character(testchar)); } PROCESS(errctx) { } FINISH(errctx, true); SUCCEED_RETURN(errctx); } akerr_ErrorContext *test_actor_manage_children(void) { akgl_Actor *parent = NULL; akgl_Actor *child = NULL; akgl_String *tmpstring = NULL; int i = 0; int j = 0; PREPARE_ERROR(errctx); ATTEMPT { CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_heap_next_string(&tmpstring)); CATCH(errctx, akgl_heap_next_actor(&parent)); CATCH(errctx, akgl_actor_initialize(parent, "parent")); for ( i = 0 ; i < AKGL_ACTOR_MAX_CHILDREN; i++ ) { sprintf((char *)&tmpstring->data, "child %d", i); CATCH(errctx, akgl_heap_next_actor(&child)); CATCH(errctx, akgl_actor_initialize(child, (char *)&tmpstring->data)); CATCH(errctx, parent->addchild(parent, child)); // Release our claim on the actor so the parent can own the child and kill it CATCH(errctx, akgl_heap_release_actor(child)); } } CLEANUP { IGNORE(akgl_heap_release_string(tmpstring)); } PROCESS(errctx) { } FINISH(errctx, true); ATTEMPT { // Doesn't matter which child we use for this test child = parent->children[1]; CATCH(errctx, parent->addchild(parent, child)); } CLEANUP { if ( errctx == NULL ) { FAIL(errctx, AKGL_ERR_BEHAVIOR, "addchild does not throw AKERR_RELATIONSHIP when child already has a parent"); } } PROCESS(errctx) { } HANDLE(errctx, AKERR_RELATIONSHIP) { // Expected behavior SDL_Log("addchild throws AKERR_RELATIONSHIP when child already has a parent"); } FINISH(errctx, true); ATTEMPT { CATCH(errctx, akgl_heap_next_actor(&child)); CATCH(errctx, parent->addchild(parent, child)); } CLEANUP { if ( errctx == NULL ) { FAIL(errctx, AKGL_ERR_BEHAVIOR, "addchild does not throw AKERR_OUTOFBOUNDS when all children already set"); } } PROCESS(errctx) { } HANDLE(errctx, AKERR_OUTOFBOUNDS) { // Expected behavior SDL_Log("addchild throws AKERR_OUTOFBOUNDS when all children already set"); } FINISH(errctx, true); ATTEMPT { 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, 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 ( akgl_heap_actors[i].children[j] != NULL ) { FAIL(errctx, AKERR_VALUE, "Actor not properly cleared"); goto _test_actor_addchild_heaprelease_cleanup; } } } for ( j = 0; j < AKGL_ACTOR_MAX_CHILDREN; j++) { sprintf((char *)&tmpstring->data, "child %d", i); FAIL_NONZERO_BREAK( errctx, SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, (char *)&tmpstring->data, NULL), AKERR_KEY, "Child %s was not removed from the registry", (char *)&tmpstring->data); } _test_actor_addchild_heaprelease_cleanup: } CLEANUP { } PROCESS(errctx) { } FINISH(errctx, true); ATTEMPT { CATCH(errctx, akgl_heap_next_actor(&parent)); CATCH(errctx, akgl_actor_initialize(parent, "parent")); CATCH(errctx, akgl_heap_next_actor(&child)); CATCH(errctx, akgl_actor_initialize(child, "child")); // Don't release our claim on the child. The child should not be reclaimed. CATCH(errctx, akgl_heap_release_actor(parent)); FAIL_NONZERO_BREAK(errctx, child->parent, AKERR_VALUE, "Child still references released parent"); FAIL_ZERO_BREAK(errctx, child->refcount, AKERR_VALUE, "Child prematurely released"); FAIL_NONZERO_BREAK(errctx, strcmp(child->name, "child"), AKERR_VALUE, "Child had identity erased"); FAIL_ZERO_BREAK( errctx, (child == SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, child->name, NULL)), AKERR_KEY, "Child prematurely removed from the registry"); // Now we can release the child CATCH(errctx, akgl_heap_release_actor(child)); } CLEANUP { } PROCESS(errctx) { } FINISH(errctx, true); SUCCEED_RETURN(errctx); } /** * @brief Build an actor bound to a character, without touching the renderer. * * The sprite and spritesheet are taken straight off the heap and populated by * hand, because akgl_sprite_load_json() would need a live renderer to build a * texture and none of the logic under test reads one. */ static akerr_ErrorContext *make_bound_actor( akgl_Actor **actor, akgl_Character **basechar, akgl_Sprite **sprite, char *name, int state) { PREPARE_ERROR(e); akgl_SpriteSheet *sheet = NULL; char spritename[AKGL_SPRITE_MAX_NAME_LENGTH]; ATTEMPT { CATCH(e, akgl_heap_next_character(basechar)); CATCH(e, akgl_character_initialize(*basechar, name)); CATCH(e, akgl_heap_next_spritesheet(&sheet)); sheet->refcount += 1; snprintf((char *)&spritename, AKGL_SPRITE_MAX_NAME_LENGTH, "%s_sprite", name); CATCH(e, akgl_heap_next_sprite(sprite)); CATCH(e, akgl_sprite_initialize(*sprite, (char *)&spritename, sheet)); (*sprite)->width = 32; (*sprite)->height = 32; (*sprite)->frames = 4; (*sprite)->speed = 100; CATCH(e, akgl_character_sprite_add(*basechar, *sprite, state)); CATCH(e, akgl_heap_next_actor(actor)); CATCH(e, akgl_actor_initialize(*actor, name)); (*actor)->basechar = *basechar; (*actor)->state = state; } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_actor_control_handlers_on(void) { PREPARE_ERROR(e); akgl_Actor actor; akgl_Character basechar; SDL_Event event; ATTEMPT { memset(&event, 0x00, sizeof(SDL_Event)); memset(&basechar, 0x00, sizeof(akgl_Character)); basechar.ax = 7.0f; basechar.ay = 11.0f; // Left: clears every facing and movement bit, then sets its own pair and // takes acceleration from the base character with the sign reversed. 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_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), "left on did not set FACE_LEFT (state %d)", actor.state); TEST_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_MOVING_RIGHT), "left on left MOVING_RIGHT set (state %d)", actor.state); TEST_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_FACE_RIGHT), "left on left FACE_RIGHT set (state %d)", actor.state); TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_ALIVE), "left on cleared an unrelated state bit (state %d)", actor.state); TEST_ASSERT_FEQ(e, actor.ax, -7.0f, "left on set ax to %f, expected -7", actor.ax); // 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_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), "right on did not set FACE_RIGHT (state %d)", actor.state); TEST_ASSERT_FEQ(e, actor.ax, 7.0f, "right on set ax to %f, expected 7", actor.ax); // 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_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), "up on did not set FACE_UP (state %d)", actor.state); TEST_ASSERT_FEQ(e, actor.ay, -11.0f, "up on set ay to %f, expected -11", actor.ay); memset(&actor, 0x00, sizeof(akgl_Actor)); actor.basechar = &basechar; 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), "down on did not set FACE_DOWN (state %d)", actor.state); TEST_ASSERT_FEQ(e, actor.ay, 11.0f, "down on set ay to %f, expected 11", actor.ay); // 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_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_MOVING_LEFT), "turning right left MOVING_LEFT set (state %d)", actor.state); } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_actor_control_handlers_off(void) { PREPARE_ERROR(e); akgl_Actor actor; akgl_Character basechar; SDL_Event event; ATTEMPT { memset(&event, 0x00, sizeof(SDL_Event)); memset(&basechar, 0x00, sizeof(akgl_Character)); basechar.ax = 7.0f; basechar.ay = 11.0f; // Releasing a direction zeroes that axis outright: acceleration, thrust, // environmental force, and velocity all go to zero together. memset(&actor, 0x00, sizeof(akgl_Actor)); actor.basechar = &basechar; 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_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); TEST_ASSERT_FEQ(e, actor.vx, 0.0f, "left off left vx at %f", actor.vx); TEST_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_MOVING_LEFT), "left off did not clear MOVING_LEFT (state %d)", actor.state); // Facing is deliberately sticky, so an idle actor keeps looking where it was. TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_FACE_LEFT), "left off cleared FACE_LEFT, which should persist (state %d)", actor.state); // The Y axis is untouched by a horizontal release. TEST_ASSERT_FEQ(e, actor.vy, 5.0f, "left off disturbed vy (%f)", actor.vy); 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_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); 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_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); 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_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); } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_actor_control_handlers_nullpointers(void) { PREPARE_ERROR(e); akgl_Actor actor; SDL_Event event; ATTEMPT { 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(&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), "left on, actor with no base character"); 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), "up on, actor with no base character"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_down_on(&actor, &event), "down on, actor with no base character"); } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_actor_automatic_face(void) { PREPARE_ERROR(e); akgl_Actor actor; ATTEMPT { // With the flag off, facing is left entirely to the caller. memset(&actor, 0x00, sizeof(akgl_Actor)); actor.movement_controls_face = false; actor.state = (AKGL_ACTOR_STATE_MOVING_LEFT | AKGL_ACTOR_STATE_FACE_RIGHT); TEST_EXPECT_OK(e, akgl_actor_automatic_face(&actor), "automatic face with the flag off"); TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_FACE_RIGHT), "automatic face changed facing while disabled (state %d)", actor.state); // With the flag on, facing follows movement. memset(&actor, 0x00, sizeof(akgl_Actor)); actor.movement_controls_face = true; actor.state = (AKGL_ACTOR_STATE_MOVING_LEFT | AKGL_ACTOR_STATE_FACE_RIGHT); TEST_EXPECT_OK(e, akgl_actor_automatic_face(&actor), "automatic face while moving left"); TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_FACE_LEFT), "automatic face did not turn the actor left (state %d)", actor.state); TEST_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_FACE_RIGHT), "automatic face left the stale facing set (state %d)", actor.state); memset(&actor, 0x00, sizeof(akgl_Actor)); actor.movement_controls_face = true; actor.state = AKGL_ACTOR_STATE_MOVING_RIGHT; TEST_EXPECT_OK(e, akgl_actor_automatic_face(&actor), "automatic face while moving right"); TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_FACE_RIGHT), "automatic face did not turn the actor right (state %d)", actor.state); memset(&actor, 0x00, sizeof(akgl_Actor)); actor.movement_controls_face = true; actor.state = AKGL_ACTOR_STATE_MOVING_UP; TEST_EXPECT_OK(e, akgl_actor_automatic_face(&actor), "automatic face while moving up"); TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_FACE_UP), "automatic face did not turn the actor up (state %d)", actor.state); memset(&actor, 0x00, sizeof(akgl_Actor)); actor.movement_controls_face = true; actor.state = AKGL_ACTOR_STATE_MOVING_DOWN; TEST_EXPECT_OK(e, akgl_actor_automatic_face(&actor), "automatic face while moving down"); TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_FACE_DOWN), "automatic face did not turn the actor down (state %d)", actor.state); // Left wins over up when both are set, per the order of the checks. memset(&actor, 0x00, sizeof(akgl_Actor)); actor.movement_controls_face = true; actor.state = (AKGL_ACTOR_STATE_MOVING_LEFT | AKGL_ACTOR_STATE_MOVING_UP); TEST_EXPECT_OK(e, akgl_actor_automatic_face(&actor), "automatic face while moving diagonally"); TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_FACE_LEFT), "diagonal movement did not resolve to the left facing (state %d)", actor.state); TEST_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_FACE_UP), "diagonal movement set two facings at once (state %d)", actor.state); // A stationary actor ends up facing nowhere. memset(&actor, 0x00, sizeof(akgl_Actor)); actor.movement_controls_face = true; actor.state = (AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_FACE_UP); TEST_EXPECT_OK(e, akgl_actor_automatic_face(&actor), "automatic face while stationary"); TEST_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_FACE_ALL), "a stationary actor kept a facing bit (state %d)", actor.state); TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_ALIVE), "automatic face cleared an unrelated state bit (state %d)", actor.state); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_automatic_face(NULL), "automatic face with a NULL actor"); } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_actor_logic_movement(void) { PREPARE_ERROR(e); akgl_Actor actor; akgl_Character basechar; ATTEMPT { memset(&basechar, 0x00, sizeof(akgl_Character)); basechar.ax = 3.0f; basechar.ay = 4.0f; basechar.sx = 30.0f; basechar.sy = 40.0f; basechar.sz = 50.0f; // Max speed is always refreshed from the base character. memset(&actor, 0x00, sizeof(akgl_Actor)); actor.basechar = &basechar; TEST_EXPECT_OK(e, akgl_actor_logic_movement(&actor, 1.0f), "movement logic while idle"); TEST_ASSERT_FEQ(e, actor.sx, 30.0f, "sx copied as %f, expected 30", actor.sx); TEST_ASSERT_FEQ(e, actor.sy, 40.0f, "sy copied as %f, expected 40", actor.sy); TEST_ASSERT_FEQ(e, actor.sz, 50.0f, "sz copied as %f, expected 50", actor.sz); actor.state = AKGL_ACTOR_STATE_MOVING_LEFT; TEST_EXPECT_OK(e, akgl_actor_logic_movement(&actor, 1.0f), "movement logic while moving left"); TEST_ASSERT_FEQ(e, actor.ax, -3.0f, "moving left set ax to %f, expected -3", actor.ax); actor.state = AKGL_ACTOR_STATE_MOVING_RIGHT; TEST_EXPECT_OK(e, akgl_actor_logic_movement(&actor, 1.0f), "movement logic while moving right"); TEST_ASSERT_FEQ(e, actor.ax, 3.0f, "moving right set ax to %f, expected 3", actor.ax); actor.state = AKGL_ACTOR_STATE_MOVING_UP; TEST_EXPECT_OK(e, akgl_actor_logic_movement(&actor, 1.0f), "movement logic while moving up"); TEST_ASSERT_FEQ(e, actor.ay, -4.0f, "moving up set ay to %f, expected -4", actor.ay); actor.state = AKGL_ACTOR_STATE_MOVING_DOWN; TEST_EXPECT_OK(e, akgl_actor_logic_movement(&actor, 1.0f), "movement logic while moving down"); TEST_ASSERT_FEQ(e, actor.ay, 4.0f, "moving down set ay to %f, expected 4", actor.ay); // Both axes at once. actor.state = (AKGL_ACTOR_STATE_MOVING_LEFT | AKGL_ACTOR_STATE_MOVING_DOWN); TEST_EXPECT_OK(e, akgl_actor_logic_movement(&actor, 1.0f), "movement logic while moving diagonally"); TEST_ASSERT_FEQ(e, actor.ax, -3.0f, "diagonal movement set ax to %f, expected -3", actor.ax); TEST_ASSERT_FEQ(e, actor.ay, 4.0f, "diagonal movement set ay to %f, expected 4", actor.ay); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_logic_movement(NULL, 1.0f), "movement logic with a NULL actor"); // Every value the movement logic writes is read off the base character, // so an actor without one has to be reported rather than dereferenced. memset(&actor, 0x00, sizeof(akgl_Actor)); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_logic_movement(&actor, 1.0f), "movement logic with no base character"); } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_actor_logic_changeframe(void) { PREPARE_ERROR(e); akgl_Actor actor; akgl_Sprite sprite; ATTEMPT { memset(&sprite, 0x00, sizeof(akgl_Sprite)); sprite.frames = 4; // Mid-animation, the frame simply advances. memset(&actor, 0x00, sizeof(akgl_Actor)); actor.curSpriteFrameId = 1; TEST_EXPECT_OK(e, akgl_actor_logic_changeframe(&actor, &sprite, 0), "advancing mid-animation"); TEST_ASSERT(e, actor.curSpriteFrameId == 2, "frame advanced to %d, expected 2", actor.curSpriteFrameId); // At the last frame without looping, it wraps to the start. memset(&actor, 0x00, sizeof(akgl_Actor)); actor.curSpriteFrameId = 3; sprite.loop = false; sprite.loopReverse = false; TEST_EXPECT_OK(e, akgl_actor_logic_changeframe(&actor, &sprite, 0), "wrapping a non-looping sprite"); TEST_ASSERT(e, actor.curSpriteFrameId == 0, "the last frame wrapped to %d, expected 0", actor.curSpriteFrameId); // A forward-looping sprite behaves the same way at the end. memset(&actor, 0x00, sizeof(akgl_Actor)); actor.curSpriteFrameId = 3; sprite.loop = true; sprite.loopReverse = false; TEST_EXPECT_OK(e, akgl_actor_logic_changeframe(&actor, &sprite, 0), "wrapping a forward-looping sprite"); TEST_ASSERT(e, actor.curSpriteFrameId == 0, "the forward loop wrapped to %d, expected 0", actor.curSpriteFrameId); // A ping-pong sprite turns around at the end instead of wrapping. memset(&actor, 0x00, sizeof(akgl_Actor)); actor.curSpriteFrameId = 3; sprite.loop = true; sprite.loopReverse = true; TEST_EXPECT_OK(e, akgl_actor_logic_changeframe(&actor, &sprite, 0), "reversing at the end"); TEST_ASSERT(e, actor.curSpriteReversing == true, "the sprite did not enter its reverse phase at the last frame"); TEST_ASSERT(e, actor.curSpriteFrameId == 2, "reversing stepped to frame %d, expected 2", actor.curSpriteFrameId); // While reversing, the frame counts back down. TEST_EXPECT_OK(e, akgl_actor_logic_changeframe(&actor, &sprite, 0), "stepping back while reversing"); TEST_ASSERT(e, actor.curSpriteFrameId == 1, "reversing stepped to frame %d, expected 1", actor.curSpriteFrameId); // At frame zero it turns around again and resumes going forward. actor.curSpriteFrameId = 0; actor.curSpriteReversing = true; TEST_EXPECT_OK(e, akgl_actor_logic_changeframe(&actor, &sprite, 0), "turning around at frame zero"); TEST_ASSERT(e, actor.curSpriteReversing == false, "the sprite stayed in its reverse phase at frame zero"); TEST_ASSERT(e, actor.curSpriteFrameId == 1, "turning around stepped to frame %d, expected 1", actor.curSpriteFrameId); // A single-frame sprite has nowhere to advance to. memset(&actor, 0x00, sizeof(akgl_Actor)); sprite.frames = 1; sprite.loop = false; sprite.loopReverse = false; TEST_EXPECT_OK(e, akgl_actor_logic_changeframe(&actor, &sprite, 0), "advancing a single-frame sprite"); TEST_ASSERT(e, actor.curSpriteFrameId == 0, "a single-frame sprite moved to frame %d, expected 0", actor.curSpriteFrameId); sprite.frames = 4; TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_logic_changeframe(NULL, &sprite, 0), "changeframe with a NULL actor"); } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } /** @brief Records calls made to the changeframe stub. */ static int changeframe_calls = 0; /** @brief Changeframe stub that records its invocation without advancing. */ static akerr_ErrorContext *stub_changeframe(akgl_Actor *obj, akgl_Sprite *curSprite, SDL_Time curtimems) { PREPARE_ERROR(e); changeframe_calls += 1; SUCCEED_RETURN(e); } akerr_ErrorContext *test_actor_update(void) { PREPARE_ERROR(e); akgl_Actor *actor = NULL; akgl_Character *basechar = NULL; akgl_Sprite *sprite = NULL; SDL_Time now = 0; ATTEMPT { CATCH(e, akgl_registry_init_actor()); CATCH(e, akgl_registry_init_sprite()); CATCH(e, akgl_registry_init_spritesheet()); CATCH(e, akgl_registry_init_character()); CATCH(e, akgl_heap_init()); CATCH(e, make_bound_actor(&actor, &basechar, &sprite, "updatable", AKGL_ACTOR_STATE_ALIVE)); actor->changeframefunc = &stub_changeframe; // Long enough since the last frame change, so the sprite advances. SDL_GetCurrentTime(&now); sprite->speed = 1; actor->curSpriteFrameTimer = 0; changeframe_calls = 0; TEST_EXPECT_OK(e, akgl_actor_update(actor), "updating an actor whose frame is due"); TEST_ASSERT(e, changeframe_calls == 1, "a due frame change fired %d times, expected 1", changeframe_calls); TEST_ASSERT(e, actor->curSpriteFrameTimer != 0, "the frame timer was not restamped after a frame change"); // Too soon since the last change, so nothing happens. SDL_GetCurrentTime(&now); sprite->speed = 1000000000; actor->curSpriteFrameTimer = now; changeframe_calls = 0; TEST_EXPECT_OK(e, akgl_actor_update(actor), "updating an actor whose frame is not due"); TEST_ASSERT(e, changeframe_calls == 0, "an early frame change fired %d times, expected 0", changeframe_calls); // An actor in a state with no sprite bound is skipped, not failed: the // missing binding is reported as AKERR_KEY and swallowed by update. actor->state = AKGL_ACTOR_STATE_DEAD; changeframe_calls = 0; TEST_EXPECT_OK(e, akgl_actor_update(actor), "updating an actor whose state has no sprite"); TEST_ASSERT(e, changeframe_calls == 0, "an actor with no sprite for its state still changed frames"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_update(NULL), "updating a NULL actor"); // An actor with no base character cannot resolve a sprite at all. actor->basechar = NULL; TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_update(actor), "updating an actor with no base character"); } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_actor_character_sprite_binding(void) { PREPARE_ERROR(e); akgl_Actor *actor = NULL; akgl_Character *basechar = NULL; akgl_Sprite *sprite = NULL; akgl_Sprite *found = NULL; ATTEMPT { CATCH(e, akgl_registry_init_actor()); CATCH(e, akgl_registry_init_sprite()); CATCH(e, akgl_registry_init_spritesheet()); CATCH(e, akgl_registry_init_character()); CATCH(e, akgl_heap_init()); CATCH(e, make_bound_actor(&actor, &basechar, &sprite, "bound", AKGL_ACTOR_STATE_ALIVE)); TEST_EXPECT_OK(e, akgl_character_sprite_get(basechar, AKGL_ACTOR_STATE_ALIVE, &found), "reading back a bound sprite"); TEST_ASSERT(e, found == sprite, "the bound sprite came back as a different object"); // A composite state is a distinct key from either of its components. TEST_EXPECT_STATUS(e, AKERR_KEY, akgl_character_sprite_get(basechar, (AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_FACE_UP), &found), "reading a composite state that was never bound"); TEST_EXPECT_STATUS(e, AKERR_KEY, akgl_character_sprite_get(basechar, 0, &found), "reading state zero, which was never bound"); // Binding a composite state makes it resolvable. TEST_EXPECT_OK(e, akgl_character_sprite_add(basechar, sprite, (AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_FACE_UP)), "binding a sprite to a composite state"); TEST_EXPECT_OK(e, akgl_character_sprite_get(basechar, (AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_FACE_UP), &found), "reading back the composite binding"); TEST_ASSERT(e, found == sprite, "the composite binding resolved to a different sprite"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_character_sprite_get(NULL, 0, &found), "sprite_get with a NULL character"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_character_sprite_get(basechar, AKGL_ACTOR_STATE_ALIVE, NULL), "sprite_get with a NULL destination"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_character_sprite_add(NULL, sprite, 0), "sprite_add with a NULL character"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_character_sprite_add(basechar, NULL, 0), "sprite_add with a NULL sprite"); } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_actor_sprite_sheet_coords(void) { PREPARE_ERROR(e); akgl_Sprite sprite; SDL_FRect coords; ATTEMPT { memset(&sprite, 0x00, sizeof(akgl_Sprite)); sprite.width = 32; sprite.height = 32; 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_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_spritesheet_coords_for_frame(&sprite, &coords, 0), "sheet coords with a NULL spritesheet"); } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } /* * A render backend whose draw_texture records what it was asked to draw instead * of drawing it. akgl_actor_render's whole output is the rectangle it hands to * draw_texture, so that rectangle is the only thing there is to assert on. */ static akgl_RenderBackend testbackend; static SDL_FRect lastdrawdest; static SDL_FRect lastdrawsrc; static int drawcalls = 0; static akerr_ErrorContext *record_draw_texture( akgl_RenderBackend *self, SDL_Texture *texture, SDL_FRect *src, SDL_FRect *dest, double angle, SDL_FPoint *center, SDL_FlipMode flip) { PREPARE_ERROR(errctx); if ( src != NULL ) { lastdrawsrc = *src; } if ( dest != NULL ) { lastdrawdest = *dest; } drawcalls += 1; SUCCEED_RETURN(errctx); } /** * @brief akgl_actor_render must take the drawn height from the sprite's height. * * It read `curSprite->width` for both, so every actor was drawn square and a * non-square sprite was stretched or squashed. Invisible in the fixtures * because they are square, which is exactly why this test uses a sprite that * is not: 48 wide by 24 high, so a regression puts 48 in both. * * This is also the first test of akgl_actor_render at all -- everything else in * this file stubs renderfunc out. */ akerr_ErrorContext *test_actor_render_uses_sprite_height(void) { PREPARE_ERROR(e); akgl_Actor *actor = NULL; akgl_Character *basechar = NULL; akgl_Sprite *sprite = NULL; SDL_Texture *sheettexture = NULL; ATTEMPT { CATCH(e, make_bound_actor(&actor, &basechar, &sprite, "renderactor", AKGL_ACTOR_STATE_ALIVE)); // akgl_spritesheet_coords_for_frame reads sheet->texture->w, so the // sheet needs a real one even though nothing is rasterized. sheettexture = SDL_CreateTexture( testbackend.sdl_renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 96, 48); FAIL_ZERO_BREAK(e, sheettexture, AKGL_ERR_SDL, "%s", SDL_GetError()); sprite->sheet->texture = sheettexture; sprite->width = 48; sprite->height = 24; sprite->frames = 2; actor->curSpriteFrameId = 0; actor->visible = true; actor->scale = 1.0f; actor->x = 0; actor->y = 0; akgl_camera->x = 0; akgl_camera->y = 0; akgl_camera->w = 640; akgl_camera->h = 480; drawcalls = 0; TEST_EXPECT_OK(e, akgl_actor_render(actor), "rendering a non-square actor"); TEST_ASSERT(e, drawcalls == 1, "actor render made %d draw calls, expected 1", drawcalls); TEST_ASSERT_FEQ(e, lastdrawdest.w, 48.0f, "drawn width is %f, expected the sprite's 48", lastdrawdest.w); TEST_ASSERT_FEQ(e, lastdrawdest.h, 24.0f, "drawn height is %f, expected the sprite's 24 -- it used to take the width", lastdrawdest.h); // The source rectangle has always come from the sprite properly; assert // it too, so a regression cannot move the bug one line up. TEST_ASSERT_FEQ(e, lastdrawsrc.w, 48.0f, "source width is %f, expected 48", lastdrawsrc.w); TEST_ASSERT_FEQ(e, lastdrawsrc.h, 24.0f, "source height is %f, expected 24", lastdrawsrc.h); // Scale multiplies both, and must not collapse them back together. actor->scale = 2.0f; drawcalls = 0; TEST_EXPECT_OK(e, akgl_actor_render(actor), "rendering a scaled non-square actor"); TEST_ASSERT_FEQ(e, lastdrawdest.w, 96.0f, "scaled width is %f, expected 96", lastdrawdest.w); TEST_ASSERT_FEQ(e, lastdrawdest.h, 48.0f, "scaled height is %f, expected 48", lastdrawdest.h); } CLEANUP { if ( sprite != NULL && sprite->sheet != NULL ) { sprite->sheet->texture = NULL; } if ( sheettexture != NULL ) { SDL_DestroyTexture(sheettexture); } if ( actor != NULL ) { IGNORE(akgl_heap_release_actor(actor)); } if ( basechar != NULL ) { IGNORE(akgl_heap_release_character(basechar)); } } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } int main(void) { akgl_actor_updated = 0; akgl_actor_rendered = 0; UNHANDLED_ERROR_BEHAVIOR = UNHANDLED_ERROR_EXIT; PREPARE_ERROR(errctx); SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "dummy"); SDL_SetHint(SDL_HINT_AUDIO_DRIVER, "dummy"); 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()); CATCH(errctx, akgl_registry_init_character()); // A renderer, so akgl_actor_render can be driven at all. The backend is // bound and then has its draw_texture replaced, which is the point: // what the function computes is only visible in what it hands over. if ( !SDL_Init(SDL_INIT_VIDEO) ) { FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't initialize SDL: %s", SDL_GetError()); } memset(&testbackend, 0x00, sizeof(akgl_RenderBackend)); if ( !SDL_CreateWindowAndRenderer("net/aklabs/libakgl/test_actor", 640, 480, SDL_WINDOW_HIDDEN, &akgl_window, &testbackend.sdl_renderer) ) { FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't create window/renderer: %s", SDL_GetError()); } CATCH(errctx, akgl_render_2d_bind(&testbackend)); testbackend.draw_texture = &record_draw_texture; akgl_renderer = &testbackend; akgl_camera = &akgl_default_camera; CATCH(errctx, test_registry_actor_iterator_nullpointers()); CATCH(errctx, test_registry_actor_iterator_missingactor()); CATCH(errctx, test_registry_actor_iterator_updaterender()); CATCH(errctx, test_akgl_actor_set_character()); CATCH(errctx, test_actor_manage_children()); CATCH(errctx, test_actor_control_handlers_on()); CATCH(errctx, test_actor_control_handlers_off()); CATCH(errctx, test_actor_control_handlers_nullpointers()); CATCH(errctx, test_actor_automatic_face()); CATCH(errctx, test_actor_logic_movement()); CATCH(errctx, test_actor_logic_changeframe()); CATCH(errctx, test_actor_update()); CATCH(errctx, test_actor_character_sprite_binding()); CATCH(errctx, test_actor_sprite_sheet_coords()); CATCH(errctx, test_actor_render_uses_sprite_height()); } CLEANUP { } PROCESS(errctx) { } FINISH_NORETURN(errctx); return 0; }