1 /*
2  * Copyright (c) 2023, Meta
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include "thrd.h"
8 
9 #include <stdint.h>
10 #include <threads.h>
11 
12 #include <zephyr/ztest.h>
13 
14 #define WAIT_TIME_MS 100
15 
16 static struct libc_cnd_fixture {
17 	/* shared between threads in tests */
18 	cnd_t cond;
19 	mtx_t mutex;
20 
21 	/* de-duplicate local variables in test cases */
22 	int res1;
23 	int res2;
24 	thrd_t thrd1;
25 	thrd_t thrd2;
26 	bool do_timedwait;
27 	bool is_broadcast;
28 	struct timespec time_point;
29 } _libc_cnd_fixture;
30 
ZTEST_F(libc_cnd,test_cnd_init_destroy)31 ZTEST_F(libc_cnd, test_cnd_init_destroy)
32 {
33 	/* degenerate cases */
34 	if (false) {
35 		/* pthread_cond_init() pthread_cond_destroy() are not hardened against these */
36 		zassert_equal(thrd_error, cnd_init(NULL));
37 		zassert_equal(thrd_error, cnd_init((cnd_t *)BIOS_FOOD));
38 		cnd_destroy(NULL);
39 		cnd_destroy((cnd_t *)BIOS_FOOD);
40 	}
41 
42 	/* happy path tested in before() / after() */
43 }
44 
ZTEST_F(libc_cnd,test_cnd_errors)45 ZTEST_F(libc_cnd, test_cnd_errors)
46 {
47 	/* degenerate test cases */
48 	if (false) {
49 		/* pthread_cond_*() are not hardened against these */
50 		zassert_equal(thrd_error, cnd_signal(NULL));
51 		zassert_equal(thrd_error, cnd_broadcast(NULL));
52 		zassert_equal(thrd_error, cnd_wait(NULL, NULL));
53 		zassert_equal(thrd_error, cnd_wait(NULL, &fixture->mutex));
54 		zassert_equal(thrd_error, cnd_wait(&fixture->cond, NULL));
55 		zassert_equal(thrd_error, cnd_timedwait(NULL, NULL, NULL));
56 		zassert_equal(thrd_error, cnd_timedwait(NULL, NULL, &fixture->time_point));
57 		zassert_equal(thrd_error, cnd_timedwait(NULL, &fixture->mutex, NULL));
58 		zassert_equal(thrd_error,
59 			      cnd_timedwait(NULL, &fixture->mutex, &fixture->time_point));
60 		zassert_equal(thrd_error, cnd_timedwait(&fixture->cond, NULL, NULL));
61 		zassert_equal(thrd_error,
62 			      cnd_timedwait(&fixture->cond, NULL, &fixture->time_point));
63 		zassert_equal(thrd_error, cnd_timedwait(&fixture->cond, &fixture->mutex, NULL));
64 	}
65 }
66 
test_cnd_thread_fn(void * arg)67 static int test_cnd_thread_fn(void *arg)
68 {
69 	int res = thrd_success;
70 	struct timespec time_point;
71 	struct libc_cnd_fixture *const fixture = arg;
72 
73 	if (fixture->do_timedwait) {
74 		zassume_ok(clock_gettime(CLOCK_MONOTONIC, &time_point));
75 		timespec_add_ms(&time_point, WAIT_TIME_MS);
76 		res = cnd_timedwait(&fixture->cond, &fixture->mutex, &time_point);
77 	} else {
78 		res = cnd_wait(&fixture->cond, &fixture->mutex);
79 	}
80 
81 	if (fixture->is_broadcast) {
82 		/* re-signal so that the next thread wakes up too */
83 		zassert_equal(thrd_success, cnd_signal(&fixture->cond));
84 	}
85 
86 	(void)mtx_unlock(&fixture->mutex);
87 
88 	return res;
89 }
90 
tst_cnd_common(struct libc_cnd_fixture * fixture,size_t wait_ms,bool th2,int exp1,int exp2)91 static void tst_cnd_common(struct libc_cnd_fixture *fixture, size_t wait_ms, bool th2, int exp1,
92 			   int exp2)
93 {
94 	zassert_equal(thrd_success, mtx_lock(&fixture->mutex));
95 
96 	zassert_equal(thrd_success, thrd_create(&fixture->thrd1, test_cnd_thread_fn, fixture));
97 	if (th2) {
98 		zassert_equal(thrd_success,
99 			      thrd_create(&fixture->thrd2, test_cnd_thread_fn, fixture));
100 	}
101 
102 	k_msleep(wait_ms);
103 
104 	if (fixture->is_broadcast) {
105 		zassert_equal(thrd_success, cnd_broadcast(&fixture->cond));
106 	} else {
107 		zassert_equal(thrd_success, cnd_signal(&fixture->cond));
108 	}
109 
110 	zassert_equal(thrd_success, mtx_unlock(&fixture->mutex));
111 
112 	zassert_equal(thrd_success, thrd_join(fixture->thrd1, &fixture->res1));
113 	if (th2) {
114 		zassert_equal(thrd_success, thrd_join(fixture->thrd2, &fixture->res2));
115 	}
116 
117 	zassert_equal(exp1, fixture->res1);
118 	if (th2) {
119 		zassert_equal(exp2, fixture->res2);
120 	}
121 }
122 
ZTEST_F(libc_cnd,test_cnd_signal_wait)123 ZTEST_F(libc_cnd, test_cnd_signal_wait)
124 {
125 	tst_cnd_common(fixture, WAIT_TIME_MS / 2, false, thrd_success, DONT_CARE);
126 }
127 
ZTEST_F(libc_cnd,test_cnd_signal_timedwait)128 ZTEST_F(libc_cnd, test_cnd_signal_timedwait)
129 {
130 	fixture->do_timedwait = true;
131 	tst_cnd_common(fixture, WAIT_TIME_MS / 2, false, thrd_success, DONT_CARE);
132 }
133 
ZTEST_F(libc_cnd,test_cnd_timedwait_timeout)134 ZTEST_F(libc_cnd, test_cnd_timedwait_timeout)
135 {
136 	fixture->do_timedwait = true;
137 	tst_cnd_common(fixture, WAIT_TIME_MS * 2, false, thrd_timedout, DONT_CARE);
138 }
139 
ZTEST_F(libc_cnd,test_cnd_broadcast_wait)140 ZTEST_F(libc_cnd, test_cnd_broadcast_wait)
141 {
142 	fixture->is_broadcast = true;
143 	tst_cnd_common(fixture, WAIT_TIME_MS, true, thrd_success, thrd_success);
144 }
145 
setup(void)146 static void *setup(void)
147 {
148 	return &_libc_cnd_fixture;
149 }
150 
before(void * arg)151 static void before(void *arg)
152 {
153 	struct libc_cnd_fixture *const fixture = arg;
154 
155 	*fixture = (struct libc_cnd_fixture){
156 		.res1 = FORTY_TWO,
157 		.res2 = SEVENTY_THREE,
158 	};
159 
160 	zassert_equal(thrd_success, mtx_init(&fixture->mutex, mtx_plain));
161 	zassert_equal(thrd_success, cnd_init(&fixture->cond));
162 }
163 
after(void * arg)164 static void after(void *arg)
165 {
166 	struct libc_cnd_fixture *const fixture = arg;
167 
168 	cnd_destroy(&fixture->cond);
169 	mtx_destroy(&fixture->mutex);
170 }
171 
172 ZTEST_SUITE(libc_cnd, NULL, setup, before, after, NULL);
173