1/*	$NetBSD: timer_test.c,v 1.2 2024/02/21 22:52:51 christos 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/mem.h>
32#include <isc/print.h>
33#include <isc/task.h>
34#include <isc/time.h>
35#include <isc/timer.h>
36#include <isc/util.h>
37
38#include "netmgr/uv-compat.h"
39#include "timer.c"
40
41#include <tests/isc.h>
42
43/* Set to true (or use -v option) for verbose output */
44static bool verbose = false;
45
46#define FUDGE_SECONDS	  0	    /* in absence of clock_getres() */
47#define FUDGE_NANOSECONDS 500000000 /* in absence of clock_getres() */
48
49static isc_timer_t *timer = NULL;
50static isc_condition_t cv;
51static isc_mutex_t mx;
52static isc_time_t endtime;
53static isc_mutex_t lasttime_mx;
54static isc_time_t lasttime;
55static int seconds;
56static int nanoseconds;
57static atomic_int_fast32_t eventcnt;
58static atomic_uint_fast32_t errcnt;
59static int nevents;
60
61static int
62_setup(void **state) {
63	atomic_init(&errcnt, ISC_R_SUCCESS);
64
65	setup_managers(state);
66
67	return (0);
68}
69
70static int
71_teardown(void **state) {
72	teardown_managers(state);
73
74	return (0);
75}
76
77static void
78test_shutdown(isc_task_t *task, isc_event_t *event) {
79	isc_result_t result;
80
81	UNUSED(task);
82
83	/*
84	 * Signal shutdown processing complete.
85	 */
86	result = isc_mutex_lock(&mx);
87	assert_int_equal(result, ISC_R_SUCCESS);
88
89	result = isc_condition_signal(&cv);
90	assert_int_equal(result, ISC_R_SUCCESS);
91
92	result = isc_mutex_unlock(&mx);
93	assert_int_equal(result, ISC_R_SUCCESS);
94
95	isc_event_free(&event);
96}
97
98static void
99setup_test(isc_timertype_t timertype, isc_time_t *expires,
100	   isc_interval_t *interval,
101	   void (*action)(isc_task_t *, isc_event_t *)) {
102	isc_result_t result;
103	isc_task_t *task = NULL;
104	isc_time_settoepoch(&endtime);
105	atomic_init(&eventcnt, 0);
106
107	isc_mutex_init(&mx);
108	isc_mutex_init(&lasttime_mx);
109
110	isc_condition_init(&cv);
111
112	atomic_store(&errcnt, ISC_R_SUCCESS);
113
114	LOCK(&mx);
115
116	result = isc_task_create(taskmgr, 0, &task);
117	assert_int_equal(result, ISC_R_SUCCESS);
118
119	result = isc_task_onshutdown(task, test_shutdown, NULL);
120	assert_int_equal(result, ISC_R_SUCCESS);
121
122	isc_mutex_lock(&lasttime_mx);
123	result = isc_time_now(&lasttime);
124	isc_mutex_unlock(&lasttime_mx);
125	assert_int_equal(result, ISC_R_SUCCESS);
126
127	result = isc_timer_create(timermgr, timertype, expires, interval, task,
128				  action, (void *)timertype, &timer);
129	assert_int_equal(result, ISC_R_SUCCESS);
130
131	/*
132	 * Wait for shutdown processing to complete.
133	 */
134	while (atomic_load(&eventcnt) != nevents) {
135		result = isc_condition_wait(&cv, &mx);
136		assert_int_equal(result, ISC_R_SUCCESS);
137	}
138
139	UNLOCK(&mx);
140
141	assert_int_equal(atomic_load(&errcnt), ISC_R_SUCCESS);
142
143	isc_task_detach(&task);
144	isc_mutex_destroy(&mx);
145	isc_mutex_destroy(&lasttime_mx);
146	(void)isc_condition_destroy(&cv);
147}
148
149static void
150set_global_error(isc_result_t result) {
151	(void)atomic_compare_exchange_strong(
152		&errcnt, &(uint_fast32_t){ ISC_R_SUCCESS }, result);
153}
154
155static void
156subthread_assert_true(bool expected, const char *file, unsigned int line) {
157	if (!expected) {
158		printf("# %s:%u subthread_assert_true\n", file, line);
159		set_global_error(ISC_R_UNEXPECTED);
160	}
161}
162#define subthread_assert_true(expected) \
163	subthread_assert_true(expected, __FILE__, __LINE__)
164
165static void
166subthread_assert_int_equal(int observed, int expected, const char *file,
167			   unsigned int line) {
168	if (observed != expected) {
169		printf("# %s:%u subthread_assert_int_equal(%d != %d)\n", file,
170		       line, observed, expected);
171		set_global_error(ISC_R_UNEXPECTED);
172	}
173}
174#define subthread_assert_int_equal(observed, expected) \
175	subthread_assert_int_equal(observed, expected, __FILE__, __LINE__)
176
177static void
178subthread_assert_result_equal(isc_result_t result, isc_result_t expected,
179			      const char *file, unsigned int line) {
180	if (result != expected) {
181		printf("# %s:%u subthread_assert_result_equal(%u != %u)\n",
182		       file, line, (unsigned int)result,
183		       (unsigned int)expected);
184		set_global_error(result);
185	}
186}
187#define subthread_assert_result_equal(observed, expected) \
188	subthread_assert_result_equal(observed, expected, __FILE__, __LINE__)
189
190static void
191ticktock(isc_task_t *task, isc_event_t *event) {
192	isc_result_t result;
193	isc_time_t now;
194	isc_time_t base;
195	isc_time_t ulim;
196	isc_time_t llim;
197	isc_interval_t interval;
198	isc_eventtype_t expected_event_type;
199
200	int tick = atomic_fetch_add(&eventcnt, 1);
201
202	if (verbose) {
203		print_message("# tick %d\n", tick);
204	}
205
206	expected_event_type = ISC_TIMEREVENT_LIFE;
207	if ((uintptr_t)event->ev_arg == isc_timertype_ticker) {
208		expected_event_type = ISC_TIMEREVENT_TICK;
209	}
210
211	if (event->ev_type != expected_event_type) {
212		print_error("# expected event type %u, got %u\n",
213			    expected_event_type, event->ev_type);
214	}
215
216	result = isc_time_now(&now);
217	subthread_assert_result_equal(result, ISC_R_SUCCESS);
218
219	isc_interval_set(&interval, seconds, nanoseconds);
220	isc_mutex_lock(&lasttime_mx);
221	result = isc_time_add(&lasttime, &interval, &base);
222	isc_mutex_unlock(&lasttime_mx);
223	subthread_assert_result_equal(result, ISC_R_SUCCESS);
224
225	isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS);
226	result = isc_time_add(&base, &interval, &ulim);
227	subthread_assert_result_equal(result, ISC_R_SUCCESS);
228
229	result = isc_time_subtract(&base, &interval, &llim);
230	subthread_assert_result_equal(result, ISC_R_SUCCESS);
231
232	subthread_assert_true(isc_time_compare(&llim, &now) <= 0);
233	subthread_assert_true(isc_time_compare(&ulim, &now) >= 0);
234
235	isc_interval_set(&interval, 0, 0);
236	isc_mutex_lock(&lasttime_mx);
237	result = isc_time_add(&now, &interval, &lasttime);
238	isc_mutex_unlock(&lasttime_mx);
239	subthread_assert_result_equal(result, ISC_R_SUCCESS);
240
241	isc_event_free(&event);
242
243	if (atomic_load(&eventcnt) == nevents) {
244		result = isc_time_now(&endtime);
245		subthread_assert_result_equal(result, ISC_R_SUCCESS);
246		isc_timer_destroy(&timer);
247		isc_task_shutdown(task);
248	}
249}
250
251/*
252 * Individual unit tests
253 */
254
255/* timer type ticker */
256ISC_RUN_TEST_IMPL(ticker) {
257	isc_time_t expires;
258	isc_interval_t interval;
259
260	UNUSED(state);
261
262	nevents = 12;
263	seconds = 0;
264	nanoseconds = 500000000;
265
266	isc_interval_set(&interval, seconds, nanoseconds);
267	isc_time_settoepoch(&expires);
268
269	setup_test(isc_timertype_ticker, &expires, &interval, ticktock);
270}
271
272/* timer type once reaches lifetime */
273ISC_RUN_TEST_IMPL(once_life) {
274	isc_result_t result;
275	isc_time_t expires;
276	isc_interval_t interval;
277
278	UNUSED(state);
279
280	nevents = 1;
281	seconds = 1;
282	nanoseconds = 100000000;
283
284	isc_interval_set(&interval, seconds, nanoseconds);
285	result = isc_time_nowplusinterval(&expires, &interval);
286	assert_int_equal(result, ISC_R_SUCCESS);
287
288	isc_interval_set(&interval, 0, 0);
289
290	setup_test(isc_timertype_once, &expires, &interval, ticktock);
291}
292
293static void
294test_idle(isc_task_t *task, isc_event_t *event) {
295	isc_result_t result;
296	isc_time_t now;
297	isc_time_t base;
298	isc_time_t ulim;
299	isc_time_t llim;
300	isc_interval_t interval;
301
302	int tick = atomic_fetch_add(&eventcnt, 1);
303
304	if (verbose) {
305		print_message("# tick %d\n", tick);
306	}
307
308	result = isc_time_now(&now);
309	subthread_assert_result_equal(result, ISC_R_SUCCESS);
310
311	isc_interval_set(&interval, seconds, nanoseconds);
312	isc_mutex_lock(&lasttime_mx);
313	result = isc_time_add(&lasttime, &interval, &base);
314	isc_mutex_unlock(&lasttime_mx);
315	subthread_assert_result_equal(result, ISC_R_SUCCESS);
316
317	isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS);
318	result = isc_time_add(&base, &interval, &ulim);
319	subthread_assert_result_equal(result, ISC_R_SUCCESS);
320
321	result = isc_time_subtract(&base, &interval, &llim);
322	subthread_assert_result_equal(result, ISC_R_SUCCESS);
323
324	subthread_assert_true(isc_time_compare(&llim, &now) <= 0);
325	subthread_assert_true(isc_time_compare(&ulim, &now) >= 0);
326
327	isc_interval_set(&interval, 0, 0);
328	isc_mutex_lock(&lasttime_mx);
329	isc_time_add(&now, &interval, &lasttime);
330	isc_mutex_unlock(&lasttime_mx);
331
332	subthread_assert_int_equal(event->ev_type, ISC_TIMEREVENT_IDLE);
333
334	isc_event_free(&event);
335
336	isc_timer_destroy(&timer);
337	isc_task_shutdown(task);
338}
339
340/* timer type once idles out */
341ISC_RUN_TEST_IMPL(once_idle) {
342	isc_result_t result;
343	isc_time_t expires;
344	isc_interval_t interval;
345
346	UNUSED(state);
347
348	nevents = 1;
349	seconds = 1;
350	nanoseconds = 200000000;
351
352	isc_interval_set(&interval, seconds + 1, nanoseconds);
353	result = isc_time_nowplusinterval(&expires, &interval);
354	assert_int_equal(result, ISC_R_SUCCESS);
355
356	isc_interval_set(&interval, seconds, nanoseconds);
357
358	setup_test(isc_timertype_once, &expires, &interval, test_idle);
359}
360
361/* timer reset */
362static void
363test_reset(isc_task_t *task, isc_event_t *event) {
364	isc_result_t result;
365	isc_time_t now;
366	isc_time_t base;
367	isc_time_t ulim;
368	isc_time_t llim;
369	isc_time_t expires;
370	isc_interval_t interval;
371
372	int tick = atomic_fetch_add(&eventcnt, 1);
373
374	if (verbose) {
375		print_message("# tick %d\n", tick);
376	}
377
378	/*
379	 * Check expired time.
380	 */
381
382	result = isc_time_now(&now);
383	subthread_assert_result_equal(result, ISC_R_SUCCESS);
384
385	isc_interval_set(&interval, seconds, nanoseconds);
386	isc_mutex_lock(&lasttime_mx);
387	result = isc_time_add(&lasttime, &interval, &base);
388	isc_mutex_unlock(&lasttime_mx);
389	subthread_assert_result_equal(result, ISC_R_SUCCESS);
390
391	isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS);
392	result = isc_time_add(&base, &interval, &ulim);
393	subthread_assert_result_equal(result, ISC_R_SUCCESS);
394
395	result = isc_time_subtract(&base, &interval, &llim);
396	subthread_assert_result_equal(result, ISC_R_SUCCESS);
397
398	subthread_assert_true(isc_time_compare(&llim, &now) <= 0);
399	subthread_assert_true(isc_time_compare(&ulim, &now) >= 0);
400
401	isc_interval_set(&interval, 0, 0);
402	isc_mutex_lock(&lasttime_mx);
403	isc_time_add(&now, &interval, &lasttime);
404	isc_mutex_unlock(&lasttime_mx);
405
406	int _eventcnt = atomic_load(&eventcnt);
407
408	if (_eventcnt < 3) {
409		subthread_assert_int_equal(event->ev_type, ISC_TIMEREVENT_TICK);
410
411		if (_eventcnt == 2) {
412			isc_interval_set(&interval, seconds, nanoseconds);
413			result = isc_time_nowplusinterval(&expires, &interval);
414			subthread_assert_result_equal(result, ISC_R_SUCCESS);
415
416			isc_interval_set(&interval, 0, 0);
417			result = isc_timer_reset(timer, isc_timertype_once,
418						 &expires, &interval, false);
419			subthread_assert_result_equal(result, ISC_R_SUCCESS);
420		}
421
422		isc_event_free(&event);
423	} else {
424		subthread_assert_int_equal(event->ev_type, ISC_TIMEREVENT_LIFE);
425
426		isc_event_free(&event);
427		isc_timer_destroy(&timer);
428		isc_task_shutdown(task);
429	}
430}
431
432ISC_RUN_TEST_IMPL(reset) {
433	isc_time_t expires;
434	isc_interval_t interval;
435
436	UNUSED(state);
437
438	nevents = 3;
439	seconds = 0;
440	nanoseconds = 750000000;
441
442	isc_interval_set(&interval, seconds, nanoseconds);
443	isc_time_settoepoch(&expires);
444
445	setup_test(isc_timertype_ticker, &expires, &interval, test_reset);
446}
447
448static atomic_bool startflag;
449static atomic_bool shutdownflag;
450static isc_timer_t *tickertimer = NULL;
451static isc_timer_t *oncetimer = NULL;
452static isc_task_t *task1 = NULL;
453static isc_task_t *task2 = NULL;
454
455/*
456 * task1 blocks on mx while events accumulate
457 * in its queue, until signaled by task2.
458 */
459
460static void
461tick_event(isc_task_t *task, isc_event_t *event) {
462	isc_result_t result;
463	isc_time_t expires;
464	isc_interval_t interval;
465
466	UNUSED(task);
467
468	if (!atomic_load(&startflag)) {
469		if (verbose) {
470			print_message("# tick_event %d\n", -1);
471		}
472		isc_event_free(&event);
473		return;
474	}
475
476	int tick = atomic_fetch_add(&eventcnt, 1);
477	if (verbose) {
478		print_message("# tick_event %d\n", tick);
479	}
480
481	/*
482	 * On the first tick, purge all remaining tick events
483	 * and then shut down the task.
484	 */
485	if (tick == 0) {
486		isc_time_settoepoch(&expires);
487		isc_interval_set(&interval, seconds, 0);
488		result = isc_timer_reset(tickertimer, isc_timertype_ticker,
489					 &expires, &interval, true);
490		subthread_assert_result_equal(result, ISC_R_SUCCESS);
491
492		isc_task_shutdown(task);
493	}
494
495	isc_event_free(&event);
496}
497
498static void
499once_event(isc_task_t *task, isc_event_t *event) {
500	if (verbose) {
501		print_message("# once_event\n");
502	}
503
504	/*
505	 * Allow task1 to start processing events.
506	 */
507	atomic_store(&startflag, true);
508
509	isc_event_free(&event);
510	isc_task_shutdown(task);
511}
512
513static void
514shutdown_purge(isc_task_t *task, isc_event_t *event) {
515	UNUSED(task);
516	UNUSED(event);
517
518	if (verbose) {
519		print_message("# shutdown_event\n");
520	}
521
522	/*
523	 * Signal shutdown processing complete.
524	 */
525	atomic_store(&shutdownflag, 1);
526
527	isc_event_free(&event);
528}
529
530/* timer events purged */
531ISC_RUN_TEST_IMPL(purge) {
532	isc_result_t result;
533	isc_time_t expires;
534	isc_interval_t interval;
535
536	UNUSED(state);
537
538	atomic_init(&startflag, 0);
539	atomic_init(&shutdownflag, 0);
540	atomic_init(&eventcnt, 0);
541	seconds = 1;
542	nanoseconds = 0;
543
544	result = isc_task_create(taskmgr, 0, &task1);
545	assert_int_equal(result, ISC_R_SUCCESS);
546
547	result = isc_task_onshutdown(task1, shutdown_purge, NULL);
548	assert_int_equal(result, ISC_R_SUCCESS);
549
550	result = isc_task_create(taskmgr, 0, &task2);
551	assert_int_equal(result, ISC_R_SUCCESS);
552
553	isc_time_settoepoch(&expires);
554	isc_interval_set(&interval, seconds, 0);
555
556	tickertimer = NULL;
557	result = isc_timer_create(timermgr, isc_timertype_ticker, &expires,
558				  &interval, task1, tick_event, NULL,
559				  &tickertimer);
560	assert_int_equal(result, ISC_R_SUCCESS);
561
562	oncetimer = NULL;
563
564	isc_interval_set(&interval, (seconds * 2) + 1, 0);
565	result = isc_time_nowplusinterval(&expires, &interval);
566	assert_int_equal(result, ISC_R_SUCCESS);
567
568	isc_interval_set(&interval, 0, 0);
569	result = isc_timer_create(timermgr, isc_timertype_once, &expires,
570				  &interval, task2, once_event, NULL,
571				  &oncetimer);
572	assert_int_equal(result, ISC_R_SUCCESS);
573
574	/*
575	 * Wait for shutdown processing to complete.
576	 */
577	while (!atomic_load(&shutdownflag)) {
578		uv_sleep(1);
579	}
580
581	assert_int_equal(atomic_load(&errcnt), ISC_R_SUCCESS);
582
583	assert_int_equal(atomic_load(&eventcnt), 1);
584
585	isc_timer_destroy(&tickertimer);
586	isc_timer_destroy(&oncetimer);
587	isc_task_destroy(&task1);
588	isc_task_destroy(&task2);
589}
590
591ISC_TEST_LIST_START
592
593ISC_TEST_ENTRY_CUSTOM(ticker, _setup, _teardown)
594ISC_TEST_ENTRY_CUSTOM(once_life, _setup, _teardown)
595ISC_TEST_ENTRY_CUSTOM(once_idle, _setup, _teardown)
596ISC_TEST_ENTRY_CUSTOM(reset, _setup, _teardown)
597ISC_TEST_ENTRY_CUSTOM(purge, _setup, _teardown)
598
599ISC_TEST_LIST_END
600
601ISC_TEST_MAIN
602