#define UNHANDLED_ERROR_TERMINATION_BEHAVIOR \ handle_unhandled_error(errctx); #include #define UNHANDLED_ERROR_EXIT 0 #define UNHANDLED_ERROR_SET 1 #include #include #include #include #include #include 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, SDL_Renderer *r) { 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, AKERR_BEHAVIOR, "actor->updatefunc not called by the iterator" ); FAIL_ZERO_BREAK( unhandled_error_context, akgl_actor_rendered, AKERR_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, AKERR_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, AKERR_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, HEAP_ACTOR[i].refcount, AKERR_VALUE, "Actor not properly cleared"); FAIL_NONZERO_BREAK(errctx, HEAP_ACTOR[i].parent, AKERR_VALUE, "Actor not properly cleared"); for ( j = 0 ; j < AKGL_ACTOR_MAX_CHILDREN; j++) { if ( HEAP_ACTOR[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); } int main(void) { akgl_actor_updated = 0; akgl_actor_rendered = 0; UNHANDLED_ERROR_BEHAVIOR = UNHANDLED_ERROR_EXIT; PREPARE_ERROR(errctx); ATTEMPT { CATCH(errctx, akgl_registry_init_actor()); CATCH(errctx, akgl_registry_init_sprite()); CATCH(errctx, akgl_registry_init_spritesheet()); CATCH(errctx, akgl_registry_init_character()); 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()); } CLEANUP { } PROCESS(errctx) { } FINISH_NORETURN(errctx); return 0; }