Files
dusk/test/error/test_error.c
T
2026-06-01 10:57:40 -05:00

188 lines
4.9 KiB
C

/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "dusktest.h"
#include "error/error.h"
#include "thread/thread.h"
#include "util/memory.h"
// Helper that throws an error.
static errorret_t helper_throw(void) {
errorThrow("Test error %d", 42);
}
// Helper that returns ok.
static errorret_t helper_ok(void) {
errorOk();
}
// Helper that chains to helper_throw.
static errorret_t helper_chain(void) {
errorChain(helper_throw());
errorOk();
}
static void test_errorThrow(void **state) {
errorret_t ret = helper_throw();
assert_int_not_equal(ret.code, ERROR_OK);
assert_non_null(ret.state);
assert_non_null(ret.state->message);
assert_non_null(ret.state->lines);
// Message should contain our format argument
assert_non_null(strstr(ret.state->message, "42"));
errorCatch(ret);
assert_int_equal(memoryGetAllocatedCount(), 0);
}
static void test_errorOk(void **state) {
errorret_t ret = helper_ok();
assert_int_equal(ret.code, ERROR_OK);
assert_null(ret.state);
assert_int_equal(memoryGetAllocatedCount(), 0);
}
static void test_errorIsOk(void **state) {
errorret_t ok = helper_ok();
assert_true(errorIsOk(ok));
assert_false(errorIsNotOk(ok));
errorret_t err = helper_throw();
assert_false(errorIsOk(err));
assert_true(errorIsNotOk(err));
errorCatch(err);
assert_int_equal(memoryGetAllocatedCount(), 0);
}
static void test_errorChain(void **state) {
errorret_t ret = helper_chain();
// Error propagated up
assert_int_not_equal(ret.code, ERROR_OK);
assert_non_null(ret.state);
assert_non_null(ret.state->lines);
// Lines should contain at least two stack entries
int32_t count = 0;
const char_t *p = ret.state->lines;
while((p = strstr(p, " at ")) != NULL) {
count++;
p++;
}
assert_true(count >= 2);
errorCatch(ret);
assert_int_equal(memoryGetAllocatedCount(), 0);
}
static void test_errorCatch_ok(void **state) {
// Catching an ok ret should be a no-op
errorret_t ret = helper_ok();
errorCatch(ret);
assert_int_equal(memoryGetAllocatedCount(), 0);
}
static void test_errorCatch_error(void **state) {
errorret_t ret = helper_throw();
assert_int_not_equal(ret.code, ERROR_OK);
errorCatch(ret);
// After catch the global state should be cleared
assert_int_equal(ERROR_STATE.code, ERROR_OK);
assert_int_equal(memoryGetAllocatedCount(), 0);
}
// --- Threaded tests ---
typedef struct {
errorcode_t capturedCode;
bool_t messageHas42;
} thread_test_data_t;
static void helper_thread_throw(thread_t *thread) {
errorret_t ret = helper_throw();
thread_test_data_t *data = (thread_test_data_t *)thread->data;
data->capturedCode = ERROR_STATE.code;
data->messageHas42 = (strstr(ret.state->message, "42") != NULL);
errorCatch(ret);
}
static void test_error_thread_isolation(void **state) {
// Main thread state is clean before the test.
assert_int_equal(ERROR_STATE.code, ERROR_OK);
thread_test_data_t data = { .capturedCode = ERROR_OK, .messageHas42 = false };
thread_t thread;
threadInit(&thread, helper_thread_throw);
thread.data = &data;
threadStart(&thread);
threadStop(&thread);
// Worker saw ERROR_NOT_OK in its own ERROR_STATE.
assert_int_equal(data.capturedCode, ERROR_NOT_OK);
assert_true(data.messageHas42);
// Main thread ERROR_STATE was not touched by the worker.
assert_int_equal(ERROR_STATE.code, ERROR_OK);
assert_int_equal(memoryGetAllocatedCount(), 0);
}
#define CONCURRENT_THREAD_COUNT 4
static thread_test_data_t concurrent_data[CONCURRENT_THREAD_COUNT];
static thread_t concurrent_threads[CONCURRENT_THREAD_COUNT];
static void test_error_concurrent_throw(void **state) {
for(int32_t i = 0; i < CONCURRENT_THREAD_COUNT; i++) {
concurrent_data[i].capturedCode = ERROR_OK;
concurrent_data[i].messageHas42 = false;
threadInit(&concurrent_threads[i], helper_thread_throw);
concurrent_threads[i].data = &concurrent_data[i];
}
for(int32_t i = 0; i < CONCURRENT_THREAD_COUNT; i++) {
threadStart(&concurrent_threads[i]);
}
for(int32_t i = 0; i < CONCURRENT_THREAD_COUNT; i++) {
threadStop(&concurrent_threads[i]);
}
// Every worker must have seen its own independent error.
for(int32_t i = 0; i < CONCURRENT_THREAD_COUNT; i++) {
assert_int_equal(concurrent_data[i].capturedCode, ERROR_NOT_OK);
assert_true(concurrent_data[i].messageHas42);
}
// Main thread is still clean.
assert_int_equal(ERROR_STATE.code, ERROR_OK);
assert_int_equal(memoryGetAllocatedCount(), 0);
}
int main(void) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_errorThrow),
cmocka_unit_test(test_errorOk),
cmocka_unit_test(test_errorIsOk),
cmocka_unit_test(test_errorChain),
cmocka_unit_test(test_errorCatch_ok),
cmocka_unit_test(test_errorCatch_error),
cmocka_unit_test(test_error_thread_isolation),
cmocka_unit_test(test_error_concurrent_throw),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}