some random fixes

This commit is contained in:
2026-08-08 01:18:07 -05:00
parent 36fb359aa2
commit 128f9ab9d4
15 changed files with 1041 additions and 52 deletions
+13 -6
View File
@@ -73,9 +73,13 @@ void animationUpdate(
loop && pingpong, loop && pingpong,
"Cannot set both ANIMATION_FLAG_LOOP and ANIMATION_FLAG_PINGPONG." "Cannot set both ANIMATION_FLAG_LOOP and ANIMATION_FLAG_PINGPONG."
); );
bool_t backward = pingpong
? (anim->flags & ANIMATION_FLAG_INTERNAL_PINGPONG_BACKWARD) != 0 bool_t backward;
: (anim->flags & ANIMATION_FLAG_REVERSE) != 0; if(pingpong) {
backward = (anim->flags & ANIMATION_FLAG_INTERNAL_PINGPONG_BACKWARD) != 0;
} else {
backward = (anim->flags & ANIMATION_FLAG_REVERSE) != 0;
}
// Resolve boundary crossings one at a time, so a single large deltaTime // Resolve boundary crossings one at a time, so a single large deltaTime
// can correctly loop/pingpong across multiple boundaries in one call. // can correctly loop/pingpong across multiple boundaries in one call.
@@ -92,9 +96,12 @@ void animationUpdate(
remaining -= toBoundary; remaining -= toBoundary;
anim->time = backward ? 0.0f : anim->duration; anim->time = backward ? 0.0f : anim->duration;
bool_t stopHere = backward bool_t stopHere;
? (anim->flags & ANIMATION_FLAG_STOP_BEGINNING) != 0 if(backward) {
: (anim->flags & ANIMATION_FLAG_STOP_END) != 0; stopHere = (anim->flags & ANIMATION_FLAG_STOP_BEGINNING) != 0;
} else {
stopHere = (anim->flags & ANIMATION_FLAG_STOP_END) != 0;
}
if(stopHere) { if(stopHere) {
justCompleted = true; justCompleted = true;
+4 -15
View File
@@ -18,10 +18,7 @@ console_t CONSOLE;
void consoleInit(void) { void consoleInit(void) {
memoryZero(&CONSOLE, sizeof(console_t)); memoryZero(&CONSOLE, sizeof(console_t));
CONSOLE.visible = false; CONSOLE.visible = false;
threadMutexInit(&CONSOLE.printMutex);
#ifdef DUSK_CONSOLE_POSIX
threadMutexInit(&CONSOLE.printMutex);
#endif
} }
void consolePrint(const char_t *message, ...) { void consolePrint(const char_t *message, ...) {
@@ -32,20 +29,14 @@ void consolePrint(const char_t *message, ...) {
int32_t len = stringFormatVA(buffer, CONSOLE_LINE_MAX, message, args); int32_t len = stringFormatVA(buffer, CONSOLE_LINE_MAX, message, args);
va_end(args); va_end(args);
#ifdef DUSK_CONSOLE_POSIX threadMutexLock(&CONSOLE.printMutex);
threadMutexLock(&CONSOLE.printMutex);
#endif
memoryMove( memoryMove(
CONSOLE.line[0], CONSOLE.line[0],
CONSOLE.line[1], CONSOLE.line[1],
(CONSOLE_HISTORY_MAX - 1) * CONSOLE_LINE_MAX (CONSOLE_HISTORY_MAX - 1) * CONSOLE_LINE_MAX
); );
memoryCopy(CONSOLE.line[CONSOLE_HISTORY_MAX - 1], buffer, len + 1); memoryCopy(CONSOLE.line[CONSOLE_HISTORY_MAX - 1], buffer, len + 1);
threadMutexUnlock(&CONSOLE.printMutex);
#ifdef DUSK_CONSOLE_POSIX
threadMutexUnlock(&CONSOLE.printMutex);
#endif
logDebug("%s\n", buffer); logDebug("%s\n", buffer);
} }
@@ -61,7 +52,5 @@ void consoleUpdate(void) {
} }
void consoleDispose(void) { void consoleDispose(void) {
#ifdef DUSK_CONSOLE_POSIX threadMutexDispose(&CONSOLE.printMutex);
threadMutexDispose(&CONSOLE.printMutex);
#endif
} }
+5 -11
View File
@@ -6,24 +6,18 @@
*/ */
#pragma once #pragma once
#include "consoledefs.h"
#include "error/error.h" #include "error/error.h"
#include "dusk.h" #include "dusk.h"
#include "thread/thread.h"
#ifdef DUSK_CONSOLE_POSIX #define CONSOLE_LINE_MAX 512
#include "thread/thread.h" #define CONSOLE_HISTORY_MAX 16
#include <poll.h> #define CONSOLE_EXEC_BUFFER_MAX 32
#include <unistd.h>
#define CONSOLE_POSIX_POLL_RATE 75
#endif
typedef struct { typedef struct {
char_t line[CONSOLE_HISTORY_MAX][CONSOLE_LINE_MAX]; char_t line[CONSOLE_HISTORY_MAX][CONSOLE_LINE_MAX];
bool_t visible; bool_t visible;
threadmutex_t printMutex;
#ifdef DUSK_CONSOLE_POSIX
threadmutex_t printMutex;
#endif
} console_t; } console_t;
extern console_t CONSOLE; extern console_t CONSOLE;
-12
View File
@@ -1,12 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#define CONSOLE_LINE_MAX 512
#define CONSOLE_HISTORY_MAX 16
#define CONSOLE_EXEC_BUFFER_MAX 32
+8 -4
View File
@@ -33,13 +33,17 @@ errorret_t displayInit(void) {
#ifdef displayPlatformInit #ifdef displayPlatformInit
errorChain(displayPlatformInit()); errorChain(displayPlatformInit());
#endif #endif
// Set initial state
errorChain(displaySetState((displaystate_t){ .flags = 0 })); errorChain(displaySetState((displaystate_t){ .flags = 0 }));
// Init the fixed textures
errorChain(textureInit( errorChain(textureInit(
&TEXTURE_WHITE, 4, 4, &TEXTURE_WHITE, TEXTURE_FIXED_WIDTH, TEXTURE_FIXED_HEIGHT,
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_WHITE_PIXELS } TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_WHITE_PIXELS }
)); ));
errorChain(textureInit( errorChain(textureInit(
&TEXTURE_TEST, 4, 4, &TEXTURE_TEST, TEXTURE_FIXED_WIDTH, TEXTURE_FIXED_HEIGHT,
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_TEST_PIXELS } TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_TEST_PIXELS }
)); ));
@@ -50,14 +54,14 @@ errorret_t displayInit(void) {
errorChain(planeInit()); errorChain(planeInit());
errorChain(capsuleInit()); errorChain(capsuleInit());
errorChain(triPrismInit()); errorChain(triPrismInit());
// Init the subsystems
errorChain(frameBufferInitBackBuffer()); errorChain(frameBufferInitBackBuffer());
errorChain(spriteBatchInit()); errorChain(spriteBatchInit());
errorChain(textInit()); errorChain(textInit());
errorChain(screenInit()); errorChain(screenInit());
// Setup initial shader with default values // Setup initial shader with default values
errorChain(shaderListInit()); errorChain(shaderListInit());
errorOk(); errorOk();
+2 -2
View File
@@ -12,7 +12,7 @@
#include "display/display.h" #include "display/display.h"
texture_t TEXTURE_WHITE; texture_t TEXTURE_WHITE;
color_t TEXTURE_WHITE_PIXELS[4*4] = { color_t TEXTURE_WHITE_PIXELS[TEXTURE_FIXED_WIDTH * TEXTURE_FIXED_HEIGHT] = {
COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE,
COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE,
COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE,
@@ -20,7 +20,7 @@ color_t TEXTURE_WHITE_PIXELS[4*4] = {
}; };
texture_t TEXTURE_TEST; texture_t TEXTURE_TEST;
color_t TEXTURE_TEST_PIXELS[4*4] = { color_t TEXTURE_TEST_PIXELS[TEXTURE_FIXED_WIDTH * TEXTURE_FIXED_HEIGHT] = {
COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA,
COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK,
COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA,
+5 -2
View File
@@ -17,6 +17,9 @@
#error "textureDisposePlatform should not be defined." #error "textureDisposePlatform should not be defined."
#endif #endif
#define TEXTURE_FIXED_WIDTH 4
#define TEXTURE_FIXED_HEIGHT 4
typedef textureformatplatform_t textureformat_t; typedef textureformatplatform_t textureformat_t;
typedef textureplatform_t texture_t; typedef textureplatform_t texture_t;
@@ -29,9 +32,9 @@ typedef union texturedata_u {
} texturedata_t; } texturedata_t;
extern texture_t TEXTURE_WHITE; extern texture_t TEXTURE_WHITE;
extern color_t TEXTURE_WHITE_PIXELS[4*4]; extern color_t TEXTURE_WHITE_PIXELS[TEXTURE_FIXED_WIDTH * TEXTURE_FIXED_HEIGHT];
extern texture_t TEXTURE_TEST; extern texture_t TEXTURE_TEST;
extern color_t TEXTURE_TEST_PIXELS[4*4]; extern color_t TEXTURE_TEST_PIXELS[TEXTURE_FIXED_WIDTH * TEXTURE_FIXED_HEIGHT];
/** /**
* Initializes a texture. * Initializes a texture.
+2
View File
@@ -6,7 +6,9 @@
add_subdirectory(animation) add_subdirectory(animation)
add_subdirectory(assert) add_subdirectory(assert)
add_subdirectory(asset) add_subdirectory(asset)
add_subdirectory(console)
add_subdirectory(error) add_subdirectory(error)
add_subdirectory(event)
add_subdirectory(thread) add_subdirectory(thread)
add_subdirectory(display) add_subdirectory(display)
add_subdirectory(rpg) add_subdirectory(rpg)
+1
View File
@@ -6,5 +6,6 @@
include(dusktest) include(dusktest)
# Tests # Tests
dusktest(test_easing.c)
dusktest(test_keyframe.c) dusktest(test_keyframe.c)
dusktest(test_animation.c) dusktest(test_animation.c)
+188
View File
@@ -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);
}
+9
View File
@@ -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)
+168
View File
@@ -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);
}
+184
View File
@@ -26,6 +26,19 @@ static errorret_t helper_chain(void) {
errorOk(); 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) { static void test_errorThrow(void **state) {
errorret_t ret = helper_throw(); errorret_t ret = helper_throw();
@@ -84,6 +97,159 @@ static void test_errorChain(void **state) {
assert_int_equal(memoryGetAllocatedCount(), 0); 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) { static void test_errorCatch_ok(void **state) {
// Catching an ok ret should be a no-op // Catching an ok ret should be a no-op
errorret_t ret = helper_ok(); errorret_t ret = helper_ok();
@@ -178,6 +344,24 @@ int main(void) {
cmocka_unit_test(test_errorOk), cmocka_unit_test(test_errorOk),
cmocka_unit_test(test_errorIsOk), cmocka_unit_test(test_errorIsOk),
cmocka_unit_test(test_errorChain), 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_ok),
cmocka_unit_test(test_errorCatch_error), cmocka_unit_test(test_errorCatch_error),
cmocka_unit_test(test_error_thread_isolation), cmocka_unit_test(test_error_thread_isolation),
+9
View File
@@ -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)
+443
View File
@@ -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, &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);
}