some random fixes
This commit is contained in:
@@ -73,9 +73,13 @@ void animationUpdate(
|
||||
loop && pingpong,
|
||||
"Cannot set both ANIMATION_FLAG_LOOP and ANIMATION_FLAG_PINGPONG."
|
||||
);
|
||||
bool_t backward = pingpong
|
||||
? (anim->flags & ANIMATION_FLAG_INTERNAL_PINGPONG_BACKWARD) != 0
|
||||
: (anim->flags & ANIMATION_FLAG_REVERSE) != 0;
|
||||
|
||||
bool_t backward;
|
||||
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
|
||||
// can correctly loop/pingpong across multiple boundaries in one call.
|
||||
@@ -92,9 +96,12 @@ void animationUpdate(
|
||||
remaining -= toBoundary;
|
||||
anim->time = backward ? 0.0f : anim->duration;
|
||||
|
||||
bool_t stopHere = backward
|
||||
? (anim->flags & ANIMATION_FLAG_STOP_BEGINNING) != 0
|
||||
: (anim->flags & ANIMATION_FLAG_STOP_END) != 0;
|
||||
bool_t stopHere;
|
||||
if(backward) {
|
||||
stopHere = (anim->flags & ANIMATION_FLAG_STOP_BEGINNING) != 0;
|
||||
} else {
|
||||
stopHere = (anim->flags & ANIMATION_FLAG_STOP_END) != 0;
|
||||
}
|
||||
|
||||
if(stopHere) {
|
||||
justCompleted = true;
|
||||
|
||||
@@ -18,10 +18,7 @@ console_t CONSOLE;
|
||||
void consoleInit(void) {
|
||||
memoryZero(&CONSOLE, sizeof(console_t));
|
||||
CONSOLE.visible = false;
|
||||
|
||||
#ifdef DUSK_CONSOLE_POSIX
|
||||
threadMutexInit(&CONSOLE.printMutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
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);
|
||||
va_end(args);
|
||||
|
||||
#ifdef DUSK_CONSOLE_POSIX
|
||||
threadMutexLock(&CONSOLE.printMutex);
|
||||
#endif
|
||||
|
||||
memoryMove(
|
||||
CONSOLE.line[0],
|
||||
CONSOLE.line[1],
|
||||
(CONSOLE_HISTORY_MAX - 1) * CONSOLE_LINE_MAX
|
||||
);
|
||||
memoryCopy(CONSOLE.line[CONSOLE_HISTORY_MAX - 1], buffer, len + 1);
|
||||
|
||||
#ifdef DUSK_CONSOLE_POSIX
|
||||
threadMutexUnlock(&CONSOLE.printMutex);
|
||||
#endif
|
||||
|
||||
logDebug("%s\n", buffer);
|
||||
}
|
||||
@@ -61,7 +52,5 @@ void consoleUpdate(void) {
|
||||
}
|
||||
|
||||
void consoleDispose(void) {
|
||||
#ifdef DUSK_CONSOLE_POSIX
|
||||
threadMutexDispose(&CONSOLE.printMutex);
|
||||
#endif
|
||||
}
|
||||
@@ -6,24 +6,18 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "consoledefs.h"
|
||||
#include "error/error.h"
|
||||
#include "dusk.h"
|
||||
#include "thread/thread.h"
|
||||
|
||||
#ifdef DUSK_CONSOLE_POSIX
|
||||
#include "thread/thread.h"
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
#define CONSOLE_POSIX_POLL_RATE 75
|
||||
#endif
|
||||
#define CONSOLE_LINE_MAX 512
|
||||
#define CONSOLE_HISTORY_MAX 16
|
||||
#define CONSOLE_EXEC_BUFFER_MAX 32
|
||||
|
||||
typedef struct {
|
||||
char_t line[CONSOLE_HISTORY_MAX][CONSOLE_LINE_MAX];
|
||||
bool_t visible;
|
||||
|
||||
#ifdef DUSK_CONSOLE_POSIX
|
||||
threadmutex_t printMutex;
|
||||
#endif
|
||||
} console_t;
|
||||
|
||||
extern console_t CONSOLE;
|
||||
|
||||
@@ -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
|
||||
@@ -33,13 +33,17 @@ errorret_t displayInit(void) {
|
||||
#ifdef displayPlatformInit
|
||||
errorChain(displayPlatformInit());
|
||||
#endif
|
||||
|
||||
// Set initial state
|
||||
errorChain(displaySetState((displaystate_t){ .flags = 0 }));
|
||||
|
||||
// Init the fixed textures
|
||||
errorChain(textureInit(
|
||||
&TEXTURE_WHITE, 4, 4,
|
||||
&TEXTURE_WHITE, TEXTURE_FIXED_WIDTH, TEXTURE_FIXED_HEIGHT,
|
||||
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_WHITE_PIXELS }
|
||||
));
|
||||
errorChain(textureInit(
|
||||
&TEXTURE_TEST, 4, 4,
|
||||
&TEXTURE_TEST, TEXTURE_FIXED_WIDTH, TEXTURE_FIXED_HEIGHT,
|
||||
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_TEST_PIXELS }
|
||||
));
|
||||
|
||||
@@ -51,13 +55,13 @@ errorret_t displayInit(void) {
|
||||
errorChain(capsuleInit());
|
||||
errorChain(triPrismInit());
|
||||
|
||||
// Init the subsystems
|
||||
errorChain(frameBufferInitBackBuffer());
|
||||
errorChain(spriteBatchInit());
|
||||
errorChain(textInit());
|
||||
errorChain(screenInit());
|
||||
|
||||
// Setup initial shader with default values
|
||||
|
||||
errorChain(shaderListInit());
|
||||
|
||||
errorOk();
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "display/display.h"
|
||||
|
||||
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,
|
||||
@@ -20,7 +20,7 @@ color_t TEXTURE_WHITE_PIXELS[4*4] = {
|
||||
};
|
||||
|
||||
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_MAGENTA, COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK,
|
||||
COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA,
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
#error "textureDisposePlatform should not be defined."
|
||||
#endif
|
||||
|
||||
#define TEXTURE_FIXED_WIDTH 4
|
||||
#define TEXTURE_FIXED_HEIGHT 4
|
||||
|
||||
typedef textureformatplatform_t textureformat_t;
|
||||
typedef textureplatform_t texture_t;
|
||||
|
||||
@@ -29,9 +32,9 @@ typedef union texturedata_u {
|
||||
} texturedata_t;
|
||||
|
||||
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 color_t TEXTURE_TEST_PIXELS[4*4];
|
||||
extern color_t TEXTURE_TEST_PIXELS[TEXTURE_FIXED_WIDTH * TEXTURE_FIXED_HEIGHT];
|
||||
|
||||
/**
|
||||
* Initializes a texture.
|
||||
|
||||
@@ -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