1#include "tests/test_internal.h"
2
3#include <sch/irql.h>
4#include <sync/once_token.h>
5#include <test/sync.h>
6#include <time/spin_sleep.h>
7
8TEST_GROUP_DEFINE(harness_sync);
9
10struct latch_fix {
11 struct test_latch latch;
12 atomic_bool worker_returned;
13 atomic_bool worker_saw_set;
14};
15
16TEST_DEFINE_UNIT(harness_sync, latch_spin_at_raised_irql) {
17 struct test_latch l;
18 test_latch_init(l: &l);
19
20 enum irql irql = irql_raise(new_level: IRQL_DISPATCH_LEVEL);
21 bool timed_out = !test_latch_spin_timeout(l: &l, timeout_ms: 50);
22 test_latch_set(l: &l);
23 bool saw_set = test_latch_spin_timeout(l: &l, timeout_ms: 50);
24 irql_lower(old_level: irql);
25
26 TEST_ASSERT(timed_out);
27 TEST_ASSERT(saw_set);
28 return TEST_SUCCESS;
29}
30
31struct phase_fix {
32 struct test_phase phase;
33 atomic_uint32_t reached;
34 atomic_uint32_t failed;
35};
36
37static void phase_climber(void *arg) {
38 struct phase_fix *f = arg;
39
40 if (!test_phase_wait(p: &f->phase, expected: 1, timeout_ms: 5000)) {
41 atomic_inc(&f->failed);
42 return;
43 }
44 atomic_inc(&f->reached);
45}
46
47static void phase_forever_waiter(void *arg) {
48 struct phase_fix *f = arg;
49
50 /* Phase 99 is never published */
51 if (!test_phase_wait(p: &f->phase, expected: 99, timeout_ms: 5000))
52 atomic_inc(&f->failed);
53 else
54 atomic_inc(&f->reached);
55}
56
57TEST_DEFINE_UNIT(harness_sync, phase_advance_wakes_waiters) {
58 struct phase_fix f = {0};
59 test_phase_init(p: &f.phase);
60
61 struct thread *t[4];
62 for (size_t i = 0; i < TEST_ARRAY_LEN(t); i++) {
63 t[i] = thread_spawn("phase_climber", phase_climber, .arg = &f,
64 .joinable = true);
65 TEST_ASSERT_NONNULL(t[i]);
66 }
67
68 sleep_spin_ms(msec: 20);
69 TEST_ASSERT_EQ(test_phase_get(&f.phase), 0);
70 test_phase_advance(p: &f.phase);
71
72 for (size_t i = 0; i < TEST_ARRAY_LEN(t); i++)
73 thread_join(t: t[i]);
74
75 TEST_ASSERT_EQ(atomic_load(&f.reached), TEST_ARRAY_LEN(t));
76 TEST_ASSERT_EQ(atomic_load(&f.failed), 0);
77 TEST_ASSERT_EQ(test_phase_get(&f.phase), 1);
78 return TEST_SUCCESS;
79}
80
81TEST_DEFINE_UNIT(harness_sync, phase_poison_unblocks_all) {
82 struct phase_fix f = {0};
83 test_phase_init(p: &f.phase);
84
85 struct thread *t[4];
86 for (size_t i = 0; i < TEST_ARRAY_LEN(t); i++) {
87 t[i] = thread_spawn("phase_waiter", phase_forever_waiter, .arg = &f,
88 .joinable = true);
89 TEST_ASSERT_NONNULL(t[i]);
90 }
91
92 sleep_spin_ms(msec: 20);
93 TEST_ASSERT(!test_phase_is_poisoned(&f.phase));
94 test_phase_poison(p: &f.phase);
95
96 for (size_t i = 0; i < TEST_ARRAY_LEN(t); i++)
97 thread_join(t: t[i]);
98
99 /* Every waiter must fail out */
100 TEST_ASSERT_EQ(atomic_load(&f.failed), TEST_ARRAY_LEN(t));
101 TEST_ASSERT_EQ(atomic_load(&f.reached), 0);
102 TEST_ASSERT(test_phase_is_poisoned(&f.phase));
103
104 /* A wait entered after poisoning must fail */
105 TEST_ASSERT(!test_phase_wait(&f.phase, 0xFFFF, 100));
106 return TEST_SUCCESS;
107}
108
109TEST_DEFINE_UNIT(harness_sync, phase_timeout_expires) {
110 struct test_phase p;
111 test_phase_init(p: &p);
112
113 time_ms_t start = time_get_ms();
114 TEST_ASSERT(!test_phase_wait(&p, 7, 100));
115 time_ms_t took = time_get_ms() - start;
116
117 TEST_ASSERT_GE(took, 90);
118 TEST_ASSERT_LT(took, 2000);
119 return TEST_SUCCESS;
120}
121
122TEST_DEFINE_UNIT(harness_sync, phase_already_at_target) {
123 struct test_phase p;
124 test_phase_init(p: &p);
125 test_phase_set(p: &p, phase: 5);
126
127 time_ms_t start = time_get_ms();
128 TEST_ASSERT(test_phase_wait(&p, 5, 5000));
129 TEST_ASSERT_LT(time_get_ms() - start, 1000);
130 TEST_ASSERT_EQ(test_phase_get(&p), 5);
131 return TEST_SUCCESS;
132}
133
134enum test_sync_token_state {
135 TOKEN_INIT = 0,
136 TOKEN_WORKING = 1,
137 TOKEN_DONE = 2,
138};
139
140TEST_DEFINE_UNIT(harness_sync, once_token_typechecked_ops) {
141 ONCE_TOKEN_DEFINE(bool, btok);
142 once_token_init(&btok);
143 once_token_typecheck_internal(&btok);
144 once_token_typecheck_internal_bool(&btok);
145
146 TEST_ASSERT(!once_token_claimed(&btok));
147 TEST_ASSERT(once_token_claim(&btok));
148 TEST_ASSERT(!once_token_claim(&btok));
149 TEST_ASSERT(once_token_claimed(&btok));
150 once_token_reset(&btok);
151 TEST_ASSERT(!once_token_claimed(&btok));
152
153 ONCE_TOKEN_DEFINE(enum test_sync_token_state, stok);
154 once_token_init(&stok, TOKEN_INIT);
155 once_token_typecheck_internal(&stok);
156 once_token_typecheck_internal_type(&stok, enum test_sync_token_state);
157
158 TEST_ASSERT(once_token_claimed(&stok, TOKEN_INIT));
159 TEST_ASSERT(once_token_claim(&stok, TOKEN_INIT, TOKEN_WORKING));
160 TEST_ASSERT(once_token_claimed(&stok, TOKEN_WORKING));
161 TEST_ASSERT(!once_token_claim(&stok, TOKEN_INIT, TOKEN_DONE));
162 once_token_reset(&stok, TOKEN_DONE);
163 TEST_ASSERT_EQ(once_token_read(&stok), TOKEN_DONE);
164
165 return TEST_SUCCESS;
166}
167