timer_test.c revision 1.2.6.1 1 /* $NetBSD: timer_test.c,v 1.2.6.1 2025/08/02 05:54:17 perseant Exp $ */
2
3 /*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * SPDX-License-Identifier: MPL-2.0
7 *
8 * This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11 *
12 * See the COPYRIGHT file distributed with this work for additional
13 * information regarding copyright ownership.
14 */
15
16 #include <inttypes.h>
17 #include <sched.h> /* IWYU pragma: keep */
18 #include <setjmp.h>
19 #include <stdarg.h>
20 #include <stddef.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
24
25 #define UNIT_TESTING
26 #include <cmocka.h>
27
28 #include <isc/atomic.h>
29 #include <isc/commandline.h>
30 #include <isc/condition.h>
31 #include <isc/job.h>
32 #include <isc/loop.h>
33 #include <isc/mem.h>
34 #include <isc/os.h>
35 #include <isc/time.h>
36 #include <isc/timer.h>
37 #include <isc/util.h>
38
39 #include "timer.c"
40
41 #include <tests/isc.h>
42
43 /* Set to true (or use -v option) for verbose output */
44 static bool verbose = true;
45
46 #define FUDGE_SECONDS 0 /* in absence of clock_getres() */
47 #define FUDGE_NANOSECONDS 500000000 /* in absence of clock_getres() */
48
49 static isc_timer_t *timer = NULL;
50 static isc_time_t endtime;
51 static isc_mutex_t lasttime_mx;
52 static isc_time_t lasttime;
53 static int seconds;
54 static int nanoseconds;
55 static atomic_int_fast32_t eventcnt;
56 static atomic_uint_fast32_t errcnt;
57 static int nevents;
58
59 typedef struct setup_test_arg {
60 isc_timertype_t timertype;
61 isc_interval_t *interval;
62 isc_job_cb action;
63 } setup_test_arg_t;
64
65 static void
66 setup_test_run(void *data) {
67 isc_timertype_t timertype = ((setup_test_arg_t *)data)->timertype;
68 isc_interval_t *interval = ((setup_test_arg_t *)data)->interval;
69 isc_job_cb action = ((setup_test_arg_t *)data)->action;
70
71 isc_mutex_lock(&lasttime_mx);
72 lasttime = isc_time_now();
73 UNLOCK(&lasttime_mx);
74
75 isc_timer_create(mainloop, action, (void *)timertype, &timer);
76 isc_timer_start(timer, timertype, interval);
77 }
78
79 static void
80 setup_test(isc_timertype_t timertype, isc_interval_t *interval,
81 isc_job_cb action) {
82 setup_test_arg_t arg = { .timertype = timertype,
83 .interval = interval,
84 .action = action };
85
86 isc_time_settoepoch(&endtime);
87 atomic_init(&eventcnt, 0);
88
89 isc_mutex_init(&lasttime_mx);
90
91 atomic_store(&errcnt, ISC_R_SUCCESS);
92
93 isc_loop_setup(mainloop, setup_test_run, &arg);
94 isc_loopmgr_run(loopmgr);
95
96 assert_int_equal(atomic_load(&errcnt), ISC_R_SUCCESS);
97
98 isc_mutex_destroy(&lasttime_mx);
99 }
100
101 static void
102 set_global_error(isc_result_t result) {
103 (void)atomic_compare_exchange_strong(
104 &errcnt, &(uint_fast32_t){ ISC_R_SUCCESS }, result);
105 }
106
107 static void
108 subthread_assert_true(bool expected, const char *file, unsigned int line) {
109 if (!expected) {
110 printf("# %s:%u subthread_assert_true\n", file, line);
111 set_global_error(ISC_R_UNEXPECTED);
112 }
113 }
114 #define subthread_assert_true(expected) \
115 subthread_assert_true(expected, __FILE__, __LINE__)
116
117 static void
118 subthread_assert_result_equal(isc_result_t result, isc_result_t expected,
119 const char *file, unsigned int line) {
120 if (result != expected) {
121 printf("# %s:%u subthread_assert_result_equal(%u != %u)\n",
122 file, line, (unsigned int)result,
123 (unsigned int)expected);
124 set_global_error(result);
125 }
126 }
127 #define subthread_assert_result_equal(observed, expected) \
128 subthread_assert_result_equal(observed, expected, __FILE__, __LINE__)
129
130 static void
131 ticktock(void *arg) {
132 isc_result_t result;
133 isc_time_t now;
134 isc_time_t base;
135 isc_time_t ulim;
136 isc_time_t llim;
137 isc_interval_t interval;
138 int tick = atomic_fetch_add(&eventcnt, 1);
139
140 UNUSED(arg);
141
142 if (verbose) {
143 print_message("# tick %d\n", tick);
144 }
145
146 now = isc_time_now();
147
148 isc_interval_set(&interval, seconds, nanoseconds);
149 isc_mutex_lock(&lasttime_mx);
150 result = isc_time_add(&lasttime, &interval, &base);
151 isc_mutex_unlock(&lasttime_mx);
152 subthread_assert_result_equal(result, ISC_R_SUCCESS);
153
154 isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS);
155 result = isc_time_add(&base, &interval, &ulim);
156 subthread_assert_result_equal(result, ISC_R_SUCCESS);
157
158 result = isc_time_subtract(&base, &interval, &llim);
159 subthread_assert_result_equal(result, ISC_R_SUCCESS);
160
161 subthread_assert_true(isc_time_compare(&llim, &now) <= 0);
162 subthread_assert_true(isc_time_compare(&ulim, &now) >= 0);
163
164 isc_interval_set(&interval, 0, 0);
165 isc_mutex_lock(&lasttime_mx);
166 result = isc_time_add(&now, &interval, &lasttime);
167 isc_mutex_unlock(&lasttime_mx);
168 subthread_assert_result_equal(result, ISC_R_SUCCESS);
169
170 if (atomic_load(&eventcnt) == nevents) {
171 endtime = isc_time_now();
172 isc_timer_destroy(&timer);
173 isc_loopmgr_shutdown(loopmgr);
174 }
175 }
176
177 /*
178 * Individual unit tests
179 */
180
181 /* timer type ticker */
182 ISC_RUN_TEST_IMPL(ticker) {
183 isc_interval_t interval;
184
185 UNUSED(state);
186
187 nevents = 12;
188 seconds = 0;
189 nanoseconds = 500000000;
190
191 isc_interval_set(&interval, seconds, nanoseconds);
192
193 setup_test(isc_timertype_ticker, &interval, ticktock);
194 }
195
196 static void
197 test_idle(void *arg) {
198 isc_result_t result;
199 isc_time_t now;
200 isc_time_t base;
201 isc_time_t ulim;
202 isc_time_t llim;
203 isc_interval_t interval;
204 int tick = atomic_fetch_add(&eventcnt, 1);
205
206 UNUSED(arg);
207
208 if (verbose) {
209 print_message("# tick %d\n", tick);
210 }
211
212 now = isc_time_now();
213
214 isc_interval_set(&interval, seconds, nanoseconds);
215 isc_mutex_lock(&lasttime_mx);
216 result = isc_time_add(&lasttime, &interval, &base);
217 isc_mutex_unlock(&lasttime_mx);
218 subthread_assert_result_equal(result, ISC_R_SUCCESS);
219
220 isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS);
221 result = isc_time_add(&base, &interval, &ulim);
222 subthread_assert_result_equal(result, ISC_R_SUCCESS);
223
224 result = isc_time_subtract(&base, &interval, &llim);
225 subthread_assert_result_equal(result, ISC_R_SUCCESS);
226
227 subthread_assert_true(isc_time_compare(&llim, &now) <= 0);
228 subthread_assert_true(isc_time_compare(&ulim, &now) >= 0);
229
230 isc_interval_set(&interval, 0, 0);
231 isc_mutex_lock(&lasttime_mx);
232 isc_time_add(&now, &interval, &lasttime);
233 isc_mutex_unlock(&lasttime_mx);
234
235 isc_timer_destroy(&timer);
236 isc_loopmgr_shutdown(loopmgr);
237 }
238
239 /* timer type once idles out */
240 ISC_RUN_TEST_IMPL(once_idle) {
241 isc_interval_t interval;
242
243 UNUSED(state);
244
245 nevents = 1;
246 seconds = 1;
247 nanoseconds = 200000000;
248
249 isc_interval_set(&interval, seconds, nanoseconds);
250
251 setup_test(isc_timertype_once, &interval, test_idle);
252 }
253
254 /* timer reset */
255 static void
256 test_reset(void *arg) {
257 isc_result_t result;
258 isc_time_t now;
259 isc_time_t base;
260 isc_time_t ulim;
261 isc_time_t llim;
262 isc_interval_t interval;
263 int tick = atomic_fetch_add(&eventcnt, 1);
264
265 UNUSED(arg);
266
267 if (verbose) {
268 print_message("# tick %d\n", tick);
269 }
270
271 /*
272 * Check expired time.
273 */
274
275 now = isc_time_now();
276
277 isc_interval_set(&interval, seconds, nanoseconds);
278 isc_mutex_lock(&lasttime_mx);
279 result = isc_time_add(&lasttime, &interval, &base);
280 isc_mutex_unlock(&lasttime_mx);
281 subthread_assert_result_equal(result, ISC_R_SUCCESS);
282
283 isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS);
284 result = isc_time_add(&base, &interval, &ulim);
285 subthread_assert_result_equal(result, ISC_R_SUCCESS);
286
287 result = isc_time_subtract(&base, &interval, &llim);
288 subthread_assert_result_equal(result, ISC_R_SUCCESS);
289
290 subthread_assert_true(isc_time_compare(&llim, &now) <= 0);
291 subthread_assert_true(isc_time_compare(&ulim, &now) >= 0);
292
293 isc_interval_set(&interval, 0, 0);
294 isc_mutex_lock(&lasttime_mx);
295 isc_time_add(&now, &interval, &lasttime);
296 isc_mutex_unlock(&lasttime_mx);
297
298 if (tick < 2) {
299 if (tick == 1) {
300 isc_interval_set(&interval, seconds, nanoseconds);
301 isc_timer_start(timer, isc_timertype_once, &interval);
302 }
303 } else {
304 isc_timer_destroy(&timer);
305 isc_loopmgr_shutdown(loopmgr);
306 }
307 }
308
309 ISC_RUN_TEST_IMPL(reset) {
310 isc_interval_t interval;
311
312 UNUSED(state);
313
314 nevents = 3;
315 seconds = 0;
316 nanoseconds = 750000000;
317
318 isc_interval_set(&interval, seconds, nanoseconds);
319
320 setup_test(isc_timertype_ticker, &interval, test_reset);
321 }
322
323 static atomic_bool startflag;
324 static isc_timer_t *tickertimer = NULL;
325 static isc_timer_t *oncetimer = NULL;
326
327 static void
328 tick_event(void *arg) {
329 int tick;
330
331 UNUSED(arg);
332
333 if (!atomic_load(&startflag)) {
334 if (verbose) {
335 print_message("# tick_event %d\n", -1);
336 }
337 return;
338 }
339
340 tick = atomic_fetch_add(&eventcnt, 1);
341 if (verbose) {
342 print_message("# tick_event %d\n", tick);
343 }
344
345 /*
346 * On the first tick, purge all remaining tick events.
347 */
348 if (tick == 0) {
349 isc_timer_destroy(&tickertimer);
350 isc_loopmgr_shutdown(loopmgr);
351 }
352 }
353
354 static void
355 once_event(void *arg) {
356 UNUSED(arg);
357
358 if (verbose) {
359 print_message("# once_event\n");
360 }
361
362 /*
363 * Allow task1 to start processing events.
364 */
365 atomic_store(&startflag, true);
366
367 isc_timer_destroy(&oncetimer);
368 }
369
370 ISC_LOOP_SETUP_IMPL(purge) {
371 atomic_init(&eventcnt, 0);
372 atomic_store(&errcnt, ISC_R_SUCCESS);
373 }
374
375 ISC_LOOP_TEARDOWN_IMPL(purge) {
376 assert_int_equal(atomic_load(&errcnt), ISC_R_SUCCESS);
377 assert_int_equal(atomic_load(&eventcnt), 1);
378 }
379
380 /* timer events purged */
381 ISC_LOOP_TEST_SETUP_TEARDOWN_IMPL(purge) {
382 isc_interval_t interval;
383
384 UNUSED(arg);
385
386 if (verbose) {
387 print_message("# purge_run\n");
388 }
389
390 atomic_init(&startflag, 0);
391 seconds = 1;
392 nanoseconds = 0;
393
394 isc_interval_set(&interval, seconds, 0);
395
396 tickertimer = NULL;
397 isc_timer_create(mainloop, tick_event, NULL, &tickertimer);
398 isc_timer_start(tickertimer, isc_timertype_ticker, &interval);
399
400 oncetimer = NULL;
401
402 isc_interval_set(&interval, (seconds * 2) + 1, 0);
403
404 isc_timer_create(mainloop, once_event, NULL, &oncetimer);
405 isc_timer_start(oncetimer, isc_timertype_once, &interval);
406 }
407
408 /*
409 * Set of tests that check whether the rescheduling works as expected.
410 */
411
412 isc_time_t timer_start;
413 isc_time_t timer_stop;
414 uint64_t timer_expect;
415 uint64_t timer_ticks;
416 isc_interval_t timer_interval;
417 isc_timertype_t timer_type;
418
419 ISC_LOOP_TEARDOWN_IMPL(timer_expect) {
420 uint64_t diff = isc_time_microdiff(&timer_stop, &timer_start) / 1000000;
421 assert_true(diff == timer_expect);
422 }
423
424 static void
425 timer_event(void *arg ISC_ATTR_UNUSED) {
426 if (--timer_ticks == 0) {
427 isc_timer_destroy(&timer);
428 isc_loopmgr_shutdown(loopmgr);
429 timer_stop = isc_loop_now(isc_loop());
430 } else {
431 isc_timer_start(timer, timer_type, &timer_interval);
432 }
433 }
434
435 ISC_LOOP_SETUP_IMPL(reschedule_up) {
436 timer_start = isc_loop_now(isc_loop());
437 timer_expect = 1;
438 timer_ticks = 1;
439 timer_type = isc_timertype_once;
440 }
441
442 ISC_LOOP_TEST_CUSTOM_IMPL(reschedule_up, setup_loop_reschedule_up,
443 teardown_loop_timer_expect) {
444 isc_timer_create(mainloop, timer_event, NULL, &timer);
445
446 /* Schedule the timer to fire immediately */
447 isc_interval_set(&timer_interval, 0, 0);
448 isc_timer_start(timer, timer_type, &timer_interval);
449
450 /* And then reschedule it to 1 second */
451 isc_interval_set(&timer_interval, 1, 0);
452 isc_timer_start(timer, timer_type, &timer_interval);
453 }
454
455 ISC_LOOP_SETUP_IMPL(reschedule_down) {
456 timer_start = isc_loop_now(isc_loop());
457 timer_expect = 0;
458 timer_ticks = 1;
459 timer_type = isc_timertype_once;
460 }
461
462 ISC_LOOP_TEST_CUSTOM_IMPL(reschedule_down, setup_loop_reschedule_down,
463 teardown_loop_timer_expect) {
464 isc_timer_create(mainloop, timer_event, NULL, &timer);
465
466 /* Schedule the timer to fire at 10 seconds */
467 isc_interval_set(&timer_interval, 10, 0);
468 isc_timer_start(timer, timer_type, &timer_interval);
469
470 /* And then reschedule it fire immediately */
471 isc_interval_set(&timer_interval, 0, 0);
472 isc_timer_start(timer, timer_type, &timer_interval);
473 }
474
475 ISC_LOOP_SETUP_IMPL(reschedule_from_callback) {
476 timer_start = isc_loop_now(isc_loop());
477 timer_expect = 1;
478 timer_ticks = 2;
479 timer_type = isc_timertype_once;
480 }
481
482 ISC_LOOP_TEST_CUSTOM_IMPL(reschedule_from_callback,
483 setup_loop_reschedule_from_callback,
484 teardown_loop_timer_expect) {
485 isc_timer_create(mainloop, timer_event, NULL, &timer);
486
487 isc_interval_set(&timer_interval, 0, NS_PER_SEC / 2);
488 isc_timer_start(timer, timer_type, &timer_interval);
489 }
490
491 ISC_LOOP_SETUP_IMPL(zero) {
492 timer_start = isc_loop_now(isc_loop());
493 timer_expect = 0;
494 timer_ticks = 1;
495 timer_type = isc_timertype_once;
496 }
497
498 ISC_LOOP_TEST_CUSTOM_IMPL(zero, setup_loop_zero, teardown_loop_timer_expect) {
499 isc_timer_create(mainloop, timer_event, NULL, &timer);
500
501 /* Schedule the timer to fire immediately (in the next event loop) */
502 isc_interval_set(&timer_interval, 0, 0);
503 isc_timer_start(timer, timer_type, &timer_interval);
504 }
505
506 ISC_LOOP_SETUP_IMPL(reschedule_ticker) {
507 timer_start = isc_loop_now(isc_loop());
508 timer_expect = 1;
509 timer_ticks = 5;
510 timer_type = isc_timertype_ticker;
511 }
512
513 ISC_LOOP_TEST_CUSTOM_IMPL(reschedule_ticker, setup_loop_reschedule_ticker,
514 teardown_loop_timer_expect) {
515 isc_timer_create(mainloop, timer_event, NULL, &timer);
516
517 /* Schedule the timer to fire immediately (in the next event loop) */
518 isc_interval_set(&timer_interval, 0, 0);
519 isc_timer_start(timer, timer_type, &timer_interval);
520
521 /* Then fire every 1/4 second */
522 isc_interval_set(&timer_interval, 0, NS_PER_SEC / 4);
523 }
524
525 ISC_TEST_LIST_START
526
527 ISC_TEST_ENTRY_CUSTOM(ticker, setup_loopmgr, teardown_loopmgr)
528 ISC_TEST_ENTRY_CUSTOM(once_idle, setup_loopmgr, teardown_loopmgr)
529 ISC_TEST_ENTRY_CUSTOM(reset, setup_loopmgr, teardown_loopmgr)
530 ISC_TEST_ENTRY_CUSTOM(purge, setup_loopmgr, teardown_loopmgr)
531 ISC_TEST_ENTRY_CUSTOM(reschedule_up, setup_loopmgr, teardown_loopmgr)
532 ISC_TEST_ENTRY_CUSTOM(reschedule_down, setup_loopmgr, teardown_loopmgr)
533 ISC_TEST_ENTRY_CUSTOM(reschedule_from_callback, setup_loopmgr, teardown_loopmgr)
534 ISC_TEST_ENTRY_CUSTOM(zero, setup_loopmgr, teardown_loopmgr)
535 ISC_TEST_ENTRY_CUSTOM(reschedule_ticker, setup_loopmgr, teardown_loopmgr)
536
537 ISC_TEST_LIST_END
538
539 ISC_TEST_MAIN
540