some random fixes
This commit is contained in:
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user