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
+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);
}