libvirt/tests/eventtest.c

503 lines
13 KiB
C

/*
* eventtest.c: Test the libvirtd event loop impl
*
* Copyright (C) 2009, 2011-2014 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <signal.h>
#include <time.h>
#if WITH_MACH_CLOCK_ROUTINES
# include <mach/clock.h>
# include <mach/mach.h>
#endif
#include "testutils.h"
#include "internal.h"
#include "virfile.h"
#include "virthread.h"
#include "virlog.h"
#include "virutil.h"
#include "virevent.h"
VIR_LOG_INIT("tests.eventtest");
#define NUM_FDS 31
#define NUM_TIME 31
static pthread_mutex_t eventThreadMutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t eventThreadCond = PTHREAD_COND_INITIALIZER;
static bool eventThreadSignaled;
static struct handleInfo {
int pipeFD[2];
int fired;
int watch;
int error;
int delete;
} handles[NUM_FDS];
static struct timerInfo {
int timeout;
int timer;
int fired;
int error;
int delete;
} timers[NUM_TIME];
enum {
EV_ERROR_NONE,
EV_ERROR_WATCH,
EV_ERROR_FD,
EV_ERROR_EVENT,
EV_ERROR_DATA,
};
struct testEventResultData {
bool failed;
const char *msg;
};
static int
testEventResultCallback(const void *opaque)
{
const struct testEventResultData *data = opaque;
if (data->failed && data->msg)
fprintf(stderr, "%s", data->msg);
return data->failed;
}
static void
G_GNUC_PRINTF(3, 4)
testEventReport(const char *name, bool failed, const char *msg, ...)
{
va_list vargs;
char *str = NULL;
struct testEventResultData data;
va_start(vargs, msg);
if (msg)
str = g_strdup_vprintf(msg, vargs);
data.failed = failed;
data.msg = str;
ignore_value(virTestRun(name, testEventResultCallback, &data));
va_end(vargs);
VIR_FREE(str);
}
static void
testPipeReader(int watch, int fd, int events, void *data)
{
struct handleInfo *info = data;
char one;
VIR_DEBUG("Handle callback watch=%d fd=%d ev=%d", watch, fd, events);
pthread_mutex_lock(&eventThreadMutex);
info->fired = 1;
if (watch != info->watch) {
info->error = EV_ERROR_WATCH;
goto cleanup;
}
if (fd != info->pipeFD[0]) {
info->error = EV_ERROR_FD;
goto cleanup;
}
if (!(events & VIR_EVENT_HANDLE_READABLE)) {
info->error = EV_ERROR_EVENT;
goto cleanup;
}
if (read(fd, &one, 1) != 1) {
info->error = EV_ERROR_DATA;
goto cleanup;
}
info->error = EV_ERROR_NONE;
if (info->delete != -1)
virEventRemoveHandle(info->delete);
cleanup:
pthread_cond_signal(&eventThreadCond);
eventThreadSignaled = true;
pthread_mutex_unlock(&eventThreadMutex);
}
static void
testTimer(int timer, void *data)
{
struct timerInfo *info = data;
VIR_DEBUG("Timer callback timer=%d", timer);
pthread_mutex_lock(&eventThreadMutex);
info->fired = 1;
if (timer != info->timer) {
info->error = EV_ERROR_WATCH;
goto cleanup;
}
info->error = EV_ERROR_NONE;
if (info->delete != -1)
virEventRemoveTimeout(info->delete);
cleanup:
pthread_cond_signal(&eventThreadCond);
eventThreadSignaled = true;
pthread_mutex_unlock(&eventThreadMutex);
}
G_GNUC_NORETURN static void *eventThreadLoop(void *data G_GNUC_UNUSED) {
while (1)
virEventRunDefaultImpl();
abort();
}
static void
waitEvents(int nhandle, int ntimer)
{
int ngothandle = 0;
int ngottimer = 0;
size_t i;
VIR_DEBUG("Wait events nhandle %d ntimer %d",
nhandle, ntimer);
while (ngothandle != nhandle || ngottimer != ntimer) {
while (!eventThreadSignaled)
pthread_cond_wait(&eventThreadCond, &eventThreadMutex);
eventThreadSignaled = false;
ngothandle = ngottimer = 0;
for (i = 0; i < NUM_FDS; i++) {
if (handles[i].fired)
ngothandle++;
}
for (i = 0; i < NUM_TIME; i++) {
if (timers[i].fired)
ngottimer++;
}
VIR_DEBUG("Wait events ngothandle %d ngottimer %d",
ngothandle, ngottimer);
}
}
static int
verifyFired(const char *name, int handle, int timer)
{
int handleFired = 0;
int timerFired = 0;
size_t i;
VIR_DEBUG("Verify fired handle %d timer %d", handle, timer);
for (i = 0; i < NUM_FDS; i++) {
if (handles[i].fired) {
if (i != handle) {
testEventReport(name, 1,
"Handle %zu fired, but expected %d\n", i,
handle);
return EXIT_FAILURE;
} else {
if (handles[i].error != EV_ERROR_NONE) {
testEventReport(name, 1,
"Handle %zu fired, but had error %d\n", i,
handles[i].error);
return EXIT_FAILURE;
}
handleFired = 1;
}
} else {
if (i == handle) {
testEventReport(name, 1,
"Handle %d should have fired, but didn't\n",
handle);
return EXIT_FAILURE;
}
}
}
if (handleFired != 1 && handle != -1) {
testEventReport(name, 1,
"Something weird happened, expecting handle %d\n",
handle);
return EXIT_FAILURE;
}
for (i = 0; i < NUM_TIME; i++) {
if (timers[i].fired) {
if (i != timer) {
testEventReport(name, 1,
"Timer %zu fired, but expected %d\n", i, timer);
return EXIT_FAILURE;
} else {
if (timers[i].error != EV_ERROR_NONE) {
testEventReport(name, 1,
"Timer %zu fired, but had error %d\n", i,
timers[i].error);
return EXIT_FAILURE;
}
timerFired = 1;
}
} else {
if (i == timer) {
testEventReport(name, 1,
"Timer %d should have fired, but didn't\n",
timer);
return EXIT_FAILURE;
}
}
}
if (timerFired != 1 && timer != -1) {
testEventReport(name, 1,
"Something weird happened, expecting timer %d\n",
timer);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
static int
finishJob(const char *name, int handle, int timer)
{
pthread_mutex_lock(&eventThreadMutex);
waitEvents(handle == -1 ? 0 : 1,
timer == -1 ? 0 : 1);
if (verifyFired(name, handle, timer) != EXIT_SUCCESS) {
pthread_mutex_unlock(&eventThreadMutex);
return EXIT_FAILURE;
}
testEventReport(name, 0, NULL);
pthread_mutex_unlock(&eventThreadMutex);
return EXIT_SUCCESS;
}
static void
resetAll(void)
{
size_t i;
pthread_mutex_lock(&eventThreadMutex);
for (i = 0; i < NUM_FDS; i++) {
handles[i].fired = 0;
handles[i].error = EV_ERROR_NONE;
}
for (i = 0; i < NUM_TIME; i++) {
timers[i].fired = 0;
timers[i].error = EV_ERROR_NONE;
}
pthread_mutex_unlock(&eventThreadMutex);
}
static int
mymain(void)
{
size_t i;
pthread_t eventThread;
char one = '1';
char *debugEnv = getenv("LIBVIRT_DEBUG");
for (i = 0; i < NUM_FDS; i++) {
if (virPipeQuiet(handles[i].pipeFD) < 0) {
fprintf(stderr, "Cannot create pipe: %d", errno);
return EXIT_FAILURE;
}
}
if (debugEnv && *debugEnv &&
(virLogSetDefaultPriority(virLogParseDefaultPriority(debugEnv)) < 0)) {
fprintf(stderr, "Invalid log level setting.\n");
return EXIT_FAILURE;
}
virEventRegisterDefaultImpl();
for (i = 0; i < NUM_FDS; i++) {
handles[i].delete = -1;
handles[i].watch =
virEventAddHandle(handles[i].pipeFD[0],
VIR_EVENT_HANDLE_READABLE,
testPipeReader,
&handles[i], NULL);
}
for (i = 0; i < NUM_TIME; i++) {
timers[i].delete = -1;
timers[i].timeout = -1;
timers[i].timer =
virEventAddTimeout(timers[i].timeout,
testTimer,
&timers[i], NULL);
}
pthread_create(&eventThread, NULL, eventThreadLoop, NULL);
/* First time, is easy - just try triggering one of our
* registered handles */
if (safewrite(handles[1].pipeFD[1], &one, 1) != 1)
return EXIT_FAILURE;
if (finishJob("Simple write", 1, -1) != EXIT_SUCCESS)
return EXIT_FAILURE;
resetAll();
/* Now lets delete one before starting poll(), and
* try triggering another handle */
virEventRemoveHandle(handles[0].watch);
if (safewrite(handles[1].pipeFD[1], &one, 1) != 1)
return EXIT_FAILURE;
if (finishJob("Deleted before poll", 1, -1) != EXIT_SUCCESS)
return EXIT_FAILURE;
resetAll();
/* Next lets delete *during* poll, which should interrupt
* the loop with no event showing */
virEventRemoveHandle(handles[1].watch);
if (finishJob("Interrupted during poll", -1, -1) != EXIT_SUCCESS)
return EXIT_FAILURE;
resetAll();
/* Getting more fun, lets delete a later handle during dispatch */
handles[2].delete = handles[3].watch;
if (safewrite(handles[2].pipeFD[1], &one, 1) != 1
|| safewrite(handles[3].pipeFD[1], &one, 1) != 1)
return EXIT_FAILURE;
if (finishJob("Deleted during dispatch", 2, -1) != EXIT_SUCCESS)
return EXIT_FAILURE;
resetAll();
/* Extreme fun, lets delete ourselves during dispatch */
handles[2].delete = handles[2].watch;
if (safewrite(handles[2].pipeFD[1], &one, 1) != 1)
return EXIT_FAILURE;
if (finishJob("Deleted during dispatch", 2, -1) != EXIT_SUCCESS)
return EXIT_FAILURE;
resetAll();
/* Run a timer on its own */
virEventUpdateTimeout(timers[1].timer, 100);
if (finishJob("Firing a timer", -1, 1) != EXIT_SUCCESS)
return EXIT_FAILURE;
virEventUpdateTimeout(timers[1].timer, -1);
resetAll();
/* Now lets delete one before starting poll(), and
* try triggering another timer */
virEventUpdateTimeout(timers[1].timer, 100);
virEventRemoveTimeout(timers[0].timer);
if (finishJob("Deleted before poll", -1, 1) != EXIT_SUCCESS)
return EXIT_FAILURE;
virEventUpdateTimeout(timers[1].timer, -1);
resetAll();
/* Next lets delete *during* poll, which should interrupt
* the loop with no event showing */
virEventRemoveTimeout(timers[1].timer);
if (finishJob("Interrupted during poll", -1, -1) != EXIT_SUCCESS)
return EXIT_FAILURE;
resetAll();
/* Getting more fun, lets delete a later timer during dispatch */
virEventUpdateTimeout(timers[2].timer, 100);
virEventUpdateTimeout(timers[3].timer, 100);
timers[2].delete = timers[3].timer;
if (finishJob("Deleted during dispatch", -1, 2) != EXIT_SUCCESS)
return EXIT_FAILURE;
virEventUpdateTimeout(timers[2].timer, -1);
resetAll();
/* Extreme fun, lets delete ourselves during dispatch */
virEventUpdateTimeout(timers[2].timer, 100);
timers[2].delete = timers[2].timer;
if (finishJob("Deleted during dispatch", -1, 2) != EXIT_SUCCESS)
return EXIT_FAILURE;
for (i = 0; i < NUM_FDS - 1; i++)
virEventRemoveHandle(handles[i].watch);
for (i = 0; i < NUM_TIME - 1; i++)
virEventRemoveTimeout(timers[i].timer);
resetAll();
/* Make sure the last handle still works several times in a row. */
for (i = 0; i < 4; i++) {
if (safewrite(handles[NUM_FDS - 1].pipeFD[1], &one, 1) != 1)
return EXIT_FAILURE;
if (finishJob("Simple write", NUM_FDS - 1, -1) != EXIT_SUCCESS)
return EXIT_FAILURE;
resetAll();
}
/* Final test, register same FD twice, once with no
* events, and make sure the right callback runs */
handles[0].pipeFD[0] = handles[1].pipeFD[0];
handles[0].pipeFD[1] = handles[1].pipeFD[1];
handles[0].watch = virEventAddHandle(handles[0].pipeFD[0],
0,
testPipeReader,
&handles[0], NULL);
handles[1].watch = virEventAddHandle(handles[1].pipeFD[0],
VIR_EVENT_HANDLE_READABLE,
testPipeReader,
&handles[1], NULL);
if (safewrite(handles[1].pipeFD[1], &one, 1) != 1)
return EXIT_FAILURE;
if (finishJob("Write duplicate", 1, -1) != EXIT_SUCCESS)
return EXIT_FAILURE;
/* pthread_kill(eventThread, SIGTERM); */
return EXIT_SUCCESS;
}
VIR_TEST_MAIN(mymain)