#include "common.h"
#include <sys/time.h>
#define MILLION 1000000
#define THOUSAND 1000
#define SEC_TO_MS(t) ((t) * THOUSAND)
#define SEC_TO_US(t) ((t) * MILLION)
#define MS_TO_US(t) ((t) * THOUSAND)
#define US_TO_NS(t) ((t) * THOUSAND)
static uint64_t
now(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
return SEC_TO_US((uint64_t)tv.tv_sec) + tv.tv_usec;
}
static void
mssleep(int t)
{
struct timespec stime = {
.tv_sec = 0,
.tv_nsec = US_TO_NS(MS_TO_US(t)),
};
nanosleep(&stime, NULL);
}
static void
ussleep(int t)
{
struct timespec stime = {
.tv_sec = 0,
.tv_nsec = US_TO_NS(t),
};
nanosleep(&stime, NULL);
}
static void
test_kevent_timer_add(void)
{
const char *test_id = "kevent(EVFILT_TIMER, EV_ADD)";
struct kevent kev;
test_begin(test_id);
EV_SET(&kev, 1, EVFILT_TIMER, EV_ADD, 0, 1000, NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
success();
}
static void
test_kevent_timer_del(void)
{
const char *test_id = "kevent(EVFILT_TIMER, EV_DELETE)";
struct kevent kev;
test_begin(test_id);
EV_SET(&kev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
test_no_kevents();
success();
}
static void
test_kevent_timer_get(void)
{
const char *test_id = "kevent(EVFILT_TIMER, wait)";
struct kevent kev;
test_begin(test_id);
EV_SET(&kev, 1, EVFILT_TIMER, EV_ADD, 0, 1000, NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags |= EV_CLEAR;
kev.data = 1;
kevent_cmp(&kev, kevent_get(kqfd));
EV_SET(&kev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
success();
}
static void
test_oneshot(void)
{
const char *test_id = "kevent(EVFILT_TIMER, EV_ONESHOT)";
struct kevent kev;
test_begin(test_id);
test_no_kevents();
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, 500,NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags = EV_ADD | EV_CLEAR | EV_ONESHOT;
kev.data = 1;
kevent_cmp(&kev, kevent_get(kqfd));
sleep(3);
test_no_kevents();
success();
}
static void
test_periodic(void)
{
const char *test_id = "kevent(EVFILT_TIMER, periodic)";
struct kevent kev;
test_begin(test_id);
test_no_kevents();
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, 1000,NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags = EV_ADD | EV_CLEAR;
kev.data = 1;
kevent_cmp(&kev, kevent_get(kqfd));
sleep(1);
kevent_cmp(&kev, kevent_get(kqfd));
kev.flags = EV_DELETE;
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
success();
}
static void
test_periodic_modify(void)
{
const char *test_id = "kevent(EVFILT_TIMER, periodic_modify)";
struct kevent kev;
test_begin(test_id);
test_no_kevents();
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, 1000, NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags = EV_ADD | EV_CLEAR;
kev.data = 1;
kevent_cmp(&kev, kevent_get(kqfd));
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, 495, NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags = EV_ADD | EV_CLEAR;
sleep(1);
kev.data = 2;
kevent_cmp(&kev, kevent_get(kqfd));
kev.flags = EV_DELETE;
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
success();
}
#if WITH_NATIVE_KQUEUE_BUGS
static void
test_periodic_to_oneshot(void)
{
const char *test_id = "kevent(EVFILT_TIMER, period_to_oneshot)";
struct kevent kev;
test_begin(test_id);
test_no_kevents();
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, 1000, NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags = EV_ADD | EV_CLEAR;
kev.data = 1;
kevent_cmp(&kev, kevent_get(kqfd));
sleep(1);
kevent_cmp(&kev, kevent_get(kqfd));
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, 500, NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags = EV_ADD | EV_CLEAR | EV_ONESHOT;
sleep(1);
kev.data = 1;
kevent_cmp(&kev, kevent_get(kqfd));
success();
}
#endif
static void
test_disable_and_enable(void)
{
const char *test_id = "kevent(EVFILT_TIMER, EV_DISABLE and EV_ENABLE)";
struct kevent kev;
test_begin(test_id);
test_no_kevents();
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, 2000,NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags = EV_DISABLE;
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
test_no_kevents();
kev.flags = EV_ENABLE;
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags = EV_ADD | EV_CLEAR | EV_ONESHOT;
kev.data = 1;
kevent_cmp(&kev, kevent_get(kqfd));
success();
}
static void
test_abstime(void)
{
const char *test_id = "kevent(EVFILT_TIMER, EV_ONESHOT, NOTE_ABSTIME)";
struct kevent kev;
uint64_t end, start, stop;
const int timeout_sec = 3;
test_begin(test_id);
test_no_kevents();
start = now();
end = start + SEC_TO_US(timeout_sec);
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,
NOTE_ABSTIME | NOTE_USECONDS, end, NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags = EV_ADD | EV_ONESHOT;
kev.data = 1;
kev.fflags = 0;
kevent_cmp(&kev, kevent_get(kqfd));
stop = now();
if (stop < end)
err(1, "too early %jd %jd", (intmax_t)stop, (intmax_t)end);
sleep(3);
test_no_kevents();
success();
}
static void
test_abstime_epoch(void)
{
const char *test_id = "kevent(EVFILT_TIMER (EPOCH), NOTE_ABSTIME)";
struct kevent kev;
test_begin(test_id);
test_no_kevents();
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, NOTE_ABSTIME, 0,
NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags = EV_ADD;
kev.data = 1;
kev.fflags = 0;
kevent_cmp(&kev, kevent_get(kqfd));
kev.flags = EV_DELETE;
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
success();
}
static void
test_abstime_preboot(void)
{
const char *test_id = "kevent(EVFILT_TIMER (PREBOOT), EV_ONESHOT, NOTE_ABSTIME)";
struct kevent kev;
struct timespec btp;
uint64_t end, start, stop;
test_begin(test_id);
test_no_kevents();
start = now();
if (clock_gettime(CLOCK_BOOTTIME, &btp) != 0)
err(1, "%s", test_id);
end = start - SEC_TO_US(btp.tv_sec + 1);
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,
NOTE_ABSTIME | NOTE_USECONDS, end, NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags = EV_ADD | EV_ONESHOT;
kev.data = 1;
kev.fflags = 0;
kevent_cmp(&kev, kevent_get(kqfd));
stop = now();
if (stop < end)
err(1, "too early %jd %jd", (intmax_t)stop, (intmax_t)end);
sleep(3);
test_no_kevents();
success();
}
static void
test_abstime_postboot(void)
{
const char *test_id = "kevent(EVFILT_TIMER (POSTBOOT), EV_ONESHOT, NOTE_ABSTIME)";
struct kevent kev;
uint64_t end, start, stop;
const int timeout_sec = 1;
test_begin(test_id);
test_no_kevents();
start = now();
end = start - SEC_TO_US(timeout_sec);
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,
NOTE_ABSTIME | NOTE_USECONDS, end, NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags = EV_ADD | EV_ONESHOT;
kev.data = 1;
kev.fflags = 0;
kevent_cmp(&kev, kevent_get(kqfd));
stop = now();
if (stop < end)
err(1, "too early %jd %jd", (intmax_t)stop, (intmax_t)end);
sleep(3);
test_no_kevents();
success();
}
static void
test_update(void)
{
const char *test_id = "kevent(EVFILT_TIMER (UPDATE), EV_ADD | EV_ONESHOT)";
struct kevent kev;
long elapsed;
uint64_t start;
test_begin(test_id);
test_no_kevents();
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,
NOTE_USECONDS, SEC_TO_US(1), (void *)1);
start = now();
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,
NOTE_USECONDS, MS_TO_US(1), (void *)2);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags |= EV_CLEAR;
kev.fflags &= ~NOTE_USECONDS;
kev.data = 1;
kevent_cmp(&kev, kevent_get(kqfd));
elapsed = now() - start;
printf("timer expired after %ld us\n", elapsed);
if (elapsed < MS_TO_US(1))
errx(1, "early timer expiration: %ld us", elapsed);
if (elapsed > SEC_TO_US(1))
errx(1, "late timer expiration: %ld us", elapsed);
success();
}
static void
test_update_equal(void)
{
const char *test_id = "kevent(EVFILT_TIMER (UPDATE=), EV_ADD | EV_ONESHOT)";
struct kevent kev;
long elapsed;
uint64_t start;
test_begin(test_id);
test_no_kevents();
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,
NOTE_USECONDS, MS_TO_US(1), NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
ussleep(600);
start = now();
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags |= EV_CLEAR;
kev.fflags &= ~NOTE_USECONDS;
kev.data = 1;
kevent_cmp(&kev, kevent_get(kqfd));
elapsed = now() - start;
printf("timer expired after %ld us\n", elapsed);
if (elapsed < MS_TO_US(1))
errx(1, "early timer expiration: %ld us", elapsed);
success();
}
static void
test_update_expired(void)
{
const char *test_id = "kevent(EVFILT_TIMER (UPDATE EXP), EV_ADD | EV_ONESHOT)";
struct kevent kev;
long elapsed;
uint64_t start;
test_begin(test_id);
test_no_kevents();
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,
NOTE_USECONDS, MS_TO_US(1), NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
mssleep(2);
start = now();
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags |= EV_CLEAR;
kev.fflags &= ~NOTE_USECONDS;
kev.data = 1;
kevent_cmp(&kev, kevent_get(kqfd));
elapsed = now() - start;
printf("timer expired after %ld us\n", elapsed);
if (elapsed < MS_TO_US(1))
errx(1, "early timer expiration: %ld us", elapsed);
mssleep(2);
test_no_kevents();
success();
}
static void
test_update_periodic(void)
{
const char *test_id = "kevent(EVFILT_TIMER (UPDATE), periodic)";
struct kevent kev;
long elapsed;
uint64_t start, stop;
test_begin(test_id);
test_no_kevents();
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, SEC_TO_MS(1), NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags = EV_ADD | EV_CLEAR;
kev.data = 1;
kevent_cmp(&kev, kevent_get(kqfd));
sleep(1);
kevent_cmp(&kev, kevent_get(kqfd));
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, SEC_TO_MS(2), NULL);
start = now();
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags = EV_ADD | EV_CLEAR;
kev.data = 1;
kevent_cmp(&kev, kevent_get(kqfd));
stop = now();
elapsed = stop - start;
printf("timer expired after %ld us\n", elapsed);
if (elapsed < MS_TO_US(2))
errx(1, "early timer expiration: %ld us", elapsed);
kev.flags = EV_DELETE;
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
success();
}
static void
test_update_timing(void)
{
#define MIN_SLEEP 500
#define MAX_SLEEP 1500
const char *test_id = "kevent(EVFILT_TIMER (UPDATE TIMING), EV_ADD | EV_ONESHOT)";
struct kevent kev;
int iteration;
int sleeptime;
long elapsed;
uint64_t start, stop;
test_begin(test_id);
test_no_kevents();
for (sleeptime = MIN_SLEEP, iteration = 1;
sleeptime < MAX_SLEEP;
++sleeptime, ++iteration) {
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,
NOTE_USECONDS, MS_TO_US(1), NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
ussleep(sleeptime);
start = now();
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags |= EV_CLEAR;
kev.fflags &= ~NOTE_USECONDS;
kev.data = 1;
kevent_cmp(&kev, kevent_get(kqfd));
stop = now();
elapsed = stop - start;
if (elapsed < MS_TO_US(1))
errx(1, "early timer expiration: %ld us", elapsed);
mssleep(2);
test_no_kevents_quietly();
}
success();
}
static void
test_dispatch(void)
{
const char *test_id = "kevent(EVFILT_TIMER, EV_ADD | EV_DISPATCH)";
struct kevent kev;
test_no_kevents();
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_DISPATCH, 0, 200, NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
kev.flags = EV_ADD | EV_CLEAR | EV_DISPATCH;
kev.data = 1;
kevent_cmp(&kev, kevent_get(kqfd));
usleep(500000);
test_no_kevents();
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ENABLE | EV_DISPATCH, 0, 200, NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
test_no_kevents();
usleep(1100000);
kev.flags = EV_ADD | EV_CLEAR | EV_DISPATCH;
kev.data = 5;
kevent_cmp(&kev, kevent_get(kqfd));
EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
err(1, "%s", test_id);
usleep(500000);
test_no_kevents();
success();
}
void
test_evfilt_timer(void)
{
kqfd = kqueue();
test_kevent_timer_add();
test_kevent_timer_del();
test_kevent_timer_get();
test_oneshot();
test_periodic();
test_periodic_modify();
#if WITH_NATIVE_KQUEUE_BUGS
test_periodic_to_oneshot();
#endif
test_abstime();
test_abstime_epoch();
test_abstime_preboot();
test_abstime_postboot();
test_update();
test_update_equal();
test_update_expired();
test_update_timing();
test_update_periodic();
test_disable_and_enable();
test_dispatch();
close(kqfd);
}