From 8b2b4b7c3dd2cf1edad3877ef7b65f2b67e6605e Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Mon, 1 Jun 2026 15:31:22 -0500 Subject: [PATCH] Fixed JSON loader, added some tests --- src/dusk/asset/loader/assetloader.h | 9 +- src/dusk/asset/loader/json/assetjsonloader.c | 8 +- src/dusk/asset/loader/json/assetjsonloader.h | 1 + test/asset/CMakeLists.txt | 2 + test/asset/test_assetjsonloader.c | 222 ++++++++++++ test/asset/test_assettilesetloader.c | 358 +++++++++++++++++++ 6 files changed, 593 insertions(+), 7 deletions(-) create mode 100644 test/asset/test_assetjsonloader.c create mode 100644 test/asset/test_assettilesetloader.c diff --git a/src/dusk/asset/loader/assetloader.h b/src/dusk/asset/loader/assetloader.h index 77b98978..8324581d 100644 --- a/src/dusk/asset/loader/assetloader.h +++ b/src/dusk/asset/loader/assetloader.h @@ -70,10 +70,11 @@ extern assetloadercallbacks_t ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_COUNT]; * @param loading The asset loading slot. * @param ret The error return value to check and chain if it's an error. */ -#define assetLoaderErrorChain(loading, ret) {\ - if(errorIsNotOk(ret)) { \ - loading->entry->state = ASSET_ENTRY_STATE_ERROR; \ - errorChain(ret); \ +#define assetLoaderErrorChain(loading, _expr) {\ + errorret_t _alec = (_expr); \ + if(errorIsNotOk(_alec)) { \ + (loading)->entry->state = ASSET_ENTRY_STATE_ERROR; \ + errorChain(_alec); \ } \ } diff --git a/src/dusk/asset/loader/json/assetjsonloader.c b/src/dusk/asset/loader/json/assetjsonloader.c index 8ff8e501..83593d3b 100644 --- a/src/dusk/asset/loader/json/assetjsonloader.c +++ b/src/dusk/asset/loader/json/assetjsonloader.c @@ -27,14 +27,16 @@ errorret_t assetJsonLoaderAsync(assetloading_t *loading) { assetLoaderErrorThrow(loading, "JSON exceeds maximum allowed size"); } - uint8_t *buffer = memoryAllocate(file->size); + size_t fileSize = (size_t)file->size; + uint8_t *buffer = memoryAllocate(fileSize); assetLoaderErrorChain(loading, assetFileOpen(file)); - assetLoaderErrorChain(loading, assetFileRead(file, buffer, file->size)); + assetLoaderErrorChain(loading, assetFileRead(file, buffer, fileSize)); assertTrue(file->lastRead == file->size, "Failed to read entire JSON file."); assetLoaderErrorChain(loading, assetFileClose(file)); assetLoaderErrorChain(loading, assetFileDispose(file)); loading->loading.json.buffer = buffer; + loading->loading.json.size = fileSize; loading->loading.json.state = ASSET_JSON_LOADING_STATE_PARSE; loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC; errorOk(); @@ -63,7 +65,7 @@ errorret_t assetJsonLoaderSync(assetloading_t *loading) { loading->entry->data.json = yyjson_read( (char *)buffer, - loading->loading.json.file.size, + loading->loading.json.size, YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS ); memoryFree(buffer); diff --git a/src/dusk/asset/loader/json/assetjsonloader.h b/src/dusk/asset/loader/json/assetjsonloader.h index cb0f5d73..4508064d 100644 --- a/src/dusk/asset/loader/json/assetjsonloader.h +++ b/src/dusk/asset/loader/json/assetjsonloader.h @@ -27,6 +27,7 @@ typedef struct { assetfile_t file; assetjsonloadingstate_t state; uint8_t *buffer; + size_t size; } assetjsonloaderloading_t; typedef yyjson_doc * assetjsonoutput_t; diff --git a/test/asset/CMakeLists.txt b/test/asset/CMakeLists.txt index 8439d153..f0f5c424 100644 --- a/test/asset/CMakeLists.txt +++ b/test/asset/CMakeLists.txt @@ -7,3 +7,5 @@ include(dusktest) dusktest(test_assetlocale.c) dusktest(test_asset.c) +dusktest(test_assetjsonloader.c) +dusktest(test_assettilesetloader.c) diff --git a/test/asset/test_assetjsonloader.c b/test/asset/test_assetjsonloader.c new file mode 100644 index 00000000..49a75c38 --- /dev/null +++ b/test/asset/test_assetjsonloader.c @@ -0,0 +1,222 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "dusktest.h" +#include "asset/asset.h" +#include "asset/loader/assetloader.h" +#include "asset/loader/assetentry.h" +#include "asset/loader/json/assetjsonloader.h" +#include "util/memory.h" +#include + +// ============================================================ +// Fixtures +// ============================================================ + +static const char_t *JSON_VALID = "{\"hello\":\"world\",\"count\":42}"; +static const char_t *JSON_INVALID = "{ this is definitely not valid json !!!"; + +// ============================================================ +// In-memory ZIP +// ============================================================ + +static zip_t *g_zip = NULL; + +static int zip_setup(void **state) { + zip_error_t err; + zip_error_init(&err); + + zip_source_t *write_src = zip_source_buffer_create(NULL, 0, 1, &err); + if(!write_src) return -1; + + zip_t *za = zip_open_from_source(write_src, ZIP_TRUNCATE, &err); + if(!za) { zip_source_free(write_src); return -1; } + + // valid.json + zip_source_t *s; + s = zip_source_buffer(za, JSON_VALID, strlen(JSON_VALID), 0); + if(zip_file_add(za, "valid.json", s, ZIP_FL_OVERWRITE) < 0) { + zip_close(za); return -1; + } + + // invalid.json + s = zip_source_buffer(za, JSON_INVALID, strlen(JSON_INVALID), 0); + if(zip_file_add(za, "invalid.json", s, ZIP_FL_OVERWRITE) < 0) { + zip_close(za); return -1; + } + + zip_source_keep(write_src); + if(zip_close(za) != 0) { zip_source_free(write_src); return -1; } + + zip_stat_t zs; + memset(&zs, 0, sizeof(zs)); + if(zip_source_stat(write_src, &zs) != 0 || !(zs.valid & ZIP_STAT_SIZE)) { + zip_source_free(write_src); return -1; + } + + void *zipbuf = malloc((size_t)zs.size); + if(!zipbuf) { zip_source_free(write_src); return -1; } + + if(zip_source_open(write_src) != 0) { + free(zipbuf); zip_source_free(write_src); return -1; + } + zip_source_read(write_src, zipbuf, (zip_uint64_t)zs.size); + zip_source_close(write_src); + zip_source_free(write_src); + + zip_error_init(&err); + zip_source_t *read_src = zip_source_buffer_create( + zipbuf, (zip_uint64_t)zs.size, 1, &err + ); + if(!read_src) { free(zipbuf); return -1; } + + g_zip = zip_open_from_source(read_src, 0, &err); + if(!g_zip) { zip_source_free(read_src); return -1; } + + ASSET.zip = g_zip; + return 0; +} + +static int zip_teardown(void **state) { + if(g_zip) { zip_close(g_zip); g_zip = NULL; } + ASSET.zip = NULL; + return 0; +} + +// ============================================================ +// Loader pipeline helper +// ============================================================ + +typedef struct { + assetentry_t entry; + assetloading_t loading; +} loader_ctx_t; + +static void loader_ctx_init(loader_ctx_t *ctx, const char_t *name) { + assetEntryInit(&ctx->entry, name, ASSET_LOADER_TYPE_JSON, NULL); + threadMutexInit(&ctx->loading.mutex); + memoryZero(&ctx->loading.loading, sizeof(ctx->loading.loading)); + ctx->loading.type = ASSET_LOADER_TYPE_JSON; + ctx->loading.entry = &ctx->entry; + ctx->entry.state = ASSET_ENTRY_STATE_PENDING_SYNC; +} + +// Drives sync(INITIAL) -> async(READ) -> sync(PARSE). +static errorret_t loader_ctx_run(loader_ctx_t *ctx) { + errorret_t ret = assetJsonLoaderSync(&ctx->loading); + if(errorIsNotOk(ret)) return ret; + + ret = assetJsonLoaderAsync(&ctx->loading); + if(errorIsNotOk(ret)) return ret; + + return assetJsonLoaderSync(&ctx->loading); +} + +static void loader_ctx_dispose(loader_ctx_t *ctx) { + if(ctx->entry.type != ASSET_LOADER_TYPE_NULL) { + errorret_t ret = assetEntryDispose(&ctx->entry); + if(errorIsNotOk(ret)) errorCatch(ret); + } + threadMutexDispose(&ctx->loading.mutex); +} + +// ============================================================ +// Tests +// ============================================================ + +static void test_json_valid_loads(void **state) { + loader_ctx_t ctx; + loader_ctx_init(&ctx, "valid.json"); + + errorret_t ret = loader_ctx_run(&ctx); + assert_true(errorIsOk(ret)); + assert_int_equal(ctx.entry.state, ASSET_ENTRY_STATE_LOADED); + assert_non_null(ctx.entry.data.json); + + // Verify content is accessible + yyjson_val *root = yyjson_doc_get_root(ctx.entry.data.json); + assert_non_null(root); + assert_true(yyjson_is_obj(root)); + + yyjson_val *hello = yyjson_obj_get(root, "hello"); + assert_non_null(hello); + assert_string_equal(yyjson_get_str(hello), "world"); + + yyjson_val *count = yyjson_obj_get(root, "count"); + assert_non_null(count); + assert_int_equal((int)yyjson_get_int(count), 42); + + loader_ctx_dispose(&ctx); + assert_int_equal(memoryGetAllocatedCount(), 0); +} + +static void test_json_parse_error(void **state) { + loader_ctx_t ctx; + loader_ctx_init(&ctx, "invalid.json"); + + // Async read succeeds; sync parse fails because yyjson rejects the content. + errorret_t ret = assetJsonLoaderSync(&ctx.loading); + assert_true(errorIsOk(ret)); + + ret = assetJsonLoaderAsync(&ctx.loading); + assert_true(errorIsOk(ret)); + + ret = assetJsonLoaderSync(&ctx.loading); + assert_true(errorIsNotOk(ret)); + errorCatch(ret); + + assert_int_equal(ctx.entry.state, ASSET_ENTRY_STATE_ERROR); + assert_null(ctx.entry.data.json); + + loader_ctx_dispose(&ctx); + assert_int_equal(memoryGetAllocatedCount(), 0); +} + +static void test_json_missing_file(void **state) { + loader_ctx_t ctx; + loader_ctx_init(&ctx, "nonexistent.json"); + + errorret_t ret = assetJsonLoaderSync(&ctx.loading); + assert_true(errorIsOk(ret)); + + // Async phase stat-fails because the file isn't in the ZIP. + ret = assetJsonLoaderAsync(&ctx.loading); + assert_true(errorIsNotOk(ret)); + errorCatch(ret); + + assert_int_equal(ctx.entry.state, ASSET_ENTRY_STATE_ERROR); + + loader_ctx_dispose(&ctx); + assert_int_equal(memoryGetAllocatedCount(), 0); +} + +static void test_json_buffer_cleared_after_load(void **state) { + loader_ctx_t ctx; + loader_ctx_init(&ctx, "valid.json"); + + loader_ctx_run(&ctx); + + // The scratch buffer must be freed by the time sync returns LOADED. + assert_null(ctx.loading.loading.json.buffer); + + loader_ctx_dispose(&ctx); + assert_int_equal(memoryGetAllocatedCount(), 0); +} + +// ============================================================ +// main +// ============================================================ + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_json_valid_loads, zip_setup, zip_teardown), + cmocka_unit_test_setup_teardown(test_json_parse_error, zip_setup, zip_teardown), + cmocka_unit_test_setup_teardown(test_json_missing_file, zip_setup, zip_teardown), + cmocka_unit_test_setup_teardown(test_json_buffer_cleared_after_load, zip_setup, zip_teardown), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/test/asset/test_assettilesetloader.c b/test/asset/test_assettilesetloader.c new file mode 100644 index 00000000..1c78740f --- /dev/null +++ b/test/asset/test_assettilesetloader.c @@ -0,0 +1,358 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "dusktest.h" +#include "asset/asset.h" +#include "asset/loader/assetloader.h" +#include "asset/loader/assetentry.h" +#include "asset/loader/display/assettilesetloader.h" +#include "util/memory.h" +#include + +// ============================================================ +// DTF binary fixtures +// DTF layout: +// [0-2] magic "DTF" +// [3] version 0x00 +// [4-5] tileWidth (uint16 LE) +// [6-7] tileHeight (uint16 LE) +// [8-9] columns (uint16 LE) +// [10-11] rows (uint16 LE) +// [12-15] reserved +// [16-19] uv[0] (float LE) +// [20-23] uv[1] (float LE, must be 0.0–1.0) +// ============================================================ + +// uv[1] = 0.5f = 0x3F000000 LE = {0x00,0x00,0x00,0x3F} +// uv[1] = 2.0f = 0x40000000 LE = {0x00,0x00,0x00,0x40} (out-of-range) + +static const uint8_t DTF_VALID[] = { + 'D','T','F', 0x00, + 16, 0, + 16, 0, + 4, 0, + 4, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0x00, 0x00, 0x00, 0x3F +}; + +static const uint8_t DTF_BAD_MAGIC[] = { + 'X','Y','Z', 0x00, + 16, 0, 16, 0, 4, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0x00, 0x00, 0x00, 0x3F +}; + +static const uint8_t DTF_BAD_VERSION[] = { + 'D','T','F', 0xFF, + 16, 0, 16, 0, 4, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0x00, 0x00, 0x00, 0x3F +}; + +static const uint8_t DTF_ZERO_WIDTH[] = { + 'D','T','F', 0x00, + 0, 0, + 16, 0, 4, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0x00, 0x00, 0x00, 0x3F +}; + +static const uint8_t DTF_ZERO_HEIGHT[] = { + 'D','T','F', 0x00, + 16, 0, + 0, 0, + 4, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0x00, 0x00, 0x00, 0x3F +}; + +static const uint8_t DTF_ZERO_COLUMNS[] = { + 'D','T','F', 0x00, + 16, 0, 16, 0, + 0, 0, + 4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0x00, 0x00, 0x00, 0x3F +}; + +static const uint8_t DTF_ZERO_ROWS[] = { + 'D','T','F', 0x00, + 16, 0, 16, 0, 4, 0, + 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0x00, 0x00, 0x00, 0x3F +}; + +// uv[1] = 2.0f, which fails the 0.0–1.0 range check +static const uint8_t DTF_INVALID_UV[] = { + 'D','T','F', 0x00, + 16, 0, 16, 0, 4, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0x00, 0x00, 0x00, 0x40 +}; + +// ============================================================ +// In-memory ZIP +// ============================================================ + +static zip_t *g_zip = NULL; + +static int tileset_zip_add(zip_t *za, const char_t *name, const void *data, size_t len) { + zip_source_t *s = zip_source_buffer(za, data, len, 0); + return (int)zip_file_add(za, name, s, ZIP_FL_OVERWRITE); +} + +static int zip_setup(void **state) { + zip_error_t err; + zip_error_init(&err); + + zip_source_t *write_src = zip_source_buffer_create(NULL, 0, 1, &err); + if(!write_src) return -1; + + zip_t *za = zip_open_from_source(write_src, ZIP_TRUNCATE, &err); + if(!za) { zip_source_free(write_src); return -1; } + + if(tileset_zip_add(za, "valid.tileset", DTF_VALID, sizeof(DTF_VALID)) < 0 || + tileset_zip_add(za, "badmagic.tileset", DTF_BAD_MAGIC, sizeof(DTF_BAD_MAGIC)) < 0 || + tileset_zip_add(za, "badversion.tileset", DTF_BAD_VERSION, sizeof(DTF_BAD_VERSION)) < 0 || + tileset_zip_add(za, "zerowidth.tileset", DTF_ZERO_WIDTH, sizeof(DTF_ZERO_WIDTH)) < 0 || + tileset_zip_add(za, "zeroheight.tileset", DTF_ZERO_HEIGHT, sizeof(DTF_ZERO_HEIGHT)) < 0 || + tileset_zip_add(za, "zerocolumns.tileset", DTF_ZERO_COLUMNS, sizeof(DTF_ZERO_COLUMNS)) < 0 || + tileset_zip_add(za, "zerorows.tileset", DTF_ZERO_ROWS, sizeof(DTF_ZERO_ROWS)) < 0 || + tileset_zip_add(za, "invaliduv.tileset", DTF_INVALID_UV, sizeof(DTF_INVALID_UV)) < 0) { + zip_close(za); return -1; + } + + zip_source_keep(write_src); + if(zip_close(za) != 0) { zip_source_free(write_src); return -1; } + + zip_stat_t zs; + memset(&zs, 0, sizeof(zs)); + if(zip_source_stat(write_src, &zs) != 0 || !(zs.valid & ZIP_STAT_SIZE)) { + zip_source_free(write_src); return -1; + } + + void *zipbuf = malloc((size_t)zs.size); + if(!zipbuf) { zip_source_free(write_src); return -1; } + + if(zip_source_open(write_src) != 0) { + free(zipbuf); zip_source_free(write_src); return -1; + } + zip_source_read(write_src, zipbuf, (zip_uint64_t)zs.size); + zip_source_close(write_src); + zip_source_free(write_src); + + zip_error_init(&err); + zip_source_t *read_src = zip_source_buffer_create( + zipbuf, (zip_uint64_t)zs.size, 1, &err + ); + if(!read_src) { free(zipbuf); return -1; } + + g_zip = zip_open_from_source(read_src, 0, &err); + if(!g_zip) { zip_source_free(read_src); return -1; } + + ASSET.zip = g_zip; + return 0; +} + +static int zip_teardown(void **state) { + if(g_zip) { zip_close(g_zip); g_zip = NULL; } + ASSET.zip = NULL; + return 0; +} + +// ============================================================ +// Loader pipeline helper +// ============================================================ + +typedef struct { + assetentry_t entry; + assetloading_t loading; +} loader_ctx_t; + +static void loader_ctx_init(loader_ctx_t *ctx, const char_t *name) { + assetEntryInit(&ctx->entry, name, ASSET_LOADER_TYPE_TILESET, NULL); + threadMutexInit(&ctx->loading.mutex); + memoryZero(&ctx->loading.loading, sizeof(ctx->loading.loading)); + ctx->loading.type = ASSET_LOADER_TYPE_TILESET; + ctx->loading.entry = &ctx->entry; + ctx->entry.state = ASSET_ENTRY_STATE_PENDING_SYNC; +} + +static errorret_t loader_ctx_run(loader_ctx_t *ctx) { + errorret_t ret = assetTilesetLoaderSync(&ctx->loading); + if(errorIsNotOk(ret)) return ret; + + ret = assetTilesetLoaderAsync(&ctx->loading); + if(errorIsNotOk(ret)) return ret; + + return assetTilesetLoaderSync(&ctx->loading); +} + +static void loader_ctx_dispose(loader_ctx_t *ctx) { + if(ctx->entry.type != ASSET_LOADER_TYPE_NULL) { + errorret_t ret = assetEntryDispose(&ctx->entry); + if(errorIsNotOk(ret)) errorCatch(ret); + } + threadMutexDispose(&ctx->loading.mutex); +} + +// ============================================================ +// Tests +// ============================================================ + +static void test_tileset_valid_loads(void **state) { + loader_ctx_t ctx; + loader_ctx_init(&ctx, "valid.tileset"); + + errorret_t ret = loader_ctx_run(&ctx); + assert_true(errorIsOk(ret)); + assert_int_equal(ctx.entry.state, ASSET_ENTRY_STATE_LOADED); + + tileset_t *ts = &ctx.entry.data.tileset; + assert_int_equal(ts->tileWidth, 16); + assert_int_equal(ts->tileHeight, 16); + assert_int_equal(ts->columns, 4); + assert_int_equal(ts->rows, 4); + assert_int_equal(ts->tileCount, 16); // columns * rows + + loader_ctx_dispose(&ctx); + assert_int_equal(memoryGetAllocatedCount(), 0); +} + +static void test_tileset_data_cleared_after_load(void **state) { + loader_ctx_t ctx; + loader_ctx_init(&ctx, "valid.tileset"); + loader_ctx_run(&ctx); + + // The async scratch buffer must be freed by the time sync returns LOADED. + assert_null(ctx.loading.loading.tileset.data); + + loader_ctx_dispose(&ctx); + assert_int_equal(memoryGetAllocatedCount(), 0); +} + +static void test_tileset_bad_magic(void **state) { + loader_ctx_t ctx; + loader_ctx_init(&ctx, "badmagic.tileset"); + + errorret_t ret = assetTilesetLoaderSync(&ctx.loading); + assert_true(errorIsOk(ret)); + ret = assetTilesetLoaderAsync(&ctx.loading); + assert_true(errorIsOk(ret)); + ret = assetTilesetLoaderSync(&ctx.loading); + assert_true(errorIsNotOk(ret)); + errorCatch(ret); + + assert_int_equal(ctx.entry.state, ASSET_ENTRY_STATE_ERROR); + loader_ctx_dispose(&ctx); + assert_int_equal(memoryGetAllocatedCount(), 0); +} + +static void test_tileset_bad_version(void **state) { + loader_ctx_t ctx; + loader_ctx_init(&ctx, "badversion.tileset"); + + errorret_t ret = assetTilesetLoaderSync(&ctx.loading); + assert_true(errorIsOk(ret)); + ret = assetTilesetLoaderAsync(&ctx.loading); + assert_true(errorIsOk(ret)); + ret = assetTilesetLoaderSync(&ctx.loading); + assert_true(errorIsNotOk(ret)); + errorCatch(ret); + + assert_int_equal(ctx.entry.state, ASSET_ENTRY_STATE_ERROR); + loader_ctx_dispose(&ctx); + assert_int_equal(memoryGetAllocatedCount(), 0); +} + +static void test_tileset_zero_tile_width(void **state) { + loader_ctx_t ctx; + loader_ctx_init(&ctx, "zerowidth.tileset"); + errorret_t ret = loader_ctx_run(&ctx); + assert_true(errorIsNotOk(ret)); + errorCatch(ret); + assert_int_equal(ctx.entry.state, ASSET_ENTRY_STATE_ERROR); + loader_ctx_dispose(&ctx); + assert_int_equal(memoryGetAllocatedCount(), 0); +} + +static void test_tileset_zero_tile_height(void **state) { + loader_ctx_t ctx; + loader_ctx_init(&ctx, "zeroheight.tileset"); + errorret_t ret = loader_ctx_run(&ctx); + assert_true(errorIsNotOk(ret)); + errorCatch(ret); + assert_int_equal(ctx.entry.state, ASSET_ENTRY_STATE_ERROR); + loader_ctx_dispose(&ctx); + assert_int_equal(memoryGetAllocatedCount(), 0); +} + +static void test_tileset_zero_columns(void **state) { + loader_ctx_t ctx; + loader_ctx_init(&ctx, "zerocolumns.tileset"); + errorret_t ret = loader_ctx_run(&ctx); + assert_true(errorIsNotOk(ret)); + errorCatch(ret); + assert_int_equal(ctx.entry.state, ASSET_ENTRY_STATE_ERROR); + loader_ctx_dispose(&ctx); + assert_int_equal(memoryGetAllocatedCount(), 0); +} + +static void test_tileset_zero_rows(void **state) { + loader_ctx_t ctx; + loader_ctx_init(&ctx, "zerorows.tileset"); + errorret_t ret = loader_ctx_run(&ctx); + assert_true(errorIsNotOk(ret)); + errorCatch(ret); + assert_int_equal(ctx.entry.state, ASSET_ENTRY_STATE_ERROR); + loader_ctx_dispose(&ctx); + assert_int_equal(memoryGetAllocatedCount(), 0); +} + +static void test_tileset_invalid_uv(void **state) { + loader_ctx_t ctx; + loader_ctx_init(&ctx, "invaliduv.tileset"); + errorret_t ret = loader_ctx_run(&ctx); + assert_true(errorIsNotOk(ret)); + errorCatch(ret); + assert_int_equal(ctx.entry.state, ASSET_ENTRY_STATE_ERROR); + loader_ctx_dispose(&ctx); + assert_int_equal(memoryGetAllocatedCount(), 0); +} + +static void test_tileset_missing_file(void **state) { + loader_ctx_t ctx; + loader_ctx_init(&ctx, "nonexistent.tileset"); + + errorret_t ret = assetTilesetLoaderSync(&ctx.loading); + assert_true(errorIsOk(ret)); + + ret = assetTilesetLoaderAsync(&ctx.loading); + assert_true(errorIsNotOk(ret)); + errorCatch(ret); + + assert_int_equal(ctx.entry.state, ASSET_ENTRY_STATE_ERROR); + loader_ctx_dispose(&ctx); + assert_int_equal(memoryGetAllocatedCount(), 0); +} + +// ============================================================ +// main +// ============================================================ + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_tileset_valid_loads, zip_setup, zip_teardown), + cmocka_unit_test_setup_teardown(test_tileset_data_cleared_after_load, zip_setup, zip_teardown), + cmocka_unit_test_setup_teardown(test_tileset_bad_magic, zip_setup, zip_teardown), + cmocka_unit_test_setup_teardown(test_tileset_bad_version, zip_setup, zip_teardown), + cmocka_unit_test_setup_teardown(test_tileset_zero_tile_width, zip_setup, zip_teardown), + cmocka_unit_test_setup_teardown(test_tileset_zero_tile_height, zip_setup, zip_teardown), + cmocka_unit_test_setup_teardown(test_tileset_zero_columns, zip_setup, zip_teardown), + cmocka_unit_test_setup_teardown(test_tileset_zero_rows, zip_setup, zip_teardown), + cmocka_unit_test_setup_teardown(test_tileset_invalid_uv, zip_setup, zip_teardown), + cmocka_unit_test_setup_teardown(test_tileset_missing_file, zip_setup, zip_teardown), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +}