/* * Copyright (C) Internet Systems Consortium, Inc. ("ISC") * * SPDX-License-Identifier: MPL-2.0 * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, you can obtain one at https://mozilla.org/MPL/2.0/. * * See the COPYRIGHT file distributed with this work for additional * information regarding copyright ownership. */ #if HAVE_CMOCKA #include #include /* IWYU pragma: keep */ #include #include #include #include #include #include #define UNIT_TESTING #include #include #include #include #include #include #include #include #include #include #include #include "../timer.c" #include "isctest.h" /* Set to true (or use -v option) for verbose output */ static bool verbose = false; #define FUDGE_SECONDS 0 /* in absence of clock_getres() */ #define FUDGE_NANOSECONDS 500000000 /* in absence of clock_getres() */ static isc_timer_t *timer = NULL; static isc_condition_t cv; static isc_mutex_t mx; static isc_time_t endtime; static isc_mutex_t lasttime_mx; static isc_time_t lasttime; static int seconds; static int nanoseconds; static atomic_int_fast32_t eventcnt; static atomic_uint_fast32_t errcnt; static int nevents; static int _setup(void **state) { isc_result_t result; UNUSED(state); /* Timer tests require two worker threads */ result = isc_test_begin(NULL, true, 2); assert_int_equal(result, ISC_R_SUCCESS); atomic_init(&errcnt, ISC_R_SUCCESS); return (0); } static int _teardown(void **state) { UNUSED(state); isc_test_end(); return (0); } static void test_shutdown(isc_task_t *task, isc_event_t *event) { isc_result_t result; UNUSED(task); /* * Signal shutdown processing complete. */ result = isc_mutex_lock(&mx); assert_int_equal(result, ISC_R_SUCCESS); result = isc_condition_signal(&cv); assert_int_equal(result, ISC_R_SUCCESS); result = isc_mutex_unlock(&mx); assert_int_equal(result, ISC_R_SUCCESS); isc_event_free(&event); } static void setup_test(isc_timertype_t timertype, isc_time_t *expires, isc_interval_t *interval, void (*action)(isc_task_t *, isc_event_t *)) { isc_result_t result; isc_task_t *task = NULL; isc_time_settoepoch(&endtime); atomic_init(&eventcnt, 0); isc_mutex_init(&mx); isc_mutex_init(&lasttime_mx); isc_condition_init(&cv); atomic_store(&errcnt, ISC_R_SUCCESS); LOCK(&mx); result = isc_task_create(taskmgr, 0, &task); assert_int_equal(result, ISC_R_SUCCESS); result = isc_task_onshutdown(task, test_shutdown, NULL); assert_int_equal(result, ISC_R_SUCCESS); isc_mutex_lock(&lasttime_mx); result = isc_time_now(&lasttime); isc_mutex_unlock(&lasttime_mx); assert_int_equal(result, ISC_R_SUCCESS); result = isc_timer_create(timermgr, timertype, expires, interval, task, action, (void *)timertype, &timer); assert_int_equal(result, ISC_R_SUCCESS); /* * Wait for shutdown processing to complete. */ while (atomic_load(&eventcnt) != nevents) { result = isc_condition_wait(&cv, &mx); assert_int_equal(result, ISC_R_SUCCESS); } UNLOCK(&mx); assert_int_equal(atomic_load(&errcnt), ISC_R_SUCCESS); isc_task_detach(&task); isc_mutex_destroy(&mx); isc_mutex_destroy(&lasttime_mx); (void)isc_condition_destroy(&cv); } static void set_global_error(isc_result_t result) { (void)atomic_compare_exchange_strong( &errcnt, &(uint_fast32_t){ ISC_R_SUCCESS }, result); } static void subthread_assert_true(bool expected, const char *file, unsigned int line) { if (!expected) { printf("# %s:%u subthread_assert_true\n", file, line); set_global_error(ISC_R_UNEXPECTED); } } #define subthread_assert_true(expected) \ subthread_assert_true(expected, __FILE__, __LINE__) static void subthread_assert_int_equal(int observed, int expected, const char *file, unsigned int line) { if (observed != expected) { printf("# %s:%u subthread_assert_int_equal(%d != %d)\n", file, line, observed, expected); set_global_error(ISC_R_UNEXPECTED); } } #define subthread_assert_int_equal(observed, expected) \ subthread_assert_int_equal(observed, expected, __FILE__, __LINE__) static void subthread_assert_result_equal(isc_result_t result, isc_result_t expected, const char *file, unsigned int line) { if (result != expected) { printf("# %s:%u subthread_assert_result_equal(%u != %u)\n", file, line, result, expected); set_global_error(result); } } #define subthread_assert_result_equal(observed, expected) \ subthread_assert_result_equal(observed, expected, __FILE__, __LINE__) static void ticktock(isc_task_t *task, isc_event_t *event) { isc_result_t result; isc_time_t now; isc_time_t base; isc_time_t ulim; isc_time_t llim; isc_interval_t interval; isc_eventtype_t expected_event_type; int tick = atomic_fetch_add(&eventcnt, 1); if (verbose) { print_message("# tick %d\n", tick); } expected_event_type = ISC_TIMEREVENT_LIFE; if ((uintptr_t)event->ev_arg == isc_timertype_ticker) { expected_event_type = ISC_TIMEREVENT_TICK; } if (event->ev_type != expected_event_type) { print_error("# expected event type %u, got %u\n", expected_event_type, event->ev_type); } result = isc_time_now(&now); subthread_assert_result_equal(result, ISC_R_SUCCESS); isc_interval_set(&interval, seconds, nanoseconds); isc_mutex_lock(&lasttime_mx); result = isc_time_add(&lasttime, &interval, &base); isc_mutex_unlock(&lasttime_mx); subthread_assert_result_equal(result, ISC_R_SUCCESS); isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS); result = isc_time_add(&base, &interval, &ulim); subthread_assert_result_equal(result, ISC_R_SUCCESS); result = isc_time_subtract(&base, &interval, &llim); subthread_assert_result_equal(result, ISC_R_SUCCESS); subthread_assert_true(isc_time_compare(&llim, &now) <= 0); subthread_assert_true(isc_time_compare(&ulim, &now) >= 0); isc_interval_set(&interval, 0, 0); isc_mutex_lock(&lasttime_mx); result = isc_time_add(&now, &interval, &lasttime); isc_mutex_unlock(&lasttime_mx); subthread_assert_result_equal(result, ISC_R_SUCCESS); isc_event_free(&event); if (atomic_load(&eventcnt) == nevents) { result = isc_time_now(&endtime); subthread_assert_result_equal(result, ISC_R_SUCCESS); isc_timer_destroy(&timer); isc_task_shutdown(task); } } /* * Individual unit tests */ /* timer type ticker */ static void ticker(void **state) { isc_time_t expires; isc_interval_t interval; UNUSED(state); nevents = 12; seconds = 0; nanoseconds = 500000000; isc_interval_set(&interval, seconds, nanoseconds); isc_time_settoepoch(&expires); setup_test(isc_timertype_ticker, &expires, &interval, ticktock); } /* timer type once reaches lifetime */ static void once_life(void **state) { isc_result_t result; isc_time_t expires; isc_interval_t interval; UNUSED(state); nevents = 1; seconds = 1; nanoseconds = 100000000; isc_interval_set(&interval, seconds, nanoseconds); result = isc_time_nowplusinterval(&expires, &interval); assert_int_equal(result, ISC_R_SUCCESS); isc_interval_set(&interval, 0, 0); setup_test(isc_timertype_once, &expires, &interval, ticktock); } static void test_idle(isc_task_t *task, isc_event_t *event) { isc_result_t result; isc_time_t now; isc_time_t base; isc_time_t ulim; isc_time_t llim; isc_interval_t interval; int tick = atomic_fetch_add(&eventcnt, 1); if (verbose) { print_message("# tick %d\n", tick); } result = isc_time_now(&now); subthread_assert_result_equal(result, ISC_R_SUCCESS); isc_interval_set(&interval, seconds, nanoseconds); isc_mutex_lock(&lasttime_mx); result = isc_time_add(&lasttime, &interval, &base); isc_mutex_unlock(&lasttime_mx); subthread_assert_result_equal(result, ISC_R_SUCCESS); isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS); result = isc_time_add(&base, &interval, &ulim); subthread_assert_result_equal(result, ISC_R_SUCCESS); result = isc_time_subtract(&base, &interval, &llim); subthread_assert_result_equal(result, ISC_R_SUCCESS); subthread_assert_true(isc_time_compare(&llim, &now) <= 0); subthread_assert_true(isc_time_compare(&ulim, &now) >= 0); isc_interval_set(&interval, 0, 0); isc_mutex_lock(&lasttime_mx); isc_time_add(&now, &interval, &lasttime); isc_mutex_unlock(&lasttime_mx); subthread_assert_int_equal(event->ev_type, ISC_TIMEREVENT_IDLE); isc_event_free(&event); isc_timer_destroy(&timer); isc_task_shutdown(task); } /* timer type once idles out */ static void once_idle(void **state) { isc_result_t result; isc_time_t expires; isc_interval_t interval; UNUSED(state); nevents = 1; seconds = 1; nanoseconds = 200000000; isc_interval_set(&interval, seconds + 1, nanoseconds); result = isc_time_nowplusinterval(&expires, &interval); assert_int_equal(result, ISC_R_SUCCESS); isc_interval_set(&interval, seconds, nanoseconds); setup_test(isc_timertype_once, &expires, &interval, test_idle); } /* timer reset */ static void test_reset(isc_task_t *task, isc_event_t *event) { isc_result_t result; isc_time_t now; isc_time_t base; isc_time_t ulim; isc_time_t llim; isc_time_t expires; isc_interval_t interval; int tick = atomic_fetch_add(&eventcnt, 1); if (verbose) { print_message("# tick %d\n", tick); } /* * Check expired time. */ result = isc_time_now(&now); subthread_assert_result_equal(result, ISC_R_SUCCESS); isc_interval_set(&interval, seconds, nanoseconds); isc_mutex_lock(&lasttime_mx); result = isc_time_add(&lasttime, &interval, &base); isc_mutex_unlock(&lasttime_mx); subthread_assert_result_equal(result, ISC_R_SUCCESS); isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS); result = isc_time_add(&base, &interval, &ulim); subthread_assert_result_equal(result, ISC_R_SUCCESS); result = isc_time_subtract(&base, &interval, &llim); subthread_assert_result_equal(result, ISC_R_SUCCESS); subthread_assert_true(isc_time_compare(&llim, &now) <= 0); subthread_assert_true(isc_time_compare(&ulim, &now) >= 0); isc_interval_set(&interval, 0, 0); isc_mutex_lock(&lasttime_mx); isc_time_add(&now, &interval, &lasttime); isc_mutex_unlock(&lasttime_mx); int _eventcnt = atomic_load(&eventcnt); if (_eventcnt < 3) { subthread_assert_int_equal(event->ev_type, ISC_TIMEREVENT_TICK); if (_eventcnt == 2) { isc_interval_set(&interval, seconds, nanoseconds); result = isc_time_nowplusinterval(&expires, &interval); subthread_assert_result_equal(result, ISC_R_SUCCESS); isc_interval_set(&interval, 0, 0); result = isc_timer_reset(timer, isc_timertype_once, &expires, &interval, false); subthread_assert_result_equal(result, ISC_R_SUCCESS); } isc_event_free(&event); } else { subthread_assert_int_equal(event->ev_type, ISC_TIMEREVENT_LIFE); isc_event_free(&event); isc_timer_destroy(&timer); isc_task_shutdown(task); } } static void reset(void **state) { isc_time_t expires; isc_interval_t interval; UNUSED(state); nevents = 3; seconds = 0; nanoseconds = 750000000; isc_interval_set(&interval, seconds, nanoseconds); isc_time_settoepoch(&expires); setup_test(isc_timertype_ticker, &expires, &interval, test_reset); } static atomic_bool startflag; static atomic_bool shutdownflag; static isc_timer_t *tickertimer = NULL; static isc_timer_t *oncetimer = NULL; static isc_task_t *task1 = NULL; static isc_task_t *task2 = NULL; /* * task1 blocks on mx while events accumulate * in its queue, until signaled by task2. */ static void tick_event(isc_task_t *task, isc_event_t *event) { isc_result_t result; isc_time_t expires; isc_interval_t interval; UNUSED(task); if (!atomic_load(&startflag)) { if (verbose) { print_message("# tick_event %d\n", -1); } isc_event_free(&event); return; } int tick = atomic_fetch_add(&eventcnt, 1); if (verbose) { print_message("# tick_event %d\n", tick); } /* * On the first tick, purge all remaining tick events * and then shut down the task. */ if (tick == 0) { isc_time_settoepoch(&expires); isc_interval_set(&interval, seconds, 0); result = isc_timer_reset(tickertimer, isc_timertype_ticker, &expires, &interval, true); subthread_assert_result_equal(result, ISC_R_SUCCESS); isc_task_shutdown(task); } isc_event_free(&event); } static void once_event(isc_task_t *task, isc_event_t *event) { if (verbose) { print_message("# once_event\n"); } /* * Allow task1 to start processing events. */ atomic_store(&startflag, true); isc_event_free(&event); isc_task_shutdown(task); } static void shutdown_purge(isc_task_t *task, isc_event_t *event) { UNUSED(task); UNUSED(event); if (verbose) { print_message("# shutdown_event\n"); } /* * Signal shutdown processing complete. */ atomic_store(&shutdownflag, 1); isc_event_free(&event); } /* timer events purged */ static void purge(void **state) { isc_result_t result; isc_time_t expires; isc_interval_t interval; UNUSED(state); atomic_init(&startflag, 0); atomic_init(&shutdownflag, 0); atomic_init(&eventcnt, 0); seconds = 1; nanoseconds = 0; result = isc_task_create(taskmgr, 0, &task1); assert_int_equal(result, ISC_R_SUCCESS); result = isc_task_onshutdown(task1, shutdown_purge, NULL); assert_int_equal(result, ISC_R_SUCCESS); result = isc_task_create(taskmgr, 0, &task2); assert_int_equal(result, ISC_R_SUCCESS); isc_time_settoepoch(&expires); isc_interval_set(&interval, seconds, 0); tickertimer = NULL; result = isc_timer_create(timermgr, isc_timertype_ticker, &expires, &interval, task1, tick_event, NULL, &tickertimer); assert_int_equal(result, ISC_R_SUCCESS); oncetimer = NULL; isc_interval_set(&interval, (seconds * 2) + 1, 0); result = isc_time_nowplusinterval(&expires, &interval); assert_int_equal(result, ISC_R_SUCCESS); isc_interval_set(&interval, 0, 0); result = isc_timer_create(timermgr, isc_timertype_once, &expires, &interval, task2, once_event, NULL, &oncetimer); assert_int_equal(result, ISC_R_SUCCESS); /* * Wait for shutdown processing to complete. */ while (!atomic_load(&shutdownflag)) { isc_test_nap(1000); } assert_int_equal(atomic_load(&errcnt), ISC_R_SUCCESS); assert_int_equal(atomic_load(&eventcnt), 1); isc_timer_destroy(&tickertimer); isc_timer_destroy(&oncetimer); isc_task_destroy(&task1); isc_task_destroy(&task2); } int main(int argc, char **argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(ticker), cmocka_unit_test(once_life), cmocka_unit_test(once_idle), cmocka_unit_test(reset), cmocka_unit_test(purge), }; int c; while ((c = isc_commandline_parse(argc, argv, "v")) != -1) { switch (c) { case 'v': verbose = true; break; default: break; } } return (cmocka_run_group_tests(tests, _setup, _teardown)); } #else /* HAVE_CMOCKA */ #include int main(void) { printf("1..0 # Skipped: cmocka not available\n"); return (SKIPPED_TEST_EXIT_CODE); } #endif /* if HAVE_CMOCKA */