mirror of
https://github.com/isc-projects/bind9.git
synced 2026-03-12 05:32:42 -04:00
Instead of relying on unreliable order of execution of the library constructors and destructors, move them to individual binaries. The advantage is that the execution time and order will remain constant and will not depend on the dynamic load dependency solver. This requires more work, but that was mitigated by a simple requirement, any executable using libisc and libdns, must include <isc/lib.h> and <dns/lib.h> respectively (in this particular order). In turn, these two headers must not be included from within any library as they contain inlined functions marked with constructor/destructor attributes.
538 lines
13 KiB
C
538 lines
13 KiB
C
/*
|
|
* 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.
|
|
*/
|
|
|
|
#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/job.h>
|
|
#include <isc/lib.h>
|
|
#include <isc/loop.h>
|
|
#include <isc/mem.h>
|
|
#include <isc/os.h>
|
|
#include <isc/time.h>
|
|
#include <isc/timer.h>
|
|
#include <isc/util.h>
|
|
|
|
#include "timer.c"
|
|
|
|
#include <tests/isc.h>
|
|
|
|
/* Set to true (or use -v option) for verbose output */
|
|
static bool verbose = true;
|
|
|
|
#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_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;
|
|
|
|
typedef struct setup_test_arg {
|
|
isc_timertype_t timertype;
|
|
isc_interval_t *interval;
|
|
isc_job_cb action;
|
|
} setup_test_arg_t;
|
|
|
|
static void
|
|
setup_test_run(void *data) {
|
|
isc_timertype_t timertype = ((setup_test_arg_t *)data)->timertype;
|
|
isc_interval_t *interval = ((setup_test_arg_t *)data)->interval;
|
|
isc_job_cb action = ((setup_test_arg_t *)data)->action;
|
|
|
|
isc_mutex_lock(&lasttime_mx);
|
|
lasttime = isc_time_now();
|
|
UNLOCK(&lasttime_mx);
|
|
|
|
isc_timer_create(mainloop, action, (void *)timertype, &timer);
|
|
isc_timer_start(timer, timertype, interval);
|
|
}
|
|
|
|
static void
|
|
setup_test(isc_timertype_t timertype, isc_interval_t *interval,
|
|
isc_job_cb action) {
|
|
setup_test_arg_t arg = { .timertype = timertype,
|
|
.interval = interval,
|
|
.action = action };
|
|
|
|
isc_time_settoepoch(&endtime);
|
|
atomic_init(&eventcnt, 0);
|
|
|
|
isc_mutex_init(&lasttime_mx);
|
|
|
|
atomic_store(&errcnt, ISC_R_SUCCESS);
|
|
|
|
isc_loop_setup(mainloop, setup_test_run, &arg);
|
|
isc_loopmgr_run(loopmgr);
|
|
|
|
assert_int_equal(atomic_load(&errcnt), ISC_R_SUCCESS);
|
|
|
|
isc_mutex_destroy(&lasttime_mx);
|
|
}
|
|
|
|
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_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, (unsigned int)result,
|
|
(unsigned int)expected);
|
|
set_global_error(result);
|
|
}
|
|
}
|
|
#define subthread_assert_result_equal(observed, expected) \
|
|
subthread_assert_result_equal(observed, expected, __FILE__, __LINE__)
|
|
|
|
static void
|
|
ticktock(void *arg) {
|
|
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);
|
|
|
|
UNUSED(arg);
|
|
|
|
if (verbose) {
|
|
print_message("# tick %d\n", tick);
|
|
}
|
|
|
|
now = isc_time_now();
|
|
|
|
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);
|
|
|
|
if (atomic_load(&eventcnt) == nevents) {
|
|
endtime = isc_time_now();
|
|
isc_timer_destroy(&timer);
|
|
isc_loopmgr_shutdown(loopmgr);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Individual unit tests
|
|
*/
|
|
|
|
/* timer type ticker */
|
|
ISC_RUN_TEST_IMPL(ticker) {
|
|
isc_interval_t interval;
|
|
|
|
UNUSED(state);
|
|
|
|
nevents = 12;
|
|
seconds = 0;
|
|
nanoseconds = 500000000;
|
|
|
|
isc_interval_set(&interval, seconds, nanoseconds);
|
|
|
|
setup_test(isc_timertype_ticker, &interval, ticktock);
|
|
}
|
|
|
|
static void
|
|
test_idle(void *arg) {
|
|
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);
|
|
|
|
UNUSED(arg);
|
|
|
|
if (verbose) {
|
|
print_message("# tick %d\n", tick);
|
|
}
|
|
|
|
now = isc_time_now();
|
|
|
|
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);
|
|
|
|
isc_timer_destroy(&timer);
|
|
isc_loopmgr_shutdown(loopmgr);
|
|
}
|
|
|
|
/* timer type once idles out */
|
|
ISC_RUN_TEST_IMPL(once_idle) {
|
|
isc_interval_t interval;
|
|
|
|
UNUSED(state);
|
|
|
|
nevents = 1;
|
|
seconds = 1;
|
|
nanoseconds = 200000000;
|
|
|
|
isc_interval_set(&interval, seconds, nanoseconds);
|
|
|
|
setup_test(isc_timertype_once, &interval, test_idle);
|
|
}
|
|
|
|
/* timer reset */
|
|
static void
|
|
test_reset(void *arg) {
|
|
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);
|
|
|
|
UNUSED(arg);
|
|
|
|
if (verbose) {
|
|
print_message("# tick %d\n", tick);
|
|
}
|
|
|
|
/*
|
|
* Check expired time.
|
|
*/
|
|
|
|
now = isc_time_now();
|
|
|
|
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);
|
|
|
|
if (tick < 2) {
|
|
if (tick == 1) {
|
|
isc_interval_set(&interval, seconds, nanoseconds);
|
|
isc_timer_start(timer, isc_timertype_once, &interval);
|
|
}
|
|
} else {
|
|
isc_timer_destroy(&timer);
|
|
isc_loopmgr_shutdown(loopmgr);
|
|
}
|
|
}
|
|
|
|
ISC_RUN_TEST_IMPL(reset) {
|
|
isc_interval_t interval;
|
|
|
|
UNUSED(state);
|
|
|
|
nevents = 3;
|
|
seconds = 0;
|
|
nanoseconds = 750000000;
|
|
|
|
isc_interval_set(&interval, seconds, nanoseconds);
|
|
|
|
setup_test(isc_timertype_ticker, &interval, test_reset);
|
|
}
|
|
|
|
static atomic_bool startflag;
|
|
static isc_timer_t *tickertimer = NULL;
|
|
static isc_timer_t *oncetimer = NULL;
|
|
|
|
static void
|
|
tick_event(void *arg) {
|
|
int tick;
|
|
|
|
UNUSED(arg);
|
|
|
|
if (!atomic_load(&startflag)) {
|
|
if (verbose) {
|
|
print_message("# tick_event %d\n", -1);
|
|
}
|
|
return;
|
|
}
|
|
|
|
tick = atomic_fetch_add(&eventcnt, 1);
|
|
if (verbose) {
|
|
print_message("# tick_event %d\n", tick);
|
|
}
|
|
|
|
/*
|
|
* On the first tick, purge all remaining tick events.
|
|
*/
|
|
if (tick == 0) {
|
|
isc_timer_destroy(&tickertimer);
|
|
isc_loopmgr_shutdown(loopmgr);
|
|
}
|
|
}
|
|
|
|
static void
|
|
once_event(void *arg) {
|
|
UNUSED(arg);
|
|
|
|
if (verbose) {
|
|
print_message("# once_event\n");
|
|
}
|
|
|
|
/*
|
|
* Allow task1 to start processing events.
|
|
*/
|
|
atomic_store(&startflag, true);
|
|
|
|
isc_timer_destroy(&oncetimer);
|
|
}
|
|
|
|
ISC_LOOP_SETUP_IMPL(purge) {
|
|
atomic_init(&eventcnt, 0);
|
|
atomic_store(&errcnt, ISC_R_SUCCESS);
|
|
}
|
|
|
|
ISC_LOOP_TEARDOWN_IMPL(purge) {
|
|
assert_int_equal(atomic_load(&errcnt), ISC_R_SUCCESS);
|
|
assert_int_equal(atomic_load(&eventcnt), 1);
|
|
}
|
|
|
|
/* timer events purged */
|
|
ISC_LOOP_TEST_SETUP_TEARDOWN_IMPL(purge) {
|
|
isc_interval_t interval;
|
|
|
|
UNUSED(arg);
|
|
|
|
if (verbose) {
|
|
print_message("# purge_run\n");
|
|
}
|
|
|
|
atomic_init(&startflag, 0);
|
|
seconds = 1;
|
|
nanoseconds = 0;
|
|
|
|
isc_interval_set(&interval, seconds, 0);
|
|
|
|
tickertimer = NULL;
|
|
isc_timer_create(mainloop, tick_event, NULL, &tickertimer);
|
|
isc_timer_start(tickertimer, isc_timertype_ticker, &interval);
|
|
|
|
oncetimer = NULL;
|
|
|
|
isc_interval_set(&interval, (seconds * 2) + 1, 0);
|
|
|
|
isc_timer_create(mainloop, once_event, NULL, &oncetimer);
|
|
isc_timer_start(oncetimer, isc_timertype_once, &interval);
|
|
}
|
|
|
|
/*
|
|
* Set of tests that check whether the rescheduling works as expected.
|
|
*/
|
|
|
|
isc_time_t timer_start;
|
|
isc_time_t timer_stop;
|
|
uint64_t timer_expect;
|
|
uint64_t timer_ticks;
|
|
isc_interval_t timer_interval;
|
|
isc_timertype_t timer_type;
|
|
|
|
ISC_LOOP_TEARDOWN_IMPL(timer_expect) {
|
|
uint64_t diff = isc_time_microdiff(&timer_stop, &timer_start) / 1000000;
|
|
assert_true(diff == timer_expect);
|
|
}
|
|
|
|
static void
|
|
timer_event(void *arg ISC_ATTR_UNUSED) {
|
|
if (--timer_ticks == 0) {
|
|
isc_timer_destroy(&timer);
|
|
isc_loopmgr_shutdown(loopmgr);
|
|
timer_stop = isc_loop_now(isc_loop());
|
|
} else {
|
|
isc_timer_start(timer, timer_type, &timer_interval);
|
|
}
|
|
}
|
|
|
|
ISC_LOOP_SETUP_IMPL(reschedule_up) {
|
|
timer_start = isc_loop_now(isc_loop());
|
|
timer_expect = 1;
|
|
timer_ticks = 1;
|
|
timer_type = isc_timertype_once;
|
|
}
|
|
|
|
ISC_LOOP_TEST_CUSTOM_IMPL(reschedule_up, setup_loop_reschedule_up,
|
|
teardown_loop_timer_expect) {
|
|
isc_timer_create(mainloop, timer_event, NULL, &timer);
|
|
|
|
/* Schedule the timer to fire immediately */
|
|
isc_interval_set(&timer_interval, 0, 0);
|
|
isc_timer_start(timer, timer_type, &timer_interval);
|
|
|
|
/* And then reschedule it to 1 second */
|
|
isc_interval_set(&timer_interval, 1, 0);
|
|
isc_timer_start(timer, timer_type, &timer_interval);
|
|
}
|
|
|
|
ISC_LOOP_SETUP_IMPL(reschedule_down) {
|
|
timer_start = isc_loop_now(isc_loop());
|
|
timer_expect = 0;
|
|
timer_ticks = 1;
|
|
timer_type = isc_timertype_once;
|
|
}
|
|
|
|
ISC_LOOP_TEST_CUSTOM_IMPL(reschedule_down, setup_loop_reschedule_down,
|
|
teardown_loop_timer_expect) {
|
|
isc_timer_create(mainloop, timer_event, NULL, &timer);
|
|
|
|
/* Schedule the timer to fire at 10 seconds */
|
|
isc_interval_set(&timer_interval, 10, 0);
|
|
isc_timer_start(timer, timer_type, &timer_interval);
|
|
|
|
/* And then reschedule it fire immediately */
|
|
isc_interval_set(&timer_interval, 0, 0);
|
|
isc_timer_start(timer, timer_type, &timer_interval);
|
|
}
|
|
|
|
ISC_LOOP_SETUP_IMPL(reschedule_from_callback) {
|
|
timer_start = isc_loop_now(isc_loop());
|
|
timer_expect = 1;
|
|
timer_ticks = 2;
|
|
timer_type = isc_timertype_once;
|
|
}
|
|
|
|
ISC_LOOP_TEST_CUSTOM_IMPL(reschedule_from_callback,
|
|
setup_loop_reschedule_from_callback,
|
|
teardown_loop_timer_expect) {
|
|
isc_timer_create(mainloop, timer_event, NULL, &timer);
|
|
|
|
isc_interval_set(&timer_interval, 0, NS_PER_SEC / 2);
|
|
isc_timer_start(timer, timer_type, &timer_interval);
|
|
}
|
|
|
|
ISC_LOOP_SETUP_IMPL(zero) {
|
|
timer_start = isc_loop_now(isc_loop());
|
|
timer_expect = 0;
|
|
timer_ticks = 1;
|
|
timer_type = isc_timertype_once;
|
|
}
|
|
|
|
ISC_LOOP_TEST_CUSTOM_IMPL(zero, setup_loop_zero, teardown_loop_timer_expect) {
|
|
isc_timer_create(mainloop, timer_event, NULL, &timer);
|
|
|
|
/* Schedule the timer to fire immediately (in the next event loop) */
|
|
isc_interval_set(&timer_interval, 0, 0);
|
|
isc_timer_start(timer, timer_type, &timer_interval);
|
|
}
|
|
|
|
ISC_LOOP_SETUP_IMPL(reschedule_ticker) {
|
|
timer_start = isc_loop_now(isc_loop());
|
|
timer_expect = 1;
|
|
timer_ticks = 5;
|
|
timer_type = isc_timertype_ticker;
|
|
}
|
|
|
|
ISC_LOOP_TEST_CUSTOM_IMPL(reschedule_ticker, setup_loop_reschedule_ticker,
|
|
teardown_loop_timer_expect) {
|
|
isc_timer_create(mainloop, timer_event, NULL, &timer);
|
|
|
|
/* Schedule the timer to fire immediately (in the next event loop) */
|
|
isc_interval_set(&timer_interval, 0, 0);
|
|
isc_timer_start(timer, timer_type, &timer_interval);
|
|
|
|
/* Then fire every 1/4 second */
|
|
isc_interval_set(&timer_interval, 0, NS_PER_SEC / 4);
|
|
}
|
|
|
|
ISC_TEST_LIST_START
|
|
|
|
ISC_TEST_ENTRY_CUSTOM(ticker, setup_loopmgr, teardown_loopmgr)
|
|
ISC_TEST_ENTRY_CUSTOM(once_idle, setup_loopmgr, teardown_loopmgr)
|
|
ISC_TEST_ENTRY_CUSTOM(reset, setup_loopmgr, teardown_loopmgr)
|
|
ISC_TEST_ENTRY_CUSTOM(purge, setup_loopmgr, teardown_loopmgr)
|
|
ISC_TEST_ENTRY_CUSTOM(reschedule_up, setup_loopmgr, teardown_loopmgr)
|
|
ISC_TEST_ENTRY_CUSTOM(reschedule_down, setup_loopmgr, teardown_loopmgr)
|
|
ISC_TEST_ENTRY_CUSTOM(reschedule_from_callback, setup_loopmgr, teardown_loopmgr)
|
|
ISC_TEST_ENTRY_CUSTOM(zero, setup_loopmgr, teardown_loopmgr)
|
|
ISC_TEST_ENTRY_CUSTOM(reschedule_ticker, setup_loopmgr, teardown_loopmgr)
|
|
|
|
ISC_TEST_LIST_END
|
|
|
|
ISC_TEST_MAIN
|