Files
dusk/test/asset/test_assettilesetloader.c
T

359 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 <zip.h>
// ============================================================
// 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.01.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.01.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);
}