diff --git a/sys/Makefile b/sys/Makefile new file mode 100644 index 00000000..5bdf242a --- /dev/null +++ b/sys/Makefile @@ -0,0 +1,20 @@ +# Custom name as LOCAL_DIR is auto-reset by binary.mk +MY_LOCAL_DIR := $(call my-dir) + +SYS_UNIT_TESTS := + +# Quick template for adding sys tests from subdir (makes test-sys-xxx binary from xxx/*.c) +# $(eval $(call add_sys_test,NAME[,LOCAL_LDFLAGS][,LOCAL_CFLAGS])) +define add_test_sys +NAME := test-sys-$(1) +SYS_UNIT_TESTS += test-sys-$(1) +SRCS := $(wildcard $(MY_LOCAL_DIR)$(1)/*.c) +DEP_LIBS := unity +LOCAL_CFLAGS := -fno-builtin $(3) +LOCAL_LDFLAGS := $(2) + +include $(binary.mk) +endef + +$(eval $(call add_test_sys,cond)) +$(eval $(call add_test_sys,mutex)) diff --git a/sys/cond/cond.c b/sys/cond/cond.c new file mode 100644 index 00000000..5afa9a31 --- /dev/null +++ b/sys/cond/cond.c @@ -0,0 +1,817 @@ +/* + * Phoenix-RTOS + * + * test-sys-cond + * + * Test for condition variables + * + * Copyright 2024 Phoenix Systems + * Author: Lukasz Leczkowski + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + + +#include +#include +#include +#include +#include +#include + +#include + + +#define SIGNAL_TEST_TIMEOUT 10000 +#define BROADCAST_TEST_TIMEOUT 100000 + + +typedef struct { + useconds_t delay; + int id; +} signal_thread_args_t; + + +typedef struct { + time_t timeout; + int id; + int thrCount; +} worker_thread_args_t; + + +static struct { + handle_t mutex; + handle_t cond; + handle_t readyMutex; + handle_t readyCond; + volatile int readyCounter; + volatile int counter; + volatile int thrErrors[2]; + volatile int thrTimeout[2]; + char stack[2][4096] __attribute__((aligned(8))); +} common; + + +/* + * Tests for relative condWait expecting timeout are not implemented as they're unreliable + * (depend on e.g. scheduling/system load) and may fail in some cases. + */ + + +/* + *----------------------------------- TESTING THREADS -------------------------------------* + */ + + +static void signal_thread(void *arg) +{ + signal_thread_args_t *args = (signal_thread_args_t *)arg; + struct timespec timeout = { .tv_sec = 0, .tv_nsec = args->delay * 1000 }, rem; + + if (args->delay != 0) { + while (nanosleep(&timeout, &rem) < 0) { + if (errno != EINTR) { + common.thrErrors[args->id]++; + } + else { + timeout = rem; + } + } + } + + if (mutexLock(common.mutex) < 0) { + common.thrErrors[args->id]++; + } + common.counter++; + if (condSignal(common.cond) < 0) { + common.thrErrors[args->id]++; + } + if (mutexUnlock(common.mutex) < 0) { + common.thrErrors[args->id]++; + } + + endthread(); +} + + +static void worker_thread_signal_test(void *arg) +{ + worker_thread_args_t *args = (worker_thread_args_t *)arg; + + if (mutexLock(common.mutex) < 0) { + common.thrErrors[args->id]++; + } + + if (mutexLock(common.readyMutex) < 0) { + common.thrErrors[args->id]++; + } + common.readyCounter++; + if (common.readyCounter == args->thrCount) { + if (condSignal(common.readyCond) < 0) { + common.thrErrors[args->id]++; + } + } + if (mutexUnlock(common.readyMutex) < 0) { + common.thrErrors[args->id]++; + } + + int err = condWait(common.cond, common.mutex, args->timeout); + + if (mutexLock(common.readyMutex) < 0) { + common.thrErrors[args->id]++; + } + if (err == 0) { + common.counter++; + } + else if (err == -ETIME) { + common.thrTimeout[args->id]++; + } + else { + common.thrErrors[args->id]++; + } + + common.readyCounter++; + + /* Signal to the waiting thread that we're done */ + if (condSignal(common.readyCond) < 0) { + common.thrErrors[args->id]++; + } + if (mutexUnlock(common.readyMutex) < 0) { + common.thrErrors[args->id]++; + } + + if (mutexUnlock(common.mutex) < 0) { + common.thrErrors[args->id]++; + } + + endthread(); +} + + +static void worker_thread_broadcast_test(void *arg) +{ + worker_thread_args_t *args = (worker_thread_args_t *)arg; + + if (mutexLock(common.mutex) < 0) { + common.thrErrors[args->id]++; + } + + if (mutexLock(common.readyMutex)) { + common.thrErrors[args->id]++; + } + common.readyCounter++; + if (common.readyCounter == args->thrCount) { + if (condSignal(common.readyCond) < 0) { + common.thrErrors[args->id]; + } + } + if (mutexUnlock(common.readyMutex) < 0) { + common.thrErrors[args->id]++; + } + + int err = condWait(common.cond, common.mutex, args->timeout); + if (err == 0) { + common.counter++; + } + else if (err == -ETIME) { + common.thrTimeout[args->id]++; + } + else { + common.thrErrors[args->id]++; + } + if (mutexUnlock(common.mutex) < 0) { + common.thrErrors[args->id]++; + } + + endthread(); +} + + +/* +/////////////////////////////////////////////////////////////////////////////////////////////// +*/ + + +TEST_GROUP(condvar_invalid_params); +TEST_GROUP(condvar_signal); +TEST_GROUP(condvar_broadcast); + + +/* + *--------------------------------- INVALID PARAMS TESTS -----------------------------------* + */ + + +TEST_SETUP(condvar_invalid_params) +{ +} + + +TEST_TEAR_DOWN(condvar_invalid_params) +{ +} + + +TEST(condvar_invalid_params, invalid_attr) +{ + handle_t cond; + struct condAttr attr = { .clock = -1 }; + + TEST_ASSERT_EQUAL_INT(-EINVAL, condCreateWithAttr(&cond, &attr)); +} + + +TEST(condvar_invalid_params, invalid_cond) +{ + TEST_ASSERT_EQUAL_INT(-EINVAL, condWait(-1, -1, 0)); + TEST_ASSERT_EQUAL_INT(-EINVAL, condSignal(-1)); + TEST_ASSERT_EQUAL_INT(-EINVAL, condBroadcast(-1)); +} + + +/* + *------------------------------------- SIGNAL TESTS ---------------------------------------* + */ + + +TEST_SETUP(condvar_signal) +{ + common.counter = 0; + common.readyCounter = 0; + memset((void *)common.thrErrors, 0, sizeof(common.thrErrors)); + memset((void *)common.thrTimeout, 0, sizeof(common.thrTimeout)); + TEST_ASSERT_EQUAL_INT(0, mutexCreate(&common.mutex)); +} + + +TEST_TEAR_DOWN(condvar_signal) +{ + TEST_ASSERT_EQUAL_INT(0, resourceDestroy(common.mutex)); + TEST_ASSERT_EQUAL_INT(0, resourceDestroy(common.cond)); +} + + +TEST(condvar_signal, default_no_timeout) +{ + handle_t tid; + signal_thread_args_t args = { .delay = 0, .id = 0 }; + + TEST_ASSERT_EQUAL_INT(0, condCreate(&common.cond)); + + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.mutex)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(signal_thread, 4, common.stack[0], sizeof(common.stack[0]), &args, &tid)); + + TEST_ASSERT_EQUAL_INT(0, condWait(common.cond, common.mutex, SIGNAL_TEST_TIMEOUT)); + TEST_ASSERT_EQUAL_INT(1, common.counter); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.mutex)); + + TEST_ASSERT_EQUAL_INT(tid, threadJoin(tid, 0)); + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[0]); +} + + +TEST(condvar_signal, relative_no_timeout) +{ + handle_t tid; + signal_thread_args_t args = { .delay = 0, .id = 0 }; + struct condAttr attr = { .clock = PH_CLOCK_RELATIVE }; + + TEST_ASSERT_EQUAL_INT(0, condCreateWithAttr(&common.cond, &attr)); + + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.mutex)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(signal_thread, 4, common.stack[0], sizeof(common.stack[0]), &args, &tid)); + + TEST_ASSERT_EQUAL_INT(0, condWait(common.cond, common.mutex, SIGNAL_TEST_TIMEOUT)); + TEST_ASSERT_EQUAL_INT(1, common.counter); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.mutex)); + + TEST_ASSERT_EQUAL_INT(tid, threadJoin(tid, 0)); + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[0]); +} + + +TEST(condvar_signal, monotonic_no_timeout) +{ + handle_t tid; + time_t timeout; + signal_thread_args_t args = { .delay = 0, .id = 0 }; + struct condAttr attr = { .clock = PH_CLOCK_MONOTONIC }; + + TEST_ASSERT_EQUAL_INT(0, condCreateWithAttr(&common.cond, &attr)); + + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.mutex)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(signal_thread, 4, common.stack[0], sizeof(common.stack[0]), &args, &tid)); + + TEST_ASSERT_EQUAL_INT(0, gettime(&timeout, NULL)); + timeout += SIGNAL_TEST_TIMEOUT; + TEST_ASSERT_EQUAL_INT(0, condWait(common.cond, common.mutex, timeout)); + TEST_ASSERT_EQUAL_INT(1, common.counter); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.mutex)); + + TEST_ASSERT_EQUAL_INT(tid, threadJoin(tid, 0)); + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[0]); +} + + +TEST(condvar_signal, realtime_no_timeout) +{ + handle_t tid; + time_t timeout, offs; + signal_thread_args_t args = { .delay = 0, .id = 0 }; + struct condAttr attr = { .clock = PH_CLOCK_REALTIME }; + + TEST_ASSERT_EQUAL_INT(0, settime(50000)); + TEST_ASSERT_EQUAL_INT(0, condCreateWithAttr(&common.cond, &attr)); + + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.mutex)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(signal_thread, 4, common.stack[0], sizeof(common.stack[0]), &args, &tid)); + + TEST_ASSERT_EQUAL_INT(0, gettime(&timeout, &offs)); + timeout += SIGNAL_TEST_TIMEOUT + offs; + TEST_ASSERT_EQUAL_INT(0, condWait(common.cond, common.mutex, timeout)); + TEST_ASSERT_EQUAL_INT(1, common.counter); + + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.mutex)); + + TEST_ASSERT_EQUAL_INT(tid, threadJoin(tid, 0)); + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[0]); +} + + +TEST(condvar_signal, monotonic_timeout) +{ + handle_t tid; + time_t timeout; + signal_thread_args_t args = { .delay = 2000, .id = 0 }; + struct condAttr attr = { .clock = PH_CLOCK_MONOTONIC }; + + TEST_ASSERT_EQUAL_INT(0, condCreateWithAttr(&common.cond, &attr)); + + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.mutex)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(signal_thread, 4, common.stack[0], sizeof(common.stack[0]), &args, &tid)); + + TEST_ASSERT_EQUAL_INT(0, gettime(&timeout, NULL)); + timeout += 1000; + TEST_ASSERT_EQUAL_INT(-ETIME, condWait(common.cond, common.mutex, timeout)); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.mutex)); + + TEST_ASSERT_EQUAL_INT(tid, threadJoin(tid, 0)); + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[0]); +} + + +TEST(condvar_signal, realtime_timeout) +{ + handle_t tid; + time_t timeout, offs; + signal_thread_args_t args = { .delay = 2000, .id = 0 }; + struct condAttr attr = { .clock = PH_CLOCK_REALTIME }; + + TEST_ASSERT_EQUAL_INT(0, settime(50000)); + TEST_ASSERT_EQUAL_INT(0, condCreateWithAttr(&common.cond, &attr)); + + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.mutex)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(signal_thread, 4, common.stack[0], sizeof(common.stack[0]), &args, &tid)); + + TEST_ASSERT_EQUAL_INT(0, gettime(&timeout, &offs)); + timeout += 1000 + offs; + TEST_ASSERT_EQUAL_INT(-ETIME, condWait(common.cond, common.mutex, timeout)); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.mutex)); + + TEST_ASSERT_EQUAL_INT(tid, threadJoin(tid, 0)); + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[0]); +} + + +TEST(condvar_signal, monotonic_past_time) +{ + handle_t tid; + time_t timeout; + signal_thread_args_t args = { .delay = 0, .id = 0 }; + struct condAttr attr = { .clock = PH_CLOCK_MONOTONIC }; + + TEST_ASSERT_EQUAL_INT(0, condCreateWithAttr(&common.cond, &attr)); + + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.mutex)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(signal_thread, 4, common.stack[0], sizeof(common.stack[0]), &args, &tid)); + + TEST_ASSERT_EQUAL_INT(0, gettime(&timeout, NULL)); + timeout -= 1000; + TEST_ASSERT_EQUAL_INT(-ETIME, condWait(common.cond, common.mutex, timeout)); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.mutex)); + + TEST_ASSERT_EQUAL_INT(tid, threadJoin(tid, 0)); + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[0]); +} + + +TEST(condvar_signal, realtime_past_time) +{ + handle_t tid; + time_t timeout, offs; + signal_thread_args_t args = { .delay = 0, .id = 0 }; + struct condAttr attr = { .clock = PH_CLOCK_REALTIME }; + + TEST_ASSERT_EQUAL_INT(0, settime(50000)); + TEST_ASSERT_EQUAL_INT(0, condCreateWithAttr(&common.cond, &attr)); + + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.mutex)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(signal_thread, 4, common.stack[0], sizeof(common.stack[0]), &args, &tid)); + + TEST_ASSERT_EQUAL_INT(0, gettime(&timeout, &offs)); + timeout -= 1000 + offs; + TEST_ASSERT_EQUAL_INT(-ETIME, condWait(common.cond, common.mutex, timeout)); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.mutex)); + + TEST_ASSERT_EQUAL_INT(tid, threadJoin(tid, 0)); + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[0]); +} + + +TEST(condvar_signal, multiple_threads) +{ + const int thrCount = 2; + handle_t tid1, tid2; + worker_thread_args_t args1 = { .timeout = 0, .thrCount = thrCount, .id = 0 }; + worker_thread_args_t args2 = { .timeout = 0, .thrCount = thrCount, .id = 1 }; + + TEST_ASSERT_EQUAL_INT(0, condCreate(&common.cond)); + TEST_ASSERT_EQUAL_INT(0, condCreate(&common.readyCond)); + TEST_ASSERT_EQUAL_INT(0, mutexCreate(&common.readyMutex)); + + TEST_ASSERT_EQUAL_INT(0, beginthreadex(worker_thread_signal_test, 3, common.stack[0], sizeof(common.stack[0]), &args1, &tid1)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(worker_thread_signal_test, 3, common.stack[1], sizeof(common.stack[1]), &args2, &tid2)); + + /* Wait for the threads to start */ + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.readyMutex)); + while (common.readyCounter < thrCount) { + TEST_ASSERT_EQUAL_INT(0, condWait(common.readyCond, common.readyMutex, 0)); + } + common.readyCounter = 0; + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.readyMutex)); + + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.mutex)); + TEST_ASSERT_EQUAL_INT(0, condSignal(common.cond)); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.mutex)); + + /* Wait for one thread to wakeup */ + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.readyMutex)); + while (common.readyCounter == 0) { + TEST_ASSERT_EQUAL_INT(0, condWait(common.readyCond, common.readyMutex, 0)); + } + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.readyMutex)); + + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.mutex)); + /* Verify that only 1 thread woke up */ + TEST_ASSERT_EQUAL_INT(1, common.counter); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.mutex)); + + /* Signal to 2nd thread */ + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.mutex)); + TEST_ASSERT_EQUAL_INT(0, condSignal(common.cond)); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.mutex)); + + /* Wait for the threads to finish */ + TEST_ASSERT_EQUAL_INT(tid1, threadJoin(tid1, 0)); + TEST_ASSERT_EQUAL_INT(tid2, threadJoin(tid2, 0)); + + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[0]); + TEST_ASSERT_EQUAL_INT(0, common.thrTimeout[0]); + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[1]); + TEST_ASSERT_EQUAL_INT(0, common.thrTimeout[1]); + TEST_ASSERT_EQUAL_INT(thrCount, common.counter); + + TEST_ASSERT_EQUAL_INT(0, resourceDestroy(common.readyMutex)); + TEST_ASSERT_EQUAL_INT(0, resourceDestroy(common.readyCond)); +} + + +/* + *----------------------------------- BROADCAST TESTS -------------------------------------* + */ + + +TEST_SETUP(condvar_broadcast) +{ + common.counter = 0; + common.readyCounter = 0; + memset((void *)common.thrErrors, 0, sizeof(common.thrErrors)); + memset((void *)common.thrTimeout, 0, sizeof(common.thrTimeout)); + TEST_ASSERT_EQUAL_INT(0, mutexCreate(&common.mutex)); + TEST_ASSERT_EQUAL_INT(0, condCreate(&common.readyCond)); + TEST_ASSERT_EQUAL_INT(0, mutexCreate(&common.readyMutex)); +} + + +TEST_TEAR_DOWN(condvar_broadcast) +{ + TEST_ASSERT_EQUAL_INT(0, resourceDestroy(common.mutex)); + TEST_ASSERT_EQUAL_INT(0, resourceDestroy(common.cond)); + TEST_ASSERT_EQUAL_INT(0, resourceDestroy(common.readyMutex)); + TEST_ASSERT_EQUAL_INT(0, resourceDestroy(common.readyCond)); +} + + +TEST(condvar_broadcast, default_no_timeout) +{ + const int thrCount = 2; + const time_t timeout = BROADCAST_TEST_TIMEOUT; + handle_t tid1, tid2; + worker_thread_args_t args1 = { .timeout = timeout, .id = 0, .thrCount = thrCount }; + worker_thread_args_t args2 = { .timeout = timeout, .id = 1, .thrCount = thrCount }; + + TEST_ASSERT_EQUAL_INT(0, condCreate(&common.cond)); + + TEST_ASSERT_EQUAL_INT(0, beginthreadex(worker_thread_broadcast_test, 3, common.stack[0], sizeof(common.stack[0]), &args1, &tid1)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(worker_thread_broadcast_test, 3, common.stack[1], sizeof(common.stack[1]), &args2, &tid2)); + + /* Wait for the threads to start */ + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.readyMutex)); + while (common.readyCounter < thrCount) { + TEST_ASSERT_EQUAL_INT(0, condWait(common.readyCond, common.readyMutex, 0)); + } + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.readyMutex)); + + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.mutex)); + TEST_ASSERT_EQUAL_INT(0, condBroadcast(common.cond)); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.mutex)); + + TEST_ASSERT_EQUAL_INT(tid1, threadJoin(tid1, 0)); + TEST_ASSERT_EQUAL_INT(tid2, threadJoin(tid2, 0)); + + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[0]); + TEST_ASSERT_EQUAL_INT(0, common.thrTimeout[0]); + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[1]); + TEST_ASSERT_EQUAL_INT(0, common.thrTimeout[1]); + TEST_ASSERT_EQUAL_INT(thrCount, common.counter); +} + + +TEST(condvar_broadcast, relative_no_timeout) +{ + const int thrCount = 2; + const time_t timeout = BROADCAST_TEST_TIMEOUT; + handle_t tid1, tid2; + worker_thread_args_t args1 = { .timeout = timeout, .id = 0, .thrCount = thrCount }; + worker_thread_args_t args2 = { .timeout = timeout, .id = 1, .thrCount = thrCount }; + struct condAttr attr = { .clock = PH_CLOCK_RELATIVE }; + + TEST_ASSERT_EQUAL_INT(0, condCreateWithAttr(&common.cond, &attr)); + + TEST_ASSERT_EQUAL_INT(0, beginthreadex(worker_thread_broadcast_test, 3, common.stack[0], sizeof(common.stack[0]), &args1, &tid1)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(worker_thread_broadcast_test, 3, common.stack[1], sizeof(common.stack[1]), &args2, &tid2)); + + /* Wait for the threads to start */ + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.readyMutex)); + while (common.readyCounter < thrCount) { + TEST_ASSERT_EQUAL_INT(0, condWait(common.readyCond, common.readyMutex, 0)); + } + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.readyMutex)); + + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.mutex)); + TEST_ASSERT_EQUAL_INT(0, condBroadcast(common.cond)); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.mutex)); + + TEST_ASSERT_EQUAL_INT(tid1, threadJoin(tid1, 0)); + TEST_ASSERT_EQUAL_INT(tid2, threadJoin(tid2, 0)); + + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[0]); + TEST_ASSERT_EQUAL_INT(0, common.thrTimeout[0]); + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[1]); + TEST_ASSERT_EQUAL_INT(0, common.thrTimeout[1]); + TEST_ASSERT_EQUAL_INT(thrCount, common.counter); +} + + +TEST(condvar_broadcast, monotonic_no_timeout) +{ + const int thrCount = 2; + handle_t tid1, tid2; + time_t timeout; + worker_thread_args_t args1 = { .id = 0, .thrCount = thrCount }; + worker_thread_args_t args2 = { .id = 1, .thrCount = thrCount }; + struct condAttr attr = { .clock = PH_CLOCK_MONOTONIC }; + + TEST_ASSERT_EQUAL_INT(0, condCreateWithAttr(&common.cond, &attr)); + + TEST_ASSERT_EQUAL_INT(0, gettime(&timeout, NULL)); + timeout += BROADCAST_TEST_TIMEOUT; + + args1.timeout = timeout; + args2.timeout = timeout; + + TEST_ASSERT_EQUAL_INT(0, beginthreadex(worker_thread_broadcast_test, 3, common.stack[0], sizeof(common.stack[0]), &args1, &tid1)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(worker_thread_broadcast_test, 3, common.stack[1], sizeof(common.stack[1]), &args2, &tid2)); + + /* Wait for the threads to start */ + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.readyMutex)); + while (common.readyCounter < thrCount) { + TEST_ASSERT_EQUAL_INT(0, condWait(common.readyCond, common.readyMutex, 0)); + } + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.readyMutex)); + + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.mutex)); + TEST_ASSERT_EQUAL_INT(0, condBroadcast(common.cond)); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.mutex)); + + TEST_ASSERT_EQUAL_INT(tid1, threadJoin(tid1, 0)); + TEST_ASSERT_EQUAL_INT(tid2, threadJoin(tid2, 0)); + + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[0]); + TEST_ASSERT_EQUAL_INT(0, common.thrTimeout[0]); + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[1]); + TEST_ASSERT_EQUAL_INT(0, common.thrTimeout[1]); + TEST_ASSERT_EQUAL_INT(thrCount, common.counter); +} + + +TEST(condvar_broadcast, realtime_no_timeout) +{ + const int thrCount = 2; + handle_t tid1, tid2; + time_t timeout, offs; + worker_thread_args_t args1 = { .id = 0, .thrCount = thrCount }; + worker_thread_args_t args2 = { .id = 1, .thrCount = thrCount }; + struct condAttr attr = { .clock = PH_CLOCK_REALTIME }; + + TEST_ASSERT_EQUAL_INT(0, settime(50000)); + TEST_ASSERT_EQUAL_INT(0, condCreateWithAttr(&common.cond, &attr)); + + TEST_ASSERT_EQUAL_INT(0, gettime(&timeout, &offs)); + timeout += BROADCAST_TEST_TIMEOUT + offs; + + args1.timeout = timeout; + args2.timeout = timeout; + + TEST_ASSERT_EQUAL_INT(0, beginthreadex(worker_thread_broadcast_test, 3, common.stack[0], sizeof(common.stack[0]), &args1, &tid1)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(worker_thread_broadcast_test, 3, common.stack[1], sizeof(common.stack[1]), &args2, &tid2)); + + /* Wait for the threads to start */ + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.readyMutex)); + while (common.readyCounter < thrCount) { + TEST_ASSERT_EQUAL_INT(0, condWait(common.readyCond, common.readyMutex, 0)); + } + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.readyMutex)); + + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.mutex)); + TEST_ASSERT_EQUAL_INT(0, condBroadcast(common.cond)); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.mutex)); + + TEST_ASSERT_EQUAL_INT(tid1, threadJoin(tid1, 0)); + TEST_ASSERT_EQUAL_INT(tid2, threadJoin(tid2, 0)); + + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[0]); + TEST_ASSERT_EQUAL_INT(0, common.thrTimeout[0]); + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[1]); + TEST_ASSERT_EQUAL_INT(0, common.thrTimeout[1]); + TEST_ASSERT_EQUAL_INT(thrCount, common.counter); +} + + +TEST(condvar_broadcast, monotonic_timeout) +{ + const int thrCount = 2; + handle_t tid1, tid2; + time_t timeout; + worker_thread_args_t args1 = { .id = 0, .thrCount = thrCount }; + worker_thread_args_t args2 = { .id = 1, .thrCount = thrCount }; + struct condAttr attr = { .clock = PH_CLOCK_MONOTONIC }; + struct timespec wait = { .tv_sec = 0, .tv_nsec = 2000 * 1000 }, rem; + + TEST_ASSERT_EQUAL_INT(0, condCreateWithAttr(&common.cond, &attr)); + + TEST_ASSERT_EQUAL_INT(0, gettime(&timeout, NULL)); + timeout += 1000; + + args1.timeout = timeout; + args2.timeout = timeout; + + TEST_ASSERT_EQUAL_INT(0, beginthreadex(worker_thread_broadcast_test, 3, common.stack[0], sizeof(common.stack[0]), &args1, &tid1)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(worker_thread_broadcast_test, 3, common.stack[1], sizeof(common.stack[1]), &args2, &tid2)); + + /* Wait for the threads to start */ + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.readyMutex)); + while (common.readyCounter < thrCount) { + TEST_ASSERT_EQUAL_INT(0, condWait(common.readyCond, common.readyMutex, 0)); + } + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.readyMutex)); + + while (nanosleep(&wait, &rem) < 0) { + TEST_ASSERT_EQUAL(EINTR, errno); + wait = rem; + } + + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.mutex)); + TEST_ASSERT_EQUAL_INT(0, condBroadcast(common.cond)); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.mutex)); + + TEST_ASSERT_EQUAL_INT(tid1, threadJoin(tid1, 0)); + TEST_ASSERT_EQUAL_INT(tid2, threadJoin(tid2, 0)); + + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[0]); + TEST_ASSERT_EQUAL_INT(1, common.thrTimeout[0]); + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[1]); + TEST_ASSERT_EQUAL_INT(1, common.thrTimeout[1]); + TEST_ASSERT_EQUAL_INT(0, common.counter); +} + + +TEST(condvar_broadcast, realtime_timeout) +{ + const int thrCount = 2; + handle_t tid1, tid2; + time_t timeout, offs; + worker_thread_args_t args1 = { .id = 0, .thrCount = thrCount }; + worker_thread_args_t args2 = { .id = 1, .thrCount = thrCount }; + struct condAttr attr = { .clock = PH_CLOCK_REALTIME }; + struct timespec wait = { .tv_sec = 0, .tv_nsec = 2000 * 1000 }, rem; + + TEST_ASSERT_EQUAL_INT(0, settime(50000)); + TEST_ASSERT_EQUAL_INT(0, condCreateWithAttr(&common.cond, &attr)); + + TEST_ASSERT_EQUAL_INT(0, gettime(&timeout, &offs)); + timeout += 1000 + offs; + + args1.timeout = timeout; + args2.timeout = timeout; + + TEST_ASSERT_EQUAL_INT(0, beginthreadex(worker_thread_broadcast_test, 3, common.stack[0], sizeof(common.stack[0]), &args1, &tid1)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(worker_thread_broadcast_test, 3, common.stack[1], sizeof(common.stack[1]), &args2, &tid2)); + + /* Wait for the threads to start */ + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.readyMutex)); + while (common.readyCounter < thrCount) { + TEST_ASSERT_EQUAL_INT(0, condWait(common.readyCond, common.readyMutex, 0)); + } + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.readyMutex)); + + while (nanosleep(&wait, &rem) < 0) { + TEST_ASSERT_EQUAL(EINTR, errno); + wait = rem; + } + + TEST_ASSERT_EQUAL_INT(0, mutexLock(common.mutex)); + TEST_ASSERT_EQUAL_INT(0, condBroadcast(common.cond)); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(common.mutex)); + + TEST_ASSERT_EQUAL_INT(tid1, threadJoin(tid1, 0)); + TEST_ASSERT_EQUAL_INT(tid2, threadJoin(tid2, 0)); + + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[0]); + TEST_ASSERT_EQUAL_INT(1, common.thrTimeout[0]); + TEST_ASSERT_EQUAL_INT(0, common.thrErrors[1]); + TEST_ASSERT_EQUAL_INT(1, common.thrTimeout[1]); + TEST_ASSERT_EQUAL_INT(0, common.counter); +} + +/* +/////////////////////////////////////////////////////////////////////////////////////////////// +*/ + + +TEST_GROUP_RUNNER(condvar_invalid_params) +{ + RUN_TEST_CASE(condvar_invalid_params, invalid_attr); + RUN_TEST_CASE(condvar_invalid_params, invalid_cond); +} + + +TEST_GROUP_RUNNER(condvar_signal) +{ + RUN_TEST_CASE(condvar_signal, default_no_timeout); + RUN_TEST_CASE(condvar_signal, relative_no_timeout); + RUN_TEST_CASE(condvar_signal, monotonic_no_timeout); + RUN_TEST_CASE(condvar_signal, realtime_no_timeout); + RUN_TEST_CASE(condvar_signal, monotonic_timeout); + RUN_TEST_CASE(condvar_signal, realtime_timeout); + RUN_TEST_CASE(condvar_signal, monotonic_past_time); + RUN_TEST_CASE(condvar_signal, realtime_past_time); + RUN_TEST_CASE(condvar_signal, multiple_threads); +} + + +TEST_GROUP_RUNNER(condvar_broadcast) +{ + RUN_TEST_CASE(condvar_broadcast, default_no_timeout); + RUN_TEST_CASE(condvar_broadcast, relative_no_timeout); + RUN_TEST_CASE(condvar_broadcast, monotonic_no_timeout); + RUN_TEST_CASE(condvar_broadcast, realtime_no_timeout); + RUN_TEST_CASE(condvar_broadcast, monotonic_timeout); + RUN_TEST_CASE(condvar_broadcast, realtime_timeout); +} + + +void runner(void) +{ + RUN_TEST_GROUP(condvar_invalid_params); + RUN_TEST_GROUP(condvar_signal); + RUN_TEST_GROUP(condvar_broadcast); +} + + +int main(int argc, char *argv[]) +{ + return (UnityMain(argc, (const char **)argv, runner) == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/sys/mutex/mutex.c b/sys/mutex/mutex.c new file mode 100644 index 00000000..189f373a --- /dev/null +++ b/sys/mutex/mutex.c @@ -0,0 +1,362 @@ +/* + * Phoenix-RTOS + * + * test-sys-mutex + * + * Test for mutexes + * + * Copyright 2024 Phoenix Systems + * Author: Lukasz Leczkowski + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + + +#include +#include +#include +#include +#include + +#include + + +/* +/////////////////////////////////////////////////////////////////////////////////////////////// +*/ + + +TEST_GROUP(mutex_invalid_params); +TEST_GROUP(mutex_single_thread); +TEST_GROUP(mutex_multithreaded); + + +/* + *--------------------------------- INVALID PARAMS TESTS -----------------------------------* + */ + + +TEST_SETUP(mutex_invalid_params) +{ +} + + +TEST_TEAR_DOWN(mutex_invalid_params) +{ +} + + +TEST(mutex_invalid_params, invalid_attr) +{ + handle_t mutex; + struct lockAttr attr = { .type = -1 }; + + TEST_ASSERT_EQUAL_INT(-EINVAL, mutexCreateWithAttr(&mutex, &attr)); +} + + +TEST(mutex_invalid_params, invalid_mutex) +{ + TEST_ASSERT_EQUAL_INT(-EINVAL, mutexLock(-1)); + TEST_ASSERT_EQUAL_INT(-EINVAL, mutexUnlock(-1)); + TEST_ASSERT_EQUAL_INT(-EINVAL, resourceDestroy(-1)); +} + + +/* + *--------------------------------- SINGLE THREADED TESTS ---------------------------------* + */ + + +TEST_SETUP(mutex_single_thread) +{ +} + + +TEST_TEAR_DOWN(mutex_single_thread) +{ +} + + +TEST(mutex_single_thread, no_attr) +{ + handle_t mutex; + + TEST_ASSERT_EQUAL_INT(0, mutexCreate(&mutex)); + TEST_ASSERT_EQUAL_INT(0, mutexLock(mutex)); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(mutex)); + TEST_ASSERT_EQUAL_INT(0, resourceDestroy(mutex)); +} + + +TEST(mutex_single_thread, type_default) +{ + /* Should behave the same way as with no attributes */ + handle_t mutex; + struct lockAttr attr = { .type = PH_LOCK_NORMAL }; + + TEST_ASSERT_EQUAL_INT(0, mutexCreateWithAttr(&mutex, &attr)); + TEST_ASSERT_EQUAL_INT(0, mutexLock(mutex)); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(mutex)); + TEST_ASSERT_EQUAL_INT(0, resourceDestroy(mutex)); +} + + +TEST(mutex_single_thread, type_errorcheck) +{ + handle_t mutex; + struct lockAttr attr = { .type = PH_LOCK_ERRORCHECK }; + + TEST_ASSERT_EQUAL_INT(0, mutexCreateWithAttr(&mutex, &attr)); + TEST_ASSERT_EQUAL_INT(0, mutexLock(mutex)); + TEST_ASSERT_EQUAL_INT(-EDEADLK, mutexLock(mutex)); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(mutex)); + /* DEBUG build will catch not-locked unlock, don't test it here */ + TEST_ASSERT_EQUAL_INT(0, resourceDestroy(mutex)); +} + + +TEST(mutex_single_thread, type_recursive) +{ + handle_t mutex; + struct lockAttr attr = { .type = PH_LOCK_RECURSIVE }; + + TEST_ASSERT_EQUAL_INT(0, mutexCreateWithAttr(&mutex, &attr)); + TEST_ASSERT_EQUAL_INT(0, mutexLock(mutex)); + TEST_ASSERT_EQUAL_INT(0, mutexLock(mutex)); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(mutex)); + TEST_ASSERT_EQUAL_INT(0, mutexUnlock(mutex)); + TEST_ASSERT_EQUAL_INT(0, resourceDestroy(mutex)); +} + + +/* + *--------------------------------- MULTITHREADED TESTS -----------------------------------* + */ + +typedef struct { + int id; + useconds_t delay; +} threadArg_t; + + +static struct { + handle_t mutex; + volatile int counter; + volatile int thrErrors[2]; + char stack[2][4096] __attribute__((aligned(8))); +} mt_common; + + +static void no_attr_thread(void *arg) +{ + threadArg_t *targ = (threadArg_t *)arg; + for (int i = 0; i < 100; i++) { + if ((mutexLock(mt_common.mutex)) < 0) { + mt_common.thrErrors[targ->id]++; + endthread(); + } + usleep(targ->delay); + if (targ->id == 0) { + mt_common.counter++; + } + else { + mt_common.counter--; + } + if ((mutexUnlock(mt_common.mutex)) < 0) { + mt_common.thrErrors[targ->id]++; + endthread(); + } + } + endthread(); +} + + +static void errorcheck_thread(void *arg) +{ + threadArg_t *targ = (threadArg_t *)arg; + for (int i = 0; i < 100; i++) { + if ((mutexLock(mt_common.mutex)) < 0) { + mt_common.thrErrors[targ->id]++; + endthread(); + } + if (mutexLock(mt_common.mutex) != -EDEADLK) { + mt_common.thrErrors[targ->id]++; + } + usleep(targ->delay); + if (targ->id == 0) { + mt_common.counter++; + } + else { + mt_common.counter--; + } + if ((mutexUnlock(mt_common.mutex)) < 0) { + mt_common.thrErrors[targ->id]++; + endthread(); + } + } + endthread(); +} + + +static void recursive_thread(void *arg) +{ + threadArg_t *targ = (threadArg_t *)arg; + for (int i = 0; i < 100; i++) { + if ((mutexLock(mt_common.mutex)) < 0) { + mt_common.thrErrors[targ->id]++; + endthread(); + } + if ((mutexLock(mt_common.mutex)) < 0) { + mt_common.thrErrors[targ->id]++; + endthread(); + } + if (targ->id == 0) { + mt_common.counter++; + } + else { + mt_common.counter--; + } + usleep(targ->delay); + if ((mutexUnlock(mt_common.mutex)) < 0) { + mt_common.thrErrors[targ->id]++; + endthread(); + } + if ((mutexUnlock(mt_common.mutex)) < 0) { + mt_common.thrErrors[targ->id]++; + endthread(); + } + } + endthread(); +} + + +TEST_SETUP(mutex_multithreaded) +{ + mt_common.counter = 0; + memset((void *)mt_common.thrErrors, 0, sizeof(mt_common.thrErrors)); +} + + +TEST_TEAR_DOWN(mutex_multithreaded) +{ + TEST_ASSERT_EQUAL_INT(0, resourceDestroy(mt_common.mutex)); +} + + +TEST(mutex_multithreaded, no_attr) +{ + handle_t tid1, tid2; + threadArg_t arg1 = { .id = 0, .delay = 1 }; + threadArg_t arg2 = { .id = 1, .delay = 3 }; + + TEST_ASSERT_EQUAL_INT(0, mutexCreate(&mt_common.mutex)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(no_attr_thread, 4, mt_common.stack[0], sizeof(mt_common.stack[0]), &arg1, &tid1)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(no_attr_thread, 4, mt_common.stack[1], sizeof(mt_common.stack[1]), &arg2, &tid2)); + TEST_ASSERT_EQUAL_INT(tid1, threadJoin(tid1, 0)); + TEST_ASSERT_EQUAL_INT(tid2, threadJoin(tid2, 0)); + TEST_ASSERT_EQUAL_INT(0, mt_common.counter); + TEST_ASSERT_EQUAL_INT(0, mt_common.thrErrors[0]); + TEST_ASSERT_EQUAL_INT(0, mt_common.thrErrors[1]); +} + + +TEST(mutex_multithreaded, type_default) +{ + /* Should behave the same way as with no attributes */ + handle_t tid1, tid2; + struct lockAttr attr = { .type = PH_LOCK_NORMAL }; + threadArg_t arg1 = { .id = 0, .delay = 1 }; + threadArg_t arg2 = { .id = 1, .delay = 3 }; + + TEST_ASSERT_EQUAL_INT(0, mutexCreateWithAttr(&mt_common.mutex, &attr)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(no_attr_thread, 4, mt_common.stack[0], sizeof(mt_common.stack[0]), &arg1, &tid1)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(no_attr_thread, 4, mt_common.stack[1], sizeof(mt_common.stack[1]), &arg2, &tid2)); + TEST_ASSERT_EQUAL_INT(tid1, threadJoin(tid1, 0)); + TEST_ASSERT_EQUAL_INT(tid2, threadJoin(tid2, 0)); + TEST_ASSERT_EQUAL_INT(0, mt_common.counter); + TEST_ASSERT_EQUAL_INT(0, mt_common.thrErrors[0]); + TEST_ASSERT_EQUAL_INT(0, mt_common.thrErrors[1]); +} + + +TEST(mutex_multithreaded, type_errorcheck) +{ + handle_t tid1, tid2; + struct lockAttr attr = { .type = PH_LOCK_ERRORCHECK }; + threadArg_t arg1 = { .id = 0, .delay = 1 }; + threadArg_t arg2 = { .id = 1, .delay = 3 }; + + TEST_ASSERT_EQUAL_INT(0, mutexCreateWithAttr(&mt_common.mutex, &attr)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(errorcheck_thread, 4, mt_common.stack[0], sizeof(mt_common.stack[0]), &arg1, &tid1)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(errorcheck_thread, 4, mt_common.stack[1], sizeof(mt_common.stack[1]), &arg2, &tid2)); + TEST_ASSERT_EQUAL_INT(tid1, threadJoin(tid1, 0)); + TEST_ASSERT_EQUAL_INT(tid2, threadJoin(tid2, 0)); + TEST_ASSERT_EQUAL_INT(0, mt_common.counter); + TEST_ASSERT_EQUAL_INT(0, mt_common.thrErrors[0]); + TEST_ASSERT_EQUAL_INT(0, mt_common.thrErrors[1]); +} + + +TEST(mutex_multithreaded, type_recursive) +{ + handle_t tid1, tid2; + struct lockAttr attr = { .type = PH_LOCK_RECURSIVE }; + threadArg_t arg1 = { .id = 0, .delay = 1 }; + threadArg_t arg2 = { .id = 1, .delay = 3 }; + + TEST_ASSERT_EQUAL_INT(0, mutexCreateWithAttr(&mt_common.mutex, &attr)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(recursive_thread, 4, mt_common.stack[0], sizeof(mt_common.stack[0]), &arg1, &tid1)); + TEST_ASSERT_EQUAL_INT(0, beginthreadex(recursive_thread, 4, mt_common.stack[1], sizeof(mt_common.stack[1]), &arg2, &tid2)); + TEST_ASSERT_EQUAL_INT(tid1, threadJoin(tid1, 0)); + TEST_ASSERT_EQUAL_INT(tid2, threadJoin(tid2, 0)); + TEST_ASSERT_EQUAL_INT(0, mt_common.counter); + TEST_ASSERT_EQUAL_INT(0, mt_common.thrErrors[0]); + TEST_ASSERT_EQUAL_INT(0, mt_common.thrErrors[1]); +} + + +/* +/////////////////////////////////////////////////////////////////////////////////////////////// +*/ + + +TEST_GROUP_RUNNER(mutex_invalid_params) +{ + RUN_TEST_CASE(mutex_invalid_params, invalid_attr); + RUN_TEST_CASE(mutex_invalid_params, invalid_mutex); +} + + +TEST_GROUP_RUNNER(mutex_single_thread) +{ + RUN_TEST_CASE(mutex_single_thread, no_attr); + RUN_TEST_CASE(mutex_single_thread, type_default); + RUN_TEST_CASE(mutex_single_thread, type_errorcheck); + RUN_TEST_CASE(mutex_single_thread, type_recursive); +} + + +TEST_GROUP_RUNNER(mutex_multithreaded) +{ + RUN_TEST_CASE(mutex_multithreaded, no_attr); + RUN_TEST_CASE(mutex_multithreaded, type_default); + RUN_TEST_CASE(mutex_multithreaded, type_errorcheck); + RUN_TEST_CASE(mutex_multithreaded, type_recursive); +} + + +void runner(void) +{ + RUN_TEST_GROUP(mutex_invalid_params); + RUN_TEST_GROUP(mutex_single_thread); + RUN_TEST_GROUP(mutex_multithreaded); +} + + +int main(int argc, char *argv[]) +{ + return (UnityMain(argc, (const char **)argv, runner) == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/sys/test.yaml b/sys/test.yaml new file mode 100644 index 00000000..8b1c5dc8 --- /dev/null +++ b/sys/test.yaml @@ -0,0 +1,8 @@ +test: + type: unity + tests: + - name: mutex + execute: test-sys-mutex + + - name: cond + execute: test-sys-cond