#include #include #include #include #include #include #include #include #include #include #include #include #include #include "testutil.h" akerr_ErrorContext *test_akgl_spritesheet_initialize(void) { PREPARE_ERROR(errctx); akgl_SpriteSheet *sheet = NULL; SDL_Texture *image = NULL; akgl_String *tmpstr = NULL; // Does the image file get loaded? // Is the image file loaded correctly? (Surface comparison) // Is the spritesheet in the registry? ATTEMPT { CATCH(errctx, akgl_heap_next_spritesheet(&sheet)); CATCH(errctx, akgl_heap_next_string(&tmpstr)); snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/spritesheet.png"); CATCH(errctx, akgl_spritesheet_initialize(sheet, 48, 48, "assets/spritesheet.png")); FAIL_ZERO_BREAK(errctx, sheet->texture, AKERR_VALUE, "akgl_spritesheet_initialize failed to load the sprite texture"); FAIL_NONZERO_BREAK( errctx, ((sheet->texture->w != 576) || (sheet->texture->h != 384)), AKERR_VALUE, "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(akgl_renderer->sdl_renderer, (char *)&tmpstr->data); FAIL_ZERO_BREAK(errctx, image, AKGL_ERR_SDL, "Failed to load comparison image"); CATCH( errctx, akgl_render_and_compare( sheet->texture, image, 0, 0, 576, 384, "test_spritesheet_loaded_image.png") ); FAIL_ZERO_BREAK( errctx, SDL_GetPointerProperty(AKGL_REGISTRY_SPRITESHEET, "assets/spritesheet.png", NULL), AKERR_KEY, "Spritesheet was not placed in the registry"); } CLEANUP { IGNORE(akgl_heap_release_string(tmpstr)); IGNORE(akgl_heap_release_spritesheet(sheet)); if ( image != NULL ) SDL_DestroyTexture(image); } PROCESS(errctx) { } FINISH(errctx, true); SUCCEED_RETURN(errctx); } akerr_ErrorContext *test_akgl_sprite_initialize(void) { PREPARE_ERROR(errctx); akgl_SpriteSheet *testsheet = NULL; akgl_Sprite *testsprite = NULL; akgl_String *tmpstr = NULL; // Does the sprite get loaded? // Do all frames of the sprite get loaded? // Are all the frames of the sprite what we expect? (Surface comparison) // Is the sprite added to the registry? ATTEMPT { CATCH(errctx, akgl_heap_next_spritesheet(&testsheet)); CATCH(errctx, akgl_heap_next_sprite(&testsprite)); CATCH(errctx, akgl_heap_next_string(&tmpstr)); snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/spritesheet.png"); CATCH(errctx, akgl_spritesheet_initialize(testsheet, 48, 48, "assets/spritesheet.png")); FAIL_ZERO_BREAK(errctx, testsheet, AKERR_VALUE, "akgl_spritesheet_initialize failed"); CATCH(errctx, akgl_sprite_initialize(testsprite, "test", testsheet)); FAIL_NONZERO_BREAK(errctx, (testsprite->sheet != testsheet), AKERR_VALUE, "Initialized sprite uses wrong sheet"); FAIL_ZERO_BREAK( errctx, SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "test", NULL), AKERR_KEY, "Sprite was not placed in the registry"); } CLEANUP { IGNORE(akgl_heap_release_sprite(testsprite)); IGNORE(akgl_heap_release_string(tmpstr)); } PROCESS(errctx) { } FINISH(errctx, true); SUCCEED_RETURN(errctx); } akerr_ErrorContext *test_akgl_sprite_load_json(void) { PREPARE_ERROR(errctx); akgl_Sprite *testsprite = NULL; akgl_Sprite *testsprite2 = NULL; akgl_String *tmpstr = NULL; SDL_Texture *image = NULL; // Does the sprite get loaded? // Do all frames of the sprite get loaded? // Are all the frames of the sprite what we expect? (Surface comparison) // Is the sprite added to the registry? ATTEMPT { CATCH(errctx, akgl_heap_next_string(&tmpstr)); CATCH(errctx, akgl_sprite_load_json("assets/testsprite.json")); testsprite = SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "testsprite", NULL); FAIL_ZERO_BREAK( errctx, testsprite, AKERR_KEY, "akgl_sprite_load_json succeeds but sprite is not placed in the registry"); FAIL_ZERO_BREAK(errctx, (testsprite->width == 48), AKERR_VALUE, "width incorrect (48 : %d)", testsprite->width); FAIL_ZERO_BREAK(errctx, (testsprite->height == 48), AKERR_VALUE, "height incorrect (48 : %d)", testsprite->height); FAIL_ZERO_BREAK(errctx, (testsprite->speed == 100000000), AKERR_VALUE, "speed incorrect (100 : %d)", testsprite->speed); FAIL_ZERO_BREAK(errctx, (testsprite->loop == true), AKERR_VALUE, "loop incorrect (1 : %d)", testsprite->loop); FAIL_ZERO_BREAK(errctx, (testsprite->loopReverse == true), AKERR_VALUE, "loopReverse incorrect (1 : %d)", testsprite->loopReverse); FAIL_ZERO_BREAK(errctx, (testsprite->frames == 3), AKERR_VALUE, "frame count incorrect (3 : %d)", testsprite->frames); FAIL_ZERO_BREAK(errctx, (testsprite->frameids[0] == 12), AKERR_VALUE, "frameids[0] incorrect (12 : %d)", testsprite->frameids[0]); FAIL_ZERO_BREAK(errctx, (testsprite->frameids[1] == 13), AKERR_VALUE, "frameids[1] incorrect (13 : %d)", testsprite->frameids[1]); FAIL_ZERO_BREAK(errctx, (testsprite->frameids[2] == 14), AKERR_VALUE, "frameids[2] incorrect (14 : %d)", testsprite->frameids[2]); FAIL_NONZERO_BREAK(errctx, strcmp(testsprite->name, "testsprite"), AKERR_VALUE, "name incorrect (testsprite : %s)", (char *)testsprite->name); // Is it using the right spritesheet? snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/spritesheet.png"); image = IMG_LoadTexture(akgl_renderer->sdl_renderer, (char *)&tmpstr->data); FAIL_ZERO_BREAK(errctx, image, AKGL_ERR_SDL, "Failed to load comparison image"); CATCH( errctx, akgl_render_and_compare( testsprite->sheet->texture, image, 0, 0, 576, 384, "test_sprite_loaded_from_json_sheet.png" ) ); // If we load a second sprite using the same sheet name, do they use the same sheet in memory? snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testsprite2.json"); CATCH(errctx, akgl_sprite_load_json("assets/testsprite2.json")); testsprite2 = SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "testsprite2", NULL); FAIL_ZERO_BREAK( errctx, testsprite, AKERR_KEY, "akgl_sprite_load_json succeeds but second sprite is not placed in the registry"); FAIL_ZERO_BREAK( errctx, (testsprite->sheet == testsprite2->sheet), AKERR_VALUE, "Previously loaded spritesheets are not reused"); } CLEANUP { if ( testsprite != NULL ) { IGNORE(akgl_heap_release_sprite(testsprite)); } if ( testsprite2 != NULL ) { IGNORE(akgl_heap_release_sprite(testsprite2)); } IGNORE(akgl_heap_release_string(tmpstr)); if ( image != NULL ) SDL_DestroyTexture(image); } PROCESS(errctx) { } FINISH(errctx, true); SUCCEED_RETURN(errctx); } /** * @brief A sprite definition must not be able to write past `frameids`. * * `frameids` is AKGL_SPRITE_MAX_FRAMES bytes and the loader took its count * straight from the document, so a definition with seventeen frames wrote past * the array, past the rest of akgl_Sprite, and into the next pool slot. It also * wrote through a `uint32_t *` cast of a `uint8_t *`, four bytes at a time, * which is why the overrun reached four bytes past the array rather than one. * * The neighbouring slot is claimed and stamped first, so the test fails on the * corruption rather than on whatever the corruption happens to do later. */ akerr_ErrorContext *test_akgl_sprite_load_json_bounds_frames(void) { PREPARE_ERROR(errctx); akgl_Sprite *loaded = NULL; int i = 0; ATTEMPT { // Exactly the maximum is legal and must still load. TEST_EXPECT_OK(errctx, akgl_sprite_load_json("assets/testsprite_maxframes.json"), "loading a sprite with exactly AKGL_SPRITE_MAX_FRAMES frames"); loaded = SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "testsprite_maxframes", NULL); FAIL_ZERO_BREAK(errctx, loaded, AKERR_KEY, "the max-frames sprite is not in the registry"); TEST_ASSERT(errctx, loaded->frames == AKGL_SPRITE_MAX_FRAMES, "max-frames sprite loaded %d frames, expected %d", loaded->frames, AKGL_SPRITE_MAX_FRAMES); for ( i = 0; i < AKGL_SPRITE_MAX_FRAMES; i++ ) { TEST_ASSERT(errctx, loaded->frameids[i] == (uint8_t)i, "max-frames sprite frame %d is %d, expected %d", i, loaded->frameids[i], i); } // One more than the maximum is refused, and nothing is registered. TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS, akgl_sprite_load_json("assets/testsprite_toomanyframes.json"), "loading a sprite with more frames than the array holds"); TEST_ASSERT(errctx, SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "testsprite_toomanyframes", NULL) == NULL, "a sprite with too many frames was registered anyway"); // A frame number a uint8_t cannot hold is refused rather than truncated // to something that indexes a different tile. TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS, akgl_sprite_load_json("assets/testsprite_widecount.json"), "loading a sprite whose frame number does not fit a uint8_t"); } CLEANUP { } PROCESS(errctx) { } FINISH(errctx, true); SUCCEED_RETURN(errctx); } 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_sprite", 640, 480, 0, &akgl_window, &akgl_renderer->sdl_renderer)) { FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't create window/renderer: %s", SDL_GetError()); } akgl_renderer->draw_texture = &akgl_render_2d_draw_texture; CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_registry_init_sprite()); CATCH(errctx, akgl_registry_init_spritesheet()); CATCH(errctx, test_akgl_spritesheet_initialize()); CATCH(errctx, test_akgl_sprite_initialize()); CATCH(errctx, test_akgl_sprite_load_json()); CATCH(errctx, test_akgl_sprite_load_json_bounds_frames()); } CLEANUP { } PROCESS(errctx) { } FINISH_NORETURN(errctx); return 0; }