#include #include #include #include #include #include #include #include #include "testutil.h" typedef akerr_ErrorContext *(*RegistryFuncPtr)(void); void *sdl_calloc_always_fails(size_t a, size_t b) { // This forces SDL to simulate an out of memory condition return NULL; } akerr_ErrorContext *test_akgl_registry_init(RegistryFuncPtr funcptr) { SDL_malloc_func malloc_func; SDL_calloc_func calloc_func; SDL_realloc_func realloc_func; SDL_free_func free_func; SDL_GetMemoryFunctions( &malloc_func, &calloc_func, &realloc_func, &free_func ); PREPARE_ERROR(errctx); ATTEMPT { SDL_SetMemoryFunctions( malloc_func, (SDL_calloc_func)&sdl_calloc_always_fails, realloc_func, free_func ); CATCH(errctx, funcptr()); } CLEANUP { SDL_SetMemoryFunctions( malloc_func, calloc_func, realloc_func, free_func ); } PROCESS(errctx) { } FINISH(errctx, true); FAIL_RETURN(errctx, AKGL_ERR_BEHAVIOR, "SDL memory allocator fails but registry reports successful property creation"); } akerr_ErrorContext *test_akgl_registry_init_creation_failures(void) { PREPARE_ERROR(errctx); ATTEMPT { CATCH(errctx, test_akgl_registry_init(&akgl_registry_init_actor)); } CLEANUP { } PROCESS(errctx) { } HANDLE(errctx, AKERR_NULLPOINTER) { printf("Sucess\n"); } FINISH(errctx, true); ATTEMPT { CATCH(errctx, test_akgl_registry_init(&akgl_registry_init_sprite)); } CLEANUP { } PROCESS(errctx) { } HANDLE(errctx, AKERR_NULLPOINTER) { printf("Sucess\n"); } FINISH(errctx, true); ATTEMPT { CATCH(errctx, test_akgl_registry_init(&akgl_registry_init_spritesheet)); } CLEANUP { } PROCESS(errctx) { } HANDLE(errctx, AKERR_NULLPOINTER) { printf("Sucess\n"); } FINISH(errctx, true); ATTEMPT { CATCH(errctx, test_akgl_registry_init(&akgl_registry_init_character)); } CLEANUP { } PROCESS(errctx) { } HANDLE(errctx, AKERR_NULLPOINTER) { printf("Sucess\n"); } FINISH(errctx, true); SUCCEED_RETURN(errctx); } /** * @brief akgl_get_property must copy the value, and not a byte more. * * It used to copy a fixed AKGL_MAX_STRING_LENGTH bytes out of whatever SDL * returned, and what SDL returns is its own strdup of the value -- four bytes * for "0.0". That read up to 4 KiB past the end of somebody else's allocation on * every call, which valgrind reported twelve times over in tests/physics.c * alone. * * The destination is filled with a sentinel first, so the assertion is not * "the value arrived" -- that would pass either way -- but "the bytes past the * terminator were left alone", which is only true if the copy was bounded by * the source. */ akerr_ErrorContext *test_akgl_get_property_copies_only_the_value(void) { PREPARE_ERROR(errctx); akgl_String *value = NULL; char oversized[AKGL_MAX_STRING_LENGTH + 8]; size_t valuelen = 0; size_t i = 0; bool untouched = true; ATTEMPT { CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_registry_init_properties()); CATCH(errctx, akgl_heap_next_string(&value)); memset(&value->data, 0x5a, AKGL_MAX_STRING_LENGTH); CATCH(errctx, akgl_set_property("registry.short", "0.0")); CATCH(errctx, akgl_get_property("registry.short", &value, "unset")); valuelen = strlen("0.0"); TEST_ASSERT(errctx, strcmp(value->data, "0.0") == 0, "akgl_get_property returned \"%s\", expected \"0.0\"", value->data); for ( i = (valuelen + 1); i < AKGL_MAX_STRING_LENGTH; i++ ) { TEST_ASSERT_FLAG(untouched, (value->data[i] == 0x5a)); } TEST_ASSERT(errctx, untouched, "akgl_get_property wrote past the end of a %zu byte value", valuelen); // The default takes the same path as a stored value. CATCH(errctx, akgl_get_property("registry.absent", &value, "fallback")); TEST_ASSERT(errctx, strcmp(value->data, "fallback") == 0, "akgl_get_property returned \"%s\" for an unset property, expected the default", value->data); // A value too long for an akgl_String is refused rather than truncated // into an unterminated buffer. memset(&oversized, 'x', sizeof(oversized)); oversized[AKGL_MAX_STRING_LENGTH + 7] = '\0'; CATCH(errctx, akgl_set_property("registry.oversized", (char *)&oversized)); TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS, akgl_get_property("registry.oversized", &value, "unset"), "reading a property longer than an akgl_String"); // And the documented refusal survives: unset, with no default. TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_get_property("registry.absent", &value, NULL), "reading an unset property with no default"); } CLEANUP { if ( value != NULL ) { IGNORE(akgl_heap_release_string(value)); } } PROCESS(errctx) { } FINISH(errctx, true); SUCCEED_RETURN(errctx); } 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 { } PROCESS(errctx) { } FINISH_NORETURN(errctx); }