Files
dusk/test/event/test_event.c
T
2026-08-08 01:18:07 -05:00

444 lines
14 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"
typedef struct {
int32_t callCount;
void *lastParams;
void *lastUser;
} eventrecord_t;
// Distinct function pointers are required per-subscriber - the same callback
// pointer cannot be subscribed twice, even with a different user pointer.
static void helper_recordA(void *params, void *user) {
eventrecord_t *record = (eventrecord_t *)user;
record->callCount++;
record->lastParams = params;
record->lastUser = user;
}
static void helper_recordB(void *params, void *user) {
eventrecord_t *record = (eventrecord_t *)user;
record->callCount++;
record->lastParams = params;
record->lastUser = user;
}
static void helper_recordC(void *params, void *user) {
eventrecord_t *record = (eventrecord_t *)user;
record->callCount++;
record->lastParams = params;
record->lastUser = user;
}
// Records via `params` instead of `user` - for cases exercising a NULL user
// (e.g. no users array), where dereferencing `user` would crash.
static void helper_recordViaParams(void *params, void *user) {
eventrecord_t *record = (eventrecord_t *)params;
record->callCount++;
record->lastParams = params;
record->lastUser = user;
}
#define EVENT_CAPACITY 4
// --- eventInit ---
static void test_eventInitSetsUpBackingArrays(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
void *users[EVENT_CAPACITY];
memset(callbacks, 0xAA, sizeof(callbacks));
memset(users, 0xAA, sizeof(users));
event_t event;
eventInit(&event, callbacks, users, EVENT_CAPACITY);
assert_ptr_equal(event.callbacks, callbacks);
assert_ptr_equal(event.users, users);
assert_int_equal(event.size, EVENT_CAPACITY);
assert_int_equal(event.count, 0);
for(int32_t i = 0; i < EVENT_CAPACITY; i++) {
assert_null(callbacks[i]);
assert_null(users[i]);
}
}
static void test_eventInitAllowsNullUsersArray(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
event_t event;
eventInit(&event, callbacks, NULL, EVENT_CAPACITY);
assert_null(event.users);
}
static void test_eventInitResetClearsSubscribersOnly(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
void *users[EVENT_CAPACITY];
eventrecord_t record = { 0 };
event_t event;
eventInit(&event, callbacks, users, EVENT_CAPACITY);
eventSubscribe(&event, helper_recordA, &record);
assert_int_equal(event.count, 1);
// Re-init (reset) with the same backing arrays.
eventInit(&event, callbacks, users, EVENT_CAPACITY);
assert_ptr_equal(event.callbacks, callbacks);
assert_int_equal(event.count, 0);
assert_null(callbacks[0]);
assert_null(users[0]);
}
static void test_eventInitNullEventAsserts(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
expect_assert_failure(eventInit(NULL, callbacks, NULL, EVENT_CAPACITY));
}
static void test_eventInitNullCallbacksAsserts(void **state) {
event_t event;
expect_assert_failure(eventInit(&event, NULL, NULL, EVENT_CAPACITY));
}
static void test_eventInitZeroSizeAsserts(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
event_t event;
expect_assert_failure(eventInit(&event, callbacks, NULL, 0));
}
// --- eventSubscribe ---
static void test_eventSubscribeAddsCallback(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
void *users[EVENT_CAPACITY];
eventrecord_t record = { 0 };
event_t event;
eventInit(&event, callbacks, users, EVENT_CAPACITY);
eventSubscribe(&event, helper_recordA, &record);
assert_int_equal(event.count, 1);
assert_ptr_equal(event.callbacks[0], helper_recordA);
assert_ptr_equal(event.users[0], &record);
}
static void test_eventSubscribeMultiple(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
void *users[EVENT_CAPACITY];
eventrecord_t recordA = { 0 }, recordB = { 0 }, recordC = { 0 };
event_t event;
eventInit(&event, callbacks, users, EVENT_CAPACITY);
eventSubscribe(&event, helper_recordA, &recordA);
eventSubscribe(&event, helper_recordB, &recordB);
eventSubscribe(&event, helper_recordC, &recordC);
assert_int_equal(event.count, 3);
}
static void test_eventSubscribeNullUserLeavesSlotNull(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
void *users[EVENT_CAPACITY];
event_t event;
eventInit(&event, callbacks, users, EVENT_CAPACITY);
eventSubscribe(&event, helper_recordA, NULL);
assert_null(event.users[0]);
}
static void test_eventSubscribeNullUserAfterUnsubscribeStaysNull(void **state) {
// The vacated slot must actually be cleared, not left holding a stale
// user pointer from whatever previously occupied it.
eventcallback_t callbacks[EVENT_CAPACITY];
void *users[EVENT_CAPACITY];
eventrecord_t recordA = { 0 }, recordB = { 0 };
event_t event;
eventInit(&event, callbacks, users, EVENT_CAPACITY);
eventSubscribe(&event, helper_recordA, &recordA);
eventSubscribe(&event, helper_recordB, &recordB);
eventUnsubscribe(&event, helper_recordA);
eventSubscribe(&event, helper_recordC, NULL);
for(int32_t i = 0; i < event.count; i++) {
if(event.callbacks[i] == helper_recordC) {
assert_null(event.users[i]);
return;
}
}
fail_msg("helper_recordC was not found after subscribing.");
}
static void test_eventSubscribeUserWithoutUsersArrayAsserts(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
eventrecord_t record = { 0 };
event_t event;
eventInit(&event, callbacks, NULL, EVENT_CAPACITY);
expect_assert_failure(eventSubscribe(&event, helper_recordA, &record));
}
static void test_eventSubscribeDuplicateCallbackAsserts(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
void *users[EVENT_CAPACITY];
eventrecord_t record = { 0 };
event_t event;
eventInit(&event, callbacks, users, EVENT_CAPACITY);
eventSubscribe(&event, helper_recordA, &record);
expect_assert_failure(eventSubscribe(&event, helper_recordA, &record));
}
static void test_eventSubscribeSameCallbackDifferentUserStillAsserts(void **state) {
// Documented as "the same (callback, user) pair may only be subscribed
// once", implying a different user should be fine - but the actual
// uniqueness check only looks at the callback pointer, so this asserts
// too. Captures actual behavior; flag if the doc/implementation should
// instead match on the (callback, user) pair.
eventcallback_t callbacks[EVENT_CAPACITY];
void *users[EVENT_CAPACITY];
eventrecord_t recordA = { 0 }, recordB = { 0 };
event_t event;
eventInit(&event, callbacks, users, EVENT_CAPACITY);
eventSubscribe(&event, helper_recordA, &recordA);
expect_assert_failure(eventSubscribe(&event, helper_recordA, &recordB));
}
static void test_eventSubscribeCapacityExceededAsserts(void **state) {
eventcallback_t callbacks[2];
void *users[2];
eventrecord_t recordA = { 0 }, recordB = { 0 }, recordC = { 0 };
event_t event;
eventInit(&event, callbacks, users, 2);
eventSubscribe(&event, helper_recordA, &recordA);
eventSubscribe(&event, helper_recordB, &recordB);
expect_assert_failure(eventSubscribe(&event, helper_recordC, &recordC));
}
static void test_eventSubscribeNullEventAsserts(void **state) {
expect_assert_failure(eventSubscribe(NULL, helper_recordA, NULL));
}
static void test_eventSubscribeNullCallbackAsserts(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
event_t event;
eventInit(&event, callbacks, NULL, EVENT_CAPACITY);
expect_assert_failure(eventSubscribe(&event, NULL, NULL));
}
// --- eventUnsubscribe ---
static void test_eventUnsubscribeMiddleSwapsLastIntoPlace(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
void *users[EVENT_CAPACITY];
eventrecord_t recordA = { 0 }, recordB = { 0 }, recordC = { 0 };
event_t event;
eventInit(&event, callbacks, users, EVENT_CAPACITY);
eventSubscribe(&event, helper_recordA, &recordA);
eventSubscribe(&event, helper_recordB, &recordB);
eventSubscribe(&event, helper_recordC, &recordC);
eventUnsubscribe(&event, helper_recordB);
assert_int_equal(event.count, 2);
assert_ptr_equal(event.callbacks[0], helper_recordA);
// recordC (previously last) was swapped into the vacated middle slot.
assert_ptr_equal(event.callbacks[1], helper_recordC);
assert_ptr_equal(event.users[1], &recordC);
// The old tail slot is fully cleared.
assert_null(event.callbacks[2]);
assert_null(event.users[2]);
}
static void test_eventUnsubscribeLastElement(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
void *users[EVENT_CAPACITY];
eventrecord_t recordA = { 0 }, recordB = { 0 };
event_t event;
eventInit(&event, callbacks, users, EVENT_CAPACITY);
eventSubscribe(&event, helper_recordA, &recordA);
eventSubscribe(&event, helper_recordB, &recordB);
eventUnsubscribe(&event, helper_recordB);
assert_int_equal(event.count, 1);
assert_ptr_equal(event.callbacks[0], helper_recordA);
assert_null(event.callbacks[1]);
}
static void test_eventUnsubscribeNotSubscribedIsNoop(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
void *users[EVENT_CAPACITY];
eventrecord_t recordA = { 0 };
event_t event;
eventInit(&event, callbacks, users, EVENT_CAPACITY);
eventSubscribe(&event, helper_recordA, &recordA);
eventUnsubscribe(&event, helper_recordB);
assert_int_equal(event.count, 1);
assert_ptr_equal(event.callbacks[0], helper_recordA);
}
static void test_eventUnsubscribeThenResubscribeSucceeds(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
void *users[EVENT_CAPACITY];
eventrecord_t recordA = { 0 };
event_t event;
eventInit(&event, callbacks, users, EVENT_CAPACITY);
eventSubscribe(&event, helper_recordA, &recordA);
eventUnsubscribe(&event, helper_recordA);
// Would have asserted (duplicate) had the removal not actually happened.
eventSubscribe(&event, helper_recordA, &recordA);
assert_int_equal(event.count, 1);
}
static void test_eventUnsubscribeWithNullUsersArrayIsSafe(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
event_t event;
eventInit(&event, callbacks, NULL, EVENT_CAPACITY);
eventSubscribe(&event, helper_recordA, NULL);
eventUnsubscribe(&event, helper_recordA);
assert_int_equal(event.count, 0);
}
static void test_eventUnsubscribeNullEventAsserts(void **state) {
expect_assert_failure(eventUnsubscribe(NULL, helper_recordA));
}
static void test_eventUnsubscribeNullCallbackAsserts(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
event_t event;
eventInit(&event, callbacks, NULL, EVENT_CAPACITY);
expect_assert_failure(eventUnsubscribe(&event, NULL));
}
// --- eventInvoke ---
static void test_eventInvokeCallsAllSubscribersWithParamsAndUser(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
void *users[EVENT_CAPACITY];
eventrecord_t recordA = { 0 }, recordB = { 0 };
int32_t params = 123;
event_t event;
eventInit(&event, callbacks, users, EVENT_CAPACITY);
eventSubscribe(&event, helper_recordA, &recordA);
eventSubscribe(&event, helper_recordB, &recordB);
eventInvoke(&event, &params);
assert_int_equal(recordA.callCount, 1);
assert_ptr_equal(recordA.lastParams, &params);
assert_ptr_equal(recordA.lastUser, &recordA);
assert_int_equal(recordB.callCount, 1);
assert_ptr_equal(recordB.lastParams, &params);
assert_ptr_equal(recordB.lastUser, &recordB);
}
static void test_eventInvokeWithNoSubscribersDoesNothing(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
void *users[EVENT_CAPACITY];
event_t event;
eventInit(&event, callbacks, users, EVENT_CAPACITY);
// No subscribers exist, so there is nothing to observe beyond "no crash".
eventInvoke(&event, NULL);
}
static void test_eventInvokeWithNullUsersArrayPassesNull(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
eventrecord_t record = { 0 };
event_t event;
eventInit(&event, callbacks, NULL, EVENT_CAPACITY);
eventSubscribe(&event, helper_recordViaParams, NULL);
eventInvoke(&event, &record);
assert_int_equal(record.callCount, 1);
assert_ptr_equal(record.lastParams, &record);
assert_null(record.lastUser);
}
static void test_eventInvokeSkipsUnsubscribedCallback(void **state) {
eventcallback_t callbacks[EVENT_CAPACITY];
void *users[EVENT_CAPACITY];
eventrecord_t recordA = { 0 }, recordB = { 0 };
event_t event;
eventInit(&event, callbacks, users, EVENT_CAPACITY);
eventSubscribe(&event, helper_recordA, &recordA);
eventSubscribe(&event, helper_recordB, &recordB);
eventUnsubscribe(&event, helper_recordA);
eventInvoke(&event, NULL);
assert_int_equal(recordA.callCount, 0);
assert_int_equal(recordB.callCount, 1);
}
static void test_eventInvokeNullEventAsserts(void **state) {
expect_assert_failure(eventInvoke(NULL, NULL));
}
int main(void) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_eventInitSetsUpBackingArrays),
cmocka_unit_test(test_eventInitAllowsNullUsersArray),
cmocka_unit_test(test_eventInitResetClearsSubscribersOnly),
cmocka_unit_test(test_eventInitNullEventAsserts),
cmocka_unit_test(test_eventInitNullCallbacksAsserts),
cmocka_unit_test(test_eventInitZeroSizeAsserts),
cmocka_unit_test(test_eventSubscribeAddsCallback),
cmocka_unit_test(test_eventSubscribeMultiple),
cmocka_unit_test(test_eventSubscribeNullUserLeavesSlotNull),
cmocka_unit_test(test_eventSubscribeNullUserAfterUnsubscribeStaysNull),
cmocka_unit_test(test_eventSubscribeUserWithoutUsersArrayAsserts),
cmocka_unit_test(test_eventSubscribeDuplicateCallbackAsserts),
cmocka_unit_test(test_eventSubscribeSameCallbackDifferentUserStillAsserts),
cmocka_unit_test(test_eventSubscribeCapacityExceededAsserts),
cmocka_unit_test(test_eventSubscribeNullEventAsserts),
cmocka_unit_test(test_eventSubscribeNullCallbackAsserts),
cmocka_unit_test(test_eventUnsubscribeMiddleSwapsLastIntoPlace),
cmocka_unit_test(test_eventUnsubscribeLastElement),
cmocka_unit_test(test_eventUnsubscribeNotSubscribedIsNoop),
cmocka_unit_test(test_eventUnsubscribeThenResubscribeSucceeds),
cmocka_unit_test(test_eventUnsubscribeWithNullUsersArrayIsSafe),
cmocka_unit_test(test_eventUnsubscribeNullEventAsserts),
cmocka_unit_test(test_eventUnsubscribeNullCallbackAsserts),
cmocka_unit_test(test_eventInvokeCallsAllSubscribersWithParamsAndUser),
cmocka_unit_test(test_eventInvokeWithNoSubscribersDoesNothing),
cmocka_unit_test(test_eventInvokeWithNullUsersArrayPassesNull),
cmocka_unit_test(test_eventInvokeSkipsUnsubscribedCallback),
cmocka_unit_test(test_eventInvokeNullEventAsserts),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}