Home | History | Annotate | Line # | Download | only in isc
timer_test.c revision 1.3
      1 /*	$NetBSD: timer_test.c,v 1.3 2025/01/26 16:25:50 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/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