diff options
Diffstat (limited to '')
-rw-r--r-- | lib/isc/tests/timer_test.c | 634 |
1 files changed, 634 insertions, 0 deletions
diff --git a/lib/isc/tests/timer_test.c b/lib/isc/tests/timer_test.c new file mode 100644 index 0000000..9bf4cf7 --- /dev/null +++ b/lib/isc/tests/timer_test.c @@ -0,0 +1,634 @@ +/* + * 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 <inttypes.h> +#include <sched.h> /* IWYU pragma: keep */ +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/atomic.h> +#include <isc/commandline.h> +#include <isc/condition.h> +#include <isc/mem.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/task.h> +#include <isc/time.h> +#include <isc/timer.h> +#include <isc/util.h> + +#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 <stdio.h> + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ |