93ab7690ba
Adds missing test coverage for the asset pipeline's binary loaders (mesh/model/texture), assetfile.c, and assetbatch.c, plus a new test/event suite for the shared event primitive. Found and fixed two real bugs while writing this: - assetFileRead's NULL-buffer skip path double-counted file->position, which could trip stb_image's EOF check early on images that skip bytes mid-decode. - eventSubscribe/eventUnsubscribe matched only on the callback pointer instead of the (callback, user) pair the docs already promised, so two independent consumers of the same cached asset (e.g. two assetbatch_t's) would abort. Covered by dedicated caching tests in both test_assetbatch.c and test_assetmodelloader.c. Also drops test_overworldscene.c, broken by the in-progress overworldscene.js/init.js export-contract rewrite. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
222 lines
7.0 KiB
C
222 lines
7.0 KiB
C
/**
|
|
* Copyright (c) 2026 Dominic Masters
|
|
*
|
|
* This software is released under the MIT License.
|
|
* https://opensource.org/licenses/MIT
|
|
*/
|
|
|
|
#include "dusktest.h"
|
|
#include "event/event.h"
|
|
|
|
#define TEST_EVENT_MAX 4
|
|
|
|
static void incrementCallback(void *params, void *user) {
|
|
(*(int32_t *)user)++;
|
|
}
|
|
|
|
static void recordingCallback(void *params, void *user) {
|
|
*(void **)user = params;
|
|
}
|
|
|
|
// ============================================================
|
|
// eventInit
|
|
// ============================================================
|
|
|
|
static void test_eventInit_clears_state(void **state) {
|
|
eventcallback_t callbacks[TEST_EVENT_MAX];
|
|
void *users[TEST_EVENT_MAX];
|
|
event_t event;
|
|
|
|
eventInit(&event, callbacks, users, TEST_EVENT_MAX);
|
|
|
|
assert_int_equal((int)event.count, 0);
|
|
assert_int_equal((int)event.size, TEST_EVENT_MAX);
|
|
assert_ptr_equal(event.callbacks, callbacks);
|
|
assert_ptr_equal(event.users, users);
|
|
}
|
|
|
|
// ============================================================
|
|
// eventSubscribe / eventInvoke
|
|
// ============================================================
|
|
|
|
static void test_subscribe_and_invoke_single(void **state) {
|
|
eventcallback_t callbacks[TEST_EVENT_MAX];
|
|
void *users[TEST_EVENT_MAX];
|
|
event_t event;
|
|
eventInit(&event, callbacks, users, TEST_EVENT_MAX);
|
|
|
|
int32_t counter = 0;
|
|
eventSubscribe(&event, incrementCallback, &counter);
|
|
|
|
eventInvoke(&event, NULL);
|
|
assert_int_equal(counter, 1);
|
|
|
|
eventInvoke(&event, NULL);
|
|
assert_int_equal(counter, 2);
|
|
}
|
|
|
|
static void test_invoke_passes_params_through(void **state) {
|
|
eventcallback_t callbacks[TEST_EVENT_MAX];
|
|
void *users[TEST_EVENT_MAX];
|
|
event_t event;
|
|
eventInit(&event, callbacks, users, TEST_EVENT_MAX);
|
|
|
|
void *seenParams = NULL;
|
|
eventSubscribe(&event, recordingCallback, &seenParams);
|
|
|
|
int32_t sentinel = 0;
|
|
eventInvoke(&event, &sentinel);
|
|
assert_ptr_equal(seenParams, &sentinel);
|
|
}
|
|
|
|
static void test_subscribe_multiple_distinct_callbacks(void **state) {
|
|
eventcallback_t callbacks[TEST_EVENT_MAX];
|
|
void *users[TEST_EVENT_MAX];
|
|
event_t event;
|
|
eventInit(&event, callbacks, users, TEST_EVENT_MAX);
|
|
|
|
int32_t counterA = 0, counterB = 0;
|
|
eventSubscribe(&event, incrementCallback, &counterA);
|
|
eventSubscribe(&event, incrementCallback, &counterB);
|
|
|
|
eventInvoke(&event, NULL);
|
|
assert_int_equal(counterA, 1);
|
|
assert_int_equal(counterB, 1);
|
|
}
|
|
|
|
// This is the exact shape assetbatch.c relies on: two independent owners
|
|
// (e.g. two assetbatch_t's) both waiting on the same cached/shared asset
|
|
// entry subscribe the SAME callback function, distinguished only by a
|
|
// different `user` pointer. Before the fix, eventSubscribe's dedup check
|
|
// matched on callback alone and would assertUnreachable() here.
|
|
static void test_subscribe_same_callback_different_users_both_fire(
|
|
void **state
|
|
) {
|
|
eventcallback_t callbacks[TEST_EVENT_MAX];
|
|
void *users[TEST_EVENT_MAX];
|
|
event_t event;
|
|
eventInit(&event, callbacks, users, TEST_EVENT_MAX);
|
|
|
|
int32_t counterA = 0, counterB = 0;
|
|
eventSubscribe(&event, incrementCallback, &counterA);
|
|
eventSubscribe(&event, incrementCallback, &counterB);
|
|
assert_int_equal((int)event.count, 2);
|
|
|
|
eventInvoke(&event, NULL);
|
|
assert_int_equal(counterA, 1);
|
|
assert_int_equal(counterB, 1);
|
|
}
|
|
|
|
static void test_subscribe_duplicate_pair_asserts(void **state) {
|
|
eventcallback_t callbacks[TEST_EVENT_MAX];
|
|
void *users[TEST_EVENT_MAX];
|
|
event_t event;
|
|
eventInit(&event, callbacks, users, TEST_EVENT_MAX);
|
|
|
|
int32_t counter = 0;
|
|
eventSubscribe(&event, incrementCallback, &counter);
|
|
|
|
// Same (callback, user) pair a second time must still assert.
|
|
expect_assert_failure(eventSubscribe(&event, incrementCallback, &counter));
|
|
}
|
|
|
|
static void test_subscribe_capacity_exceeded_asserts(void **state) {
|
|
eventcallback_t callbacks[1];
|
|
void *users[1];
|
|
event_t event;
|
|
eventInit(&event, callbacks, users, 1);
|
|
|
|
int32_t counterA = 0, counterB = 0;
|
|
eventSubscribe(&event, incrementCallback, &counterA);
|
|
|
|
expect_assert_failure(eventSubscribe(&event, incrementCallback, &counterB));
|
|
}
|
|
|
|
// ============================================================
|
|
// eventUnsubscribe
|
|
// ============================================================
|
|
|
|
static void test_unsubscribe_removes_only_matching_pair(void **state) {
|
|
eventcallback_t callbacks[TEST_EVENT_MAX];
|
|
void *users[TEST_EVENT_MAX];
|
|
event_t event;
|
|
eventInit(&event, callbacks, users, TEST_EVENT_MAX);
|
|
|
|
int32_t counterA = 0, counterB = 0;
|
|
eventSubscribe(&event, incrementCallback, &counterA);
|
|
eventSubscribe(&event, incrementCallback, &counterB);
|
|
|
|
// Unsubscribing A's registration must not disturb B's, even though they
|
|
// share the same callback function pointer.
|
|
eventUnsubscribe(&event, incrementCallback, &counterA);
|
|
assert_int_equal((int)event.count, 1);
|
|
|
|
eventInvoke(&event, NULL);
|
|
assert_int_equal(counterA, 0);
|
|
assert_int_equal(counterB, 1);
|
|
}
|
|
|
|
static void test_unsubscribe_nonexistent_pair_is_noop(void **state) {
|
|
eventcallback_t callbacks[TEST_EVENT_MAX];
|
|
void *users[TEST_EVENT_MAX];
|
|
event_t event;
|
|
eventInit(&event, callbacks, users, TEST_EVENT_MAX);
|
|
|
|
int32_t counterA = 0, counterB = 0;
|
|
eventSubscribe(&event, incrementCallback, &counterA);
|
|
|
|
// Same callback, but a user pointer that was never subscribed.
|
|
eventUnsubscribe(&event, incrementCallback, &counterB);
|
|
assert_int_equal((int)event.count, 1);
|
|
|
|
eventInvoke(&event, NULL);
|
|
assert_int_equal(counterA, 1);
|
|
}
|
|
|
|
static void test_unsubscribe_from_middle_keeps_remaining_working(
|
|
void **state
|
|
) {
|
|
eventcallback_t callbacks[TEST_EVENT_MAX];
|
|
void *users[TEST_EVENT_MAX];
|
|
event_t event;
|
|
eventInit(&event, callbacks, users, TEST_EVENT_MAX);
|
|
|
|
int32_t counterA = 0, counterB = 0, counterC = 0;
|
|
eventSubscribe(&event, incrementCallback, &counterA);
|
|
eventSubscribe(&event, incrementCallback, &counterB);
|
|
eventSubscribe(&event, incrementCallback, &counterC);
|
|
|
|
// Removing the middle subscriber swaps the last slot into its place;
|
|
// verify every remaining subscriber still fires exactly once.
|
|
eventUnsubscribe(&event, incrementCallback, &counterB);
|
|
assert_int_equal((int)event.count, 2);
|
|
|
|
eventInvoke(&event, NULL);
|
|
assert_int_equal(counterA, 1);
|
|
assert_int_equal(counterB, 0);
|
|
assert_int_equal(counterC, 1);
|
|
}
|
|
|
|
// ============================================================
|
|
// main
|
|
// ============================================================
|
|
|
|
int main(void) {
|
|
assertInit();
|
|
const struct CMUnitTest tests[] = {
|
|
cmocka_unit_test(test_eventInit_clears_state),
|
|
|
|
cmocka_unit_test(test_subscribe_and_invoke_single),
|
|
cmocka_unit_test(test_invoke_passes_params_through),
|
|
cmocka_unit_test(test_subscribe_multiple_distinct_callbacks),
|
|
cmocka_unit_test(test_subscribe_same_callback_different_users_both_fire),
|
|
cmocka_unit_test(test_subscribe_duplicate_pair_asserts),
|
|
cmocka_unit_test(test_subscribe_capacity_exceeded_asserts),
|
|
|
|
cmocka_unit_test(test_unsubscribe_removes_only_matching_pair),
|
|
cmocka_unit_test(test_unsubscribe_nonexistent_pair_is_noop),
|
|
cmocka_unit_test(test_unsubscribe_from_middle_keeps_remaining_working),
|
|
};
|
|
return cmocka_run_group_tests(tests, NULL, NULL);
|
|
}
|