/* * Copyright (c) 2023, Meta * * SPDX-License-Identifier: Apache-2.0 */ #include "thrd.h" #include #include #include #define WAIT_TIME_MS 100 static struct libc_cnd_fixture { /* shared between threads in tests */ cnd_t cond; mtx_t mutex; /* de-duplicate local variables in test cases */ int res1; int res2; thrd_t thrd1; thrd_t thrd2; bool do_timedwait; bool is_broadcast; struct timespec time_point; } _libc_cnd_fixture; ZTEST_F(libc_cnd, test_cnd_init_destroy) { /* degenerate cases */ if (false) { /* pthread_cond_init() pthread_cond_destroy() are not hardened against these */ zassert_equal(thrd_error, cnd_init(NULL)); zassert_equal(thrd_error, cnd_init((cnd_t *)BIOS_FOOD)); cnd_destroy(NULL); cnd_destroy((cnd_t *)BIOS_FOOD); } /* happy path tested in before() / after() */ } ZTEST_F(libc_cnd, test_cnd_errors) { /* degenerate test cases */ if (false) { /* pthread_cond_*() are not hardened against these */ zassert_equal(thrd_error, cnd_signal(NULL)); zassert_equal(thrd_error, cnd_broadcast(NULL)); zassert_equal(thrd_error, cnd_wait(NULL, NULL)); zassert_equal(thrd_error, cnd_wait(NULL, &fixture->mutex)); zassert_equal(thrd_error, cnd_wait(&fixture->cond, NULL)); zassert_equal(thrd_error, cnd_timedwait(NULL, NULL, NULL)); zassert_equal(thrd_error, cnd_timedwait(NULL, NULL, &fixture->time_point)); zassert_equal(thrd_error, cnd_timedwait(NULL, &fixture->mutex, NULL)); zassert_equal(thrd_error, cnd_timedwait(NULL, &fixture->mutex, &fixture->time_point)); zassert_equal(thrd_error, cnd_timedwait(&fixture->cond, NULL, NULL)); zassert_equal(thrd_error, cnd_timedwait(&fixture->cond, NULL, &fixture->time_point)); zassert_equal(thrd_error, cnd_timedwait(&fixture->cond, &fixture->mutex, NULL)); } } static int test_cnd_thread_fn(void *arg) { int res = thrd_success; struct timespec time_point; struct libc_cnd_fixture *const fixture = arg; if (fixture->do_timedwait) { zassume_ok(clock_gettime(CLOCK_MONOTONIC, &time_point)); timespec_add_ms(&time_point, WAIT_TIME_MS); res = cnd_timedwait(&fixture->cond, &fixture->mutex, &time_point); } else { res = cnd_wait(&fixture->cond, &fixture->mutex); } if (fixture->is_broadcast) { /* re-signal so that the next thread wakes up too */ zassert_equal(thrd_success, cnd_signal(&fixture->cond)); } (void)mtx_unlock(&fixture->mutex); return res; } static void tst_cnd_common(struct libc_cnd_fixture *fixture, size_t wait_ms, bool th2, int exp1, int exp2) { zassert_equal(thrd_success, mtx_lock(&fixture->mutex)); zassert_equal(thrd_success, thrd_create(&fixture->thrd1, test_cnd_thread_fn, fixture)); if (th2) { zassert_equal(thrd_success, thrd_create(&fixture->thrd2, test_cnd_thread_fn, fixture)); } k_msleep(wait_ms); if (fixture->is_broadcast) { zassert_equal(thrd_success, cnd_broadcast(&fixture->cond)); } else { zassert_equal(thrd_success, cnd_signal(&fixture->cond)); } zassert_equal(thrd_success, mtx_unlock(&fixture->mutex)); zassert_equal(thrd_success, thrd_join(fixture->thrd1, &fixture->res1)); if (th2) { zassert_equal(thrd_success, thrd_join(fixture->thrd2, &fixture->res2)); } zassert_equal(exp1, fixture->res1); if (th2) { zassert_equal(exp2, fixture->res2); } } ZTEST_F(libc_cnd, test_cnd_signal_wait) { tst_cnd_common(fixture, WAIT_TIME_MS / 2, false, thrd_success, DONT_CARE); } ZTEST_F(libc_cnd, test_cnd_signal_timedwait) { fixture->do_timedwait = true; tst_cnd_common(fixture, WAIT_TIME_MS / 2, false, thrd_success, DONT_CARE); } ZTEST_F(libc_cnd, test_cnd_timedwait_timeout) { fixture->do_timedwait = true; tst_cnd_common(fixture, WAIT_TIME_MS * 2, false, thrd_timedout, DONT_CARE); } ZTEST_F(libc_cnd, test_cnd_broadcast_wait) { fixture->is_broadcast = true; tst_cnd_common(fixture, WAIT_TIME_MS, true, thrd_success, thrd_success); } static void *setup(void) { return &_libc_cnd_fixture; } static void before(void *arg) { struct libc_cnd_fixture *const fixture = arg; *fixture = (struct libc_cnd_fixture){ .res1 = FORTY_TWO, .res2 = SEVENTY_THREE, }; zassert_equal(thrd_success, mtx_init(&fixture->mutex, mtx_plain)); zassert_equal(thrd_success, cnd_init(&fixture->cond)); } static void after(void *arg) { struct libc_cnd_fixture *const fixture = arg; cnd_destroy(&fixture->cond); mtx_destroy(&fixture->mutex); } ZTEST_SUITE(libc_cnd, NULL, setup, before, after, NULL);