some random fixes
This commit is contained in:
@@ -6,7 +6,9 @@
|
||||
add_subdirectory(animation)
|
||||
add_subdirectory(assert)
|
||||
add_subdirectory(asset)
|
||||
add_subdirectory(console)
|
||||
add_subdirectory(error)
|
||||
add_subdirectory(event)
|
||||
add_subdirectory(thread)
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(rpg)
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
include(dusktest)
|
||||
|
||||
# Tests
|
||||
dusktest(test_easing.c)
|
||||
dusktest(test_keyframe.c)
|
||||
dusktest(test_animation.c)
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "dusktest.h"
|
||||
#include "animation/easing.h"
|
||||
|
||||
#define EASING_TOLERANCE 0.0001f
|
||||
|
||||
static void test_easingLinear(void **state) {
|
||||
assert_float_equal(easingLinear(0.0f), 0.0f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingLinear(0.25f), 0.25f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingLinear(0.5f), 0.5f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingLinear(0.75f), 0.75f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingLinear(1.0f), 1.0f, EASING_TOLERANCE);
|
||||
}
|
||||
|
||||
static void test_easingInSine(void **state) {
|
||||
assert_float_equal(easingInSine(0.0f), 0.0f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInSine(0.25f), 0.076120f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInSine(0.5f), 0.292893f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInSine(0.75f), 0.617317f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInSine(1.0f), 1.0f, EASING_TOLERANCE);
|
||||
}
|
||||
|
||||
static void test_easingOutSine(void **state) {
|
||||
assert_float_equal(easingOutSine(0.0f), 0.0f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutSine(0.25f), 0.382683f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutSine(0.5f), 0.707107f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutSine(0.75f), 0.923880f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutSine(1.0f), 1.0f, EASING_TOLERANCE);
|
||||
}
|
||||
|
||||
static void test_easingInOutSine(void **state) {
|
||||
assert_float_equal(easingInOutSine(0.0f), 0.0f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutSine(0.25f), 0.146447f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutSine(0.5f), 0.5f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutSine(0.75f), 0.853553f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutSine(1.0f), 1.0f, EASING_TOLERANCE);
|
||||
}
|
||||
|
||||
static void test_easingInQuad(void **state) {
|
||||
assert_float_equal(easingInQuad(0.0f), 0.0f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInQuad(0.25f), 0.0625f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInQuad(0.5f), 0.25f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInQuad(0.75f), 0.5625f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInQuad(1.0f), 1.0f, EASING_TOLERANCE);
|
||||
}
|
||||
|
||||
static void test_easingOutQuad(void **state) {
|
||||
assert_float_equal(easingOutQuad(0.0f), 0.0f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutQuad(0.25f), 0.4375f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutQuad(0.5f), 0.75f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutQuad(0.75f), 0.9375f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutQuad(1.0f), 1.0f, EASING_TOLERANCE);
|
||||
}
|
||||
|
||||
static void test_easingInOutQuad(void **state) {
|
||||
assert_float_equal(easingInOutQuad(0.0f), 0.0f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutQuad(0.25f), 0.125f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutQuad(0.5f), 0.5f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutQuad(0.75f), 0.875f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutQuad(1.0f), 1.0f, EASING_TOLERANCE);
|
||||
}
|
||||
|
||||
static void test_easingInCubic(void **state) {
|
||||
assert_float_equal(easingInCubic(0.0f), 0.0f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInCubic(0.25f), 0.015625f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInCubic(0.5f), 0.125f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInCubic(0.75f), 0.421875f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInCubic(1.0f), 1.0f, EASING_TOLERANCE);
|
||||
}
|
||||
|
||||
static void test_easingOutCubic(void **state) {
|
||||
assert_float_equal(easingOutCubic(0.0f), 0.0f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutCubic(0.25f), 0.578125f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutCubic(0.5f), 0.875f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutCubic(0.75f), 0.984375f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutCubic(1.0f), 1.0f, EASING_TOLERANCE);
|
||||
}
|
||||
|
||||
static void test_easingInOutCubic(void **state) {
|
||||
assert_float_equal(easingInOutCubic(0.0f), 0.0f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutCubic(0.25f), 0.0625f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutCubic(0.5f), 0.5f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutCubic(0.75f), 0.9375f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutCubic(1.0f), 1.0f, EASING_TOLERANCE);
|
||||
}
|
||||
|
||||
static void test_easingInQuart(void **state) {
|
||||
assert_float_equal(easingInQuart(0.0f), 0.0f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInQuart(0.25f), 0.003906f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInQuart(0.5f), 0.0625f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInQuart(0.75f), 0.316406f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInQuart(1.0f), 1.0f, EASING_TOLERANCE);
|
||||
}
|
||||
|
||||
static void test_easingOutQuart(void **state) {
|
||||
assert_float_equal(easingOutQuart(0.0f), 0.0f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutQuart(0.25f), 0.683594f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutQuart(0.5f), 0.9375f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutQuart(0.75f), 0.996094f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutQuart(1.0f), 1.0f, EASING_TOLERANCE);
|
||||
}
|
||||
|
||||
static void test_easingInOutQuart(void **state) {
|
||||
assert_float_equal(easingInOutQuart(0.0f), 0.0f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutQuart(0.25f), 0.03125f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutQuart(0.5f), 0.5f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutQuart(0.75f), 0.96875f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutQuart(1.0f), 1.0f, EASING_TOLERANCE);
|
||||
}
|
||||
|
||||
static void test_easingInBack(void **state) {
|
||||
// "In back" overshoots below 0 before rising to 1 - that's the point of it.
|
||||
assert_float_equal(easingInBack(0.0f), 0.0f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInBack(0.25f), -0.064137f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInBack(0.5f), -0.087698f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInBack(0.75f), 0.182590f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInBack(1.0f), 1.0f, EASING_TOLERANCE);
|
||||
}
|
||||
|
||||
static void test_easingOutBack(void **state) {
|
||||
// "Out back" overshoots above 1 before settling at 1.
|
||||
assert_float_equal(easingOutBack(0.0f), 0.0f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutBack(0.25f), 0.817410f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutBack(0.5f), 1.087697f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutBack(0.75f), 1.064137f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingOutBack(1.0f), 1.0f, EASING_TOLERANCE);
|
||||
}
|
||||
|
||||
static void test_easingInOutBack(void **state) {
|
||||
assert_float_equal(easingInOutBack(0.0f), 0.0f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutBack(0.25f), -0.099682f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutBack(0.5f), 0.5f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutBack(0.75f), 1.099682f, EASING_TOLERANCE);
|
||||
assert_float_equal(easingInOutBack(1.0f), 1.0f, EASING_TOLERANCE);
|
||||
}
|
||||
|
||||
static void test_easingApplyDispatchesToEachType(void **state) {
|
||||
assert_float_equal(easingApply(EASING_LINEAR, 0.5f), easingLinear(0.5f), EASING_TOLERANCE);
|
||||
assert_float_equal(easingApply(EASING_IN_SINE, 0.5f), easingInSine(0.5f), EASING_TOLERANCE);
|
||||
assert_float_equal(easingApply(EASING_OUT_SINE, 0.5f), easingOutSine(0.5f), EASING_TOLERANCE);
|
||||
assert_float_equal(easingApply(EASING_IN_OUT_SINE, 0.5f), easingInOutSine(0.5f), EASING_TOLERANCE);
|
||||
assert_float_equal(easingApply(EASING_IN_QUAD, 0.5f), easingInQuad(0.5f), EASING_TOLERANCE);
|
||||
assert_float_equal(easingApply(EASING_OUT_QUAD, 0.5f), easingOutQuad(0.5f), EASING_TOLERANCE);
|
||||
assert_float_equal(easingApply(EASING_IN_OUT_QUAD, 0.5f), easingInOutQuad(0.5f), EASING_TOLERANCE);
|
||||
assert_float_equal(easingApply(EASING_IN_CUBIC, 0.5f), easingInCubic(0.5f), EASING_TOLERANCE);
|
||||
assert_float_equal(easingApply(EASING_OUT_CUBIC, 0.5f), easingOutCubic(0.5f), EASING_TOLERANCE);
|
||||
assert_float_equal(easingApply(EASING_IN_OUT_CUBIC, 0.5f), easingInOutCubic(0.5f), EASING_TOLERANCE);
|
||||
assert_float_equal(easingApply(EASING_IN_QUART, 0.5f), easingInQuart(0.5f), EASING_TOLERANCE);
|
||||
assert_float_equal(easingApply(EASING_OUT_QUART, 0.5f), easingOutQuart(0.5f), EASING_TOLERANCE);
|
||||
assert_float_equal(easingApply(EASING_IN_OUT_QUART, 0.5f), easingInOutQuart(0.5f), EASING_TOLERANCE);
|
||||
assert_float_equal(easingApply(EASING_IN_BACK, 0.5f), easingInBack(0.5f), EASING_TOLERANCE);
|
||||
assert_float_equal(easingApply(EASING_OUT_BACK, 0.5f), easingOutBack(0.5f), EASING_TOLERANCE);
|
||||
assert_float_equal(easingApply(EASING_IN_OUT_BACK, 0.5f), easingInOutBack(0.5f), EASING_TOLERANCE);
|
||||
}
|
||||
|
||||
static void test_easingApplyInvalidTypeAsserts(void **state) {
|
||||
expect_assert_failure(easingApply(EASING_COUNT, 0.5f));
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test_easingLinear),
|
||||
cmocka_unit_test(test_easingInSine),
|
||||
cmocka_unit_test(test_easingOutSine),
|
||||
cmocka_unit_test(test_easingInOutSine),
|
||||
cmocka_unit_test(test_easingInQuad),
|
||||
cmocka_unit_test(test_easingOutQuad),
|
||||
cmocka_unit_test(test_easingInOutQuad),
|
||||
cmocka_unit_test(test_easingInCubic),
|
||||
cmocka_unit_test(test_easingOutCubic),
|
||||
cmocka_unit_test(test_easingInOutCubic),
|
||||
cmocka_unit_test(test_easingInQuart),
|
||||
cmocka_unit_test(test_easingOutQuart),
|
||||
cmocka_unit_test(test_easingInOutQuart),
|
||||
cmocka_unit_test(test_easingInBack),
|
||||
cmocka_unit_test(test_easingOutBack),
|
||||
cmocka_unit_test(test_easingInOutBack),
|
||||
cmocka_unit_test(test_easingApplyDispatchesToEachType),
|
||||
cmocka_unit_test(test_easingApplyInvalidTypeAsserts),
|
||||
};
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
include(dusktest)
|
||||
|
||||
# Tests
|
||||
dusktest(test_console.c)
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "dusktest.h"
|
||||
#include "console/console.h"
|
||||
#include "thread/thread.h"
|
||||
#include "util/string.h"
|
||||
|
||||
static void test_consoleInitDefaults(void **state) {
|
||||
consoleInit();
|
||||
|
||||
assert_false(CONSOLE.visible);
|
||||
for(int32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) {
|
||||
assert_string_equal(CONSOLE.line[i], "");
|
||||
}
|
||||
|
||||
consoleDispose();
|
||||
}
|
||||
|
||||
static void test_consolePrintAddsNewestAtEnd(void **state) {
|
||||
consoleInit();
|
||||
|
||||
consolePrint("hello %d", 5);
|
||||
|
||||
assert_string_equal(CONSOLE.line[CONSOLE_HISTORY_MAX - 1], "hello 5");
|
||||
|
||||
consoleDispose();
|
||||
}
|
||||
|
||||
static void test_consolePrintShiftsHistory(void **state) {
|
||||
consoleInit();
|
||||
|
||||
consolePrint("one");
|
||||
consolePrint("two");
|
||||
consolePrint("three");
|
||||
|
||||
assert_string_equal(CONSOLE.line[CONSOLE_HISTORY_MAX - 1], "three");
|
||||
assert_string_equal(CONSOLE.line[CONSOLE_HISTORY_MAX - 2], "two");
|
||||
assert_string_equal(CONSOLE.line[CONSOLE_HISTORY_MAX - 3], "one");
|
||||
|
||||
consoleDispose();
|
||||
}
|
||||
|
||||
static void test_consolePrintOverflowDropsOldest(void **state) {
|
||||
consoleInit();
|
||||
|
||||
const int32_t total = CONSOLE_HISTORY_MAX + 2;
|
||||
for(int32_t i = 0; i < total; i++) {
|
||||
consolePrint("line-%d", i);
|
||||
}
|
||||
|
||||
// The oldest two ("line-0", "line-1") must have been dropped.
|
||||
for(int32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) {
|
||||
char_t expected[CONSOLE_LINE_MAX];
|
||||
stringFormat(expected, CONSOLE_LINE_MAX, "line-%d", i + 2);
|
||||
assert_string_equal(CONSOLE.line[i], expected);
|
||||
}
|
||||
|
||||
consoleDispose();
|
||||
}
|
||||
|
||||
// --- Thread-safety of consolePrint's shared history buffer ---
|
||||
|
||||
typedef struct {
|
||||
int32_t threadIndex;
|
||||
int32_t iterations;
|
||||
} consoleprinter_data_t;
|
||||
|
||||
static void helper_consolePrinter(thread_t *thread) {
|
||||
consoleprinter_data_t *data = (consoleprinter_data_t *)thread->data;
|
||||
for(int32_t i = 0; i < data->iterations; i++) {
|
||||
consolePrint("T%d-%03d", data->threadIndex, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Parses a "T<thread>-<seq>" line, requiring the whole line to match.
|
||||
// Returns false for an empty or malformed line.
|
||||
static bool_t parsePrintedLine(
|
||||
const char_t *line, int32_t *threadIndex, int32_t *seq
|
||||
) {
|
||||
int32_t consumed = 0;
|
||||
int32_t matched = sscanf(line, "T%d-%d%n", threadIndex, seq, &consumed);
|
||||
return matched == 2 && consumed == (int32_t)strlen(line);
|
||||
}
|
||||
|
||||
#define CONSOLE_THREAD_COUNT 4
|
||||
|
||||
static void test_consolePrintConcurrentExactlyFillsHistory(void **state) {
|
||||
// 4 threads x 4 prints = exactly CONSOLE_HISTORY_MAX, so every slot must
|
||||
// end up holding one well-formed, uncorrupted message - none left empty,
|
||||
// none torn/interleaved by a missing lock.
|
||||
consoleInit();
|
||||
|
||||
const int32_t iterations = CONSOLE_HISTORY_MAX / CONSOLE_THREAD_COUNT;
|
||||
consoleprinter_data_t data[CONSOLE_THREAD_COUNT];
|
||||
thread_t threads[CONSOLE_THREAD_COUNT];
|
||||
|
||||
for(int32_t i = 0; i < CONSOLE_THREAD_COUNT; i++) {
|
||||
data[i].threadIndex = i;
|
||||
data[i].iterations = iterations;
|
||||
threadInit(&threads[i], helper_consolePrinter);
|
||||
threads[i].data = &data[i];
|
||||
}
|
||||
for(int32_t i = 0; i < CONSOLE_THREAD_COUNT; i++) threadStart(&threads[i]);
|
||||
for(int32_t i = 0; i < CONSOLE_THREAD_COUNT; i++) threadStop(&threads[i]);
|
||||
|
||||
int32_t seenPerThread[CONSOLE_THREAD_COUNT] = { 0 };
|
||||
for(int32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) {
|
||||
int32_t threadIndex, seq;
|
||||
assert_true(parsePrintedLine(CONSOLE.line[i], &threadIndex, &seq));
|
||||
assert_true(threadIndex >= 0 && threadIndex < CONSOLE_THREAD_COUNT);
|
||||
assert_true(seq >= 0 && seq < iterations);
|
||||
seenPerThread[threadIndex]++;
|
||||
}
|
||||
|
||||
// Every message from every thread survived, since total prints == capacity.
|
||||
for(int32_t i = 0; i < CONSOLE_THREAD_COUNT; i++) {
|
||||
assert_int_equal(seenPerThread[i], iterations);
|
||||
}
|
||||
|
||||
consoleDispose();
|
||||
}
|
||||
|
||||
static void test_consolePrintConcurrentStressNoCorruption(void **state) {
|
||||
// Far more prints than history capacity, from multiple threads at once.
|
||||
// If printMutex didn't actually serialize the shift+copy in consolePrint,
|
||||
// concurrent writers would tear each other's memoryMove/memoryCopy calls
|
||||
// and this would surface as lines failing to parse as "T<n>-<seq>".
|
||||
consoleInit();
|
||||
|
||||
const int32_t iterations = 500;
|
||||
consoleprinter_data_t data[CONSOLE_THREAD_COUNT];
|
||||
thread_t threads[CONSOLE_THREAD_COUNT];
|
||||
|
||||
for(int32_t i = 0; i < CONSOLE_THREAD_COUNT; i++) {
|
||||
data[i].threadIndex = i;
|
||||
data[i].iterations = iterations;
|
||||
threadInit(&threads[i], helper_consolePrinter);
|
||||
threads[i].data = &data[i];
|
||||
}
|
||||
for(int32_t i = 0; i < CONSOLE_THREAD_COUNT; i++) threadStart(&threads[i]);
|
||||
for(int32_t i = 0; i < CONSOLE_THREAD_COUNT; i++) threadStop(&threads[i]);
|
||||
|
||||
for(int32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) {
|
||||
int32_t threadIndex, seq;
|
||||
assert_true(parsePrintedLine(CONSOLE.line[i], &threadIndex, &seq));
|
||||
assert_true(threadIndex >= 0 && threadIndex < CONSOLE_THREAD_COUNT);
|
||||
assert_true(seq >= 0 && seq < iterations);
|
||||
}
|
||||
|
||||
consoleDispose();
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test_consoleInitDefaults),
|
||||
cmocka_unit_test(test_consolePrintAddsNewestAtEnd),
|
||||
cmocka_unit_test(test_consolePrintShiftsHistory),
|
||||
cmocka_unit_test(test_consolePrintOverflowDropsOldest),
|
||||
cmocka_unit_test(test_consolePrintConcurrentExactlyFillsHistory),
|
||||
cmocka_unit_test(test_consolePrintConcurrentStressNoCorruption),
|
||||
};
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
}
|
||||
@@ -26,6 +26,19 @@ static errorret_t helper_chain(void) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
// Helper that throws with no format arguments (exercises the zero-arg
|
||||
// ##__VA_ARGS__ branch of the errorThrow macro).
|
||||
static errorret_t helper_throwNoArgs(void) {
|
||||
errorThrow("Simple message with no arguments");
|
||||
}
|
||||
|
||||
static const errorcode_t CUSTOM_ERROR_CODE = 7;
|
||||
|
||||
// Helper that throws with an explicit, non-default error code.
|
||||
static errorret_t helper_throwWithCode(void) {
|
||||
errorThrowWithCode(CUSTOM_ERROR_CODE, "Custom error %d", 99);
|
||||
}
|
||||
|
||||
static void test_errorThrow(void **state) {
|
||||
errorret_t ret = helper_throw();
|
||||
|
||||
@@ -84,6 +97,159 @@ static void test_errorChain(void **state) {
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_errorThrowNoFormatArguments(void **state) {
|
||||
errorret_t ret = helper_throwNoArgs();
|
||||
|
||||
assert_int_not_equal(ret.code, ERROR_OK);
|
||||
assert_string_equal(ret.state->message, "Simple message with no arguments");
|
||||
|
||||
errorCatch(ret);
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_errorThrowWithCode(void **state) {
|
||||
errorret_t ret = helper_throwWithCode();
|
||||
|
||||
// The explicit code must round-trip, not just "not OK".
|
||||
assert_int_equal(ret.code, CUSTOM_ERROR_CODE);
|
||||
assert_non_null(strstr(ret.state->message, "99"));
|
||||
|
||||
errorCatch(ret);
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_errorChainOkRetvalPassesThroughUnchanged(void **state) {
|
||||
// errorChainImpl's own early-return branch, called directly since the
|
||||
// errorChain macro pre-filters on code != ERROR_OK before ever reaching it.
|
||||
errorret_t ok = helper_ok();
|
||||
errorret_t chained = errorChainImpl(ok, __FILE__, __func__, __LINE__);
|
||||
|
||||
assert_int_equal(chained.code, ERROR_OK);
|
||||
assert_null(chained.state);
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_errorPrintOkRetvalPassesThroughUnchanged(void **state) {
|
||||
errorret_t ok = helper_ok();
|
||||
errorret_t printed = errorPrint(ok);
|
||||
|
||||
assert_int_equal(printed.code, ERROR_OK);
|
||||
assert_null(printed.state);
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_errorPrintErrorRetvalPassesThroughUnchanged(void **state) {
|
||||
errorret_t ret = helper_throw();
|
||||
errorret_t printed = errorPrint(ret);
|
||||
|
||||
// errorPrint logs, but must not mutate or consume the error state.
|
||||
assert_int_equal(printed.code, ret.code);
|
||||
assert_ptr_equal(printed.state, ret.state);
|
||||
assert_non_null(printed.state->message);
|
||||
|
||||
errorCatch(printed);
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
// --- Negative / misuse paths ---
|
||||
//
|
||||
// These bypass the errorThrow/errorChain/errorOk macros to call the *Impl
|
||||
// functions directly with invalid arguments, since correct macro usage can
|
||||
// never produce these states (they always supply a valid &ERROR_STATE,
|
||||
// __FILE__, __func__, __LINE__).
|
||||
|
||||
static void test_errorThrowImplNullStateAsserts(void **state) {
|
||||
expect_assert_failure(
|
||||
errorThrowImpl(NULL, ERROR_NOT_OK, __FILE__, __func__, __LINE__, "msg")
|
||||
);
|
||||
}
|
||||
|
||||
static void test_errorThrowImplOkCodeAsserts(void **state) {
|
||||
errorstate_t local = { 0 };
|
||||
expect_assert_failure(
|
||||
errorThrowImpl(&local, ERROR_OK, __FILE__, __func__, __LINE__, "msg")
|
||||
);
|
||||
}
|
||||
|
||||
static void test_errorThrowImplNullFileAsserts(void **state) {
|
||||
errorstate_t local = { 0 };
|
||||
expect_assert_failure(
|
||||
errorThrowImpl(&local, ERROR_NOT_OK, NULL, __func__, __LINE__, "msg")
|
||||
);
|
||||
}
|
||||
|
||||
static void test_errorThrowImplNullFunctionAsserts(void **state) {
|
||||
errorstate_t local = { 0 };
|
||||
expect_assert_failure(
|
||||
errorThrowImpl(&local, ERROR_NOT_OK, __FILE__, NULL, __LINE__, "msg")
|
||||
);
|
||||
}
|
||||
|
||||
static void test_errorThrowImplNegativeLineAsserts(void **state) {
|
||||
errorstate_t local = { 0 };
|
||||
expect_assert_failure(
|
||||
errorThrowImpl(&local, ERROR_NOT_OK, __FILE__, __func__, -1, "msg")
|
||||
);
|
||||
}
|
||||
|
||||
static void test_errorThrowImplNullMessageAsserts(void **state) {
|
||||
errorstate_t local = { 0 };
|
||||
expect_assert_failure(
|
||||
errorThrowImpl(&local, ERROR_NOT_OK, __FILE__, __func__, __LINE__, NULL)
|
||||
);
|
||||
}
|
||||
|
||||
static void test_errorOkAssertsIfPreviousErrorNotCaught(void **state) {
|
||||
// Simulate a caller forgetting to errorCatch a previous throw: leave
|
||||
// ERROR_STATE.code non-OK, then errorOk() must assert rather than silently
|
||||
// returning an OK that papers over the uncaught error.
|
||||
assert_int_equal(ERROR_STATE.code, ERROR_OK);
|
||||
ERROR_STATE.code = ERROR_NOT_OK;
|
||||
|
||||
expect_assert_failure(helper_ok());
|
||||
|
||||
// Nothing was actually allocated for this fake corruption - just restore
|
||||
// the global so later tests see a clean state.
|
||||
ERROR_STATE.code = ERROR_OK;
|
||||
}
|
||||
|
||||
static void test_errorChainImplNullStateAsserts(void **state) {
|
||||
errorret_t bad = { .code = ERROR_NOT_OK, .state = NULL };
|
||||
expect_assert_failure(
|
||||
errorChainImpl(bad, __FILE__, __func__, __LINE__)
|
||||
);
|
||||
}
|
||||
|
||||
static void test_errorChainImplNullMessageAsserts(void **state) {
|
||||
errorstate_t local = { .code = ERROR_NOT_OK, .message = NULL, .lines = NULL };
|
||||
errorret_t bad = { .code = ERROR_NOT_OK, .state = &local };
|
||||
expect_assert_failure(
|
||||
errorChainImpl(bad, __FILE__, __func__, __LINE__)
|
||||
);
|
||||
}
|
||||
|
||||
static void test_errorCatchNullStateAsserts(void **state) {
|
||||
errorret_t bad = { .code = ERROR_NOT_OK, .state = NULL };
|
||||
expect_assert_failure(errorCatch(bad));
|
||||
}
|
||||
|
||||
static void test_errorCatchNullMessageAsserts(void **state) {
|
||||
errorstate_t local = { .code = ERROR_NOT_OK, .message = NULL, .lines = NULL };
|
||||
errorret_t bad = { .code = ERROR_NOT_OK, .state = &local };
|
||||
expect_assert_failure(errorCatch(bad));
|
||||
}
|
||||
|
||||
static void test_errorPrintNullStateAsserts(void **state) {
|
||||
errorret_t bad = { .code = ERROR_NOT_OK, .state = NULL };
|
||||
expect_assert_failure(errorPrint(bad));
|
||||
}
|
||||
|
||||
static void test_errorPrintNullMessageAsserts(void **state) {
|
||||
errorstate_t local = { .code = ERROR_NOT_OK, .message = NULL, .lines = NULL };
|
||||
errorret_t bad = { .code = ERROR_NOT_OK, .state = &local };
|
||||
expect_assert_failure(errorPrint(bad));
|
||||
}
|
||||
|
||||
static void test_errorCatch_ok(void **state) {
|
||||
// Catching an ok ret should be a no-op
|
||||
errorret_t ret = helper_ok();
|
||||
@@ -178,6 +344,24 @@ int main(void) {
|
||||
cmocka_unit_test(test_errorOk),
|
||||
cmocka_unit_test(test_errorIsOk),
|
||||
cmocka_unit_test(test_errorChain),
|
||||
cmocka_unit_test(test_errorThrowNoFormatArguments),
|
||||
cmocka_unit_test(test_errorThrowWithCode),
|
||||
cmocka_unit_test(test_errorChainOkRetvalPassesThroughUnchanged),
|
||||
cmocka_unit_test(test_errorPrintOkRetvalPassesThroughUnchanged),
|
||||
cmocka_unit_test(test_errorPrintErrorRetvalPassesThroughUnchanged),
|
||||
cmocka_unit_test(test_errorThrowImplNullStateAsserts),
|
||||
cmocka_unit_test(test_errorThrowImplOkCodeAsserts),
|
||||
cmocka_unit_test(test_errorThrowImplNullFileAsserts),
|
||||
cmocka_unit_test(test_errorThrowImplNullFunctionAsserts),
|
||||
cmocka_unit_test(test_errorThrowImplNegativeLineAsserts),
|
||||
cmocka_unit_test(test_errorThrowImplNullMessageAsserts),
|
||||
cmocka_unit_test(test_errorOkAssertsIfPreviousErrorNotCaught),
|
||||
cmocka_unit_test(test_errorChainImplNullStateAsserts),
|
||||
cmocka_unit_test(test_errorChainImplNullMessageAsserts),
|
||||
cmocka_unit_test(test_errorCatchNullStateAsserts),
|
||||
cmocka_unit_test(test_errorCatchNullMessageAsserts),
|
||||
cmocka_unit_test(test_errorPrintNullStateAsserts),
|
||||
cmocka_unit_test(test_errorPrintNullMessageAsserts),
|
||||
cmocka_unit_test(test_errorCatch_ok),
|
||||
cmocka_unit_test(test_errorCatch_error),
|
||||
cmocka_unit_test(test_error_thread_isolation),
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
include(dusktest)
|
||||
|
||||
# Tests
|
||||
dusktest(test_event.c)
|
||||
@@ -0,0 +1,443 @@
|
||||
/**
|
||||
* 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, ¶ms);
|
||||
|
||||
assert_int_equal(recordA.callCount, 1);
|
||||
assert_ptr_equal(recordA.lastParams, ¶ms);
|
||||
assert_ptr_equal(recordA.lastUser, &recordA);
|
||||
|
||||
assert_int_equal(recordB.callCount, 1);
|
||||
assert_ptr_equal(recordB.lastParams, ¶ms);
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user