]> xenbits.xensource.com Git - libvirt.git/commitdiff
Add test case for exercising the event loop
authorDaniel P. Berrange <berrange@redhat.com>
Tue, 12 May 2009 16:45:14 +0000 (16:45 +0000)
committerDaniel P. Berrange <berrange@redhat.com>
Tue, 12 May 2009 16:45:14 +0000 (16:45 +0000)
.hgignore
ChangeLog
tests/.cvsignore
tests/.gitignore
tests/Makefile.am
tests/eventtest.c [new file with mode: 0644]
tests/testutils.h

index bfc08495f06c952ac24a56619409955ed1f2f047..5cb3f7a1c7e8f5aa3127881d6afb5f724355db6b 100644 (file)
--- a/.hgignore
+++ b/.hgignore
@@ -261,6 +261,7 @@ tests/Makefile.in
 tests/confdata/Makefile
 tests/confdata/Makefile.in
 tests/conftest
+tests/eventtest
 tests/nodedevxml2xmltest
 tests/nodeinfotest
 tests/qemuxml2argvtest
index b87157753fdc16de2d2176458660e7100f0ad338..39626a58a12c6f03e75140313b72c768f27f6153 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+Tue May 12 17:45:22 BST 2009 Daniel P. Berrange <berrange@redhat.com>
+
+       * .hgignore, tests/.cvsignore, tests/.gitignore: Ignore
+       new eventtest binary.
+       * tests/Makefile.am, tests/eventtest.c, tests/testutils.h: Add
+       test case for exercising the event loop
+
 Tue May 12 17:43:22 BST 2009 Daniel P. Berrange <berrange@redhat.com>
 
        Fix watch/timer event deletion
index 4f33d0bd81afc7a8e1b56268adb9194dafbf1f86..6a3c555e806ba07b927e7406ff007821589e02e4 100644 (file)
@@ -16,6 +16,7 @@ nodeinfotest
 statstest
 qparamtest
 seclabeltest
+eventtest
 *.gcda
 *.gcno
 *.exe
index 4f33d0bd81afc7a8e1b56268adb9194dafbf1f86..6a3c555e806ba07b927e7406ff007821589e02e4 100644 (file)
@@ -16,6 +16,7 @@ nodeinfotest
 statstest
 qparamtest
 seclabeltest
+eventtest
 *.gcda
 *.gcno
 *.exe
index 52d5c398e442adb20068028092678549c8c43a3e..3cb7056199427ca70c7919230a4044b728a8c2bc 100644 (file)
@@ -122,6 +122,11 @@ if WITH_SECDRIVER_SELINUX
 TESTS += seclabeltest
 endif
 
+if WITH_LIBVIRTD
+noinst_PROGRAMS += eventtest
+TESTS += eventtest
+endif
+
 TESTS += nodedevxml2xmltest
 
 path_add = $$abs_top_builddir/src$(PATH_SEPARATOR)$$abs_top_builddir/qemud
@@ -223,4 +228,10 @@ qparamtest_SOURCES = \
        qparamtest.c testutils.h testutils.c
 qparamtest_LDADD = $(LDADDS)
 
+if WITH_LIBVIRTD
+eventtest_SOURCES = \
+       eventtest.c testutils.h testutils.c ../qemud/event.c
+eventtest_LDADD = -lrt $(LDADDS)
+endif
+
 CLEANFILES = *.cov *.gcov .libs/*.gcda .libs/*.gcno *.gcno *.gcda
diff --git a/tests/eventtest.c b/tests/eventtest.c
new file mode 100644 (file)
index 0000000..4a6366e
--- /dev/null
@@ -0,0 +1,445 @@
+/*
+ * eventtest.c: Test the libvirtd event loop impl
+ *
+ * Copyright (C) 2009 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ *
+ * Author: Daniel P. Berrange <berrange@redhat.com>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <signal.h>
+#include <time.h>
+
+#include "testutils.h"
+#include "internal.h"
+#include "threads.h"
+#include "logging.h"
+#include "../qemud/event.h"
+
+#define NUM_FDS 5
+#define NUM_TIME 5
+
+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,
+};
+
+static void
+testPipeReader(int watch, int fd, int events, void *data)
+{
+    struct handleInfo *info = data;
+    char one;
+
+    info->fired = 1;
+
+    if (watch != info->watch) {
+        info->error = EV_ERROR_WATCH;
+        return;
+    }
+
+    if (fd != info->pipeFD[0]) {
+        info->error = EV_ERROR_FD;
+        return;
+    }
+
+    if (!(events & VIR_EVENT_HANDLE_READABLE)) {
+        info->error = EV_ERROR_EVENT;
+        return;
+    }
+    if (read(fd, &one, 1) != 1) {
+        info->error = EV_ERROR_DATA;
+        return;
+    }
+    info->error = EV_ERROR_NONE;
+
+    if (info->delete != -1)
+        virEventRemoveHandleImpl(info->delete);
+}
+
+
+static void
+testTimer(int timer, void *data)
+{
+    struct timerInfo *info = data;
+
+    info->fired = 1;
+
+    if (timer != info->timer) {
+        info->error = EV_ERROR_WATCH;
+        return;
+    }
+
+    info->error = EV_ERROR_NONE;
+
+    if (info->delete != -1)
+        virEventRemoveTimeoutImpl(info->delete);
+}
+
+static pthread_mutex_t eventThreadMutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t eventThreadRunCond = PTHREAD_COND_INITIALIZER;
+static int eventThreadRunOnce = 0;
+static pthread_cond_t eventThreadJobCond = PTHREAD_COND_INITIALIZER;
+static int eventThreadJobDone = 0;
+
+
+static void *eventThreadLoop(void *data ATTRIBUTE_UNUSED) {
+    while (1) {
+        pthread_mutex_lock(&eventThreadMutex);
+        while (!eventThreadRunOnce) {
+            pthread_cond_wait(&eventThreadRunCond, &eventThreadMutex);
+        }
+        eventThreadRunOnce = 0;
+        pthread_mutex_unlock(&eventThreadMutex);
+
+        virEventRunOnce();
+
+        pthread_mutex_lock(&eventThreadMutex);
+        eventThreadJobDone = 1;
+        pthread_cond_signal(&eventThreadJobCond);
+        pthread_mutex_unlock(&eventThreadMutex);
+    }
+    return NULL;
+}
+
+
+static int
+verifyFired(int handle, int timer)
+{
+    int handleFired = 0;
+    int timerFired = 0;
+    int i;
+    for (i = 0 ; i < NUM_FDS ; i++) {
+        if (handles[i].fired) {
+            if (i != handle) {
+                fprintf(stderr, "FAIL Handle %d fired, but expected %d\n", i, handle);
+                return EXIT_FAILURE;
+            } else {
+                if (handles[i].error != EV_ERROR_NONE) {
+                    fprintf(stderr, "FAIL Handle %d fired, but had error %d\n", i,
+                            handles[i].error);
+                    return EXIT_FAILURE;
+                }
+                handleFired = 1;
+            }
+        } else {
+            if (i == handle) {
+                fprintf(stderr, "FAIL Handle %d should have fired, but didn't\n", handle);
+                return EXIT_FAILURE;
+            }
+        }
+    }
+    if (handleFired != 1 && handle != -1) {
+        fprintf(stderr, "FAIL Something wierd happened, expecting handle %d\n", handle);
+        return EXIT_FAILURE;
+    }
+
+
+    for (i = 0 ; i < NUM_TIME ; i++) {
+        if (timers[i].fired) {
+            if (i != timer) {
+                fprintf(stderr, "FAIL Timer %d fired, but expected %d\n", i, timer);
+                return EXIT_FAILURE;
+            } else {
+                if (timers[i].error != EV_ERROR_NONE) {
+                    fprintf(stderr, "FAIL Timer %d fired, but had error %d\n", i,
+                            timers[i].error);
+                    return EXIT_FAILURE;
+                }
+                timerFired = 1;
+            }
+        } else {
+            if (i == timer) {
+                fprintf(stderr, "FAIL Timer %d should have fired, but didn't\n", timer);
+                return EXIT_FAILURE;
+            }
+        }
+    }
+    if (timerFired != 1 && timer != -1) {
+        fprintf(stderr, "FAIL Something wierd happened, expecting timer %d\n", timer);
+        return EXIT_FAILURE;
+    }
+    return EXIT_SUCCESS;
+}
+
+static void
+startJob(const char *msg, int *test)
+{
+    fprintf(stderr, "%2d: %s ", (*test)++, msg);
+    eventThreadRunOnce = 1;
+    eventThreadJobDone = 0;
+    pthread_cond_signal(&eventThreadRunCond);
+    pthread_mutex_unlock(&eventThreadMutex);
+    sched_yield();
+    pthread_mutex_lock(&eventThreadMutex);
+}
+
+static int
+finishJob(int handle, int timer)
+{
+    struct timespec waitTime;
+    int rc;
+    clock_gettime(CLOCK_REALTIME, &waitTime);
+    waitTime.tv_sec += 5;
+    rc = 0;
+    while (!eventThreadJobDone && rc == 0)
+        rc = pthread_cond_timedwait(&eventThreadJobCond, &eventThreadMutex, &waitTime);
+    if (rc != 0) {
+        fprintf(stderr, "FAIL Timed out waiting for pipe event\n");
+        return EXIT_FAILURE;
+    }
+
+    if (verifyFired(handle, timer) != EXIT_SUCCESS)
+        return EXIT_FAILURE;
+
+    fprintf(stderr, "OK\n");
+    return EXIT_SUCCESS;
+}
+
+static void
+resetAll(void)
+{
+    int i;
+    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;
+    }
+}
+
+static int
+mymain(int argc, char **argv)
+{
+    int ret = 0;
+    char *progname;
+    int i;
+    pthread_t eventThread;
+    char one = '1';
+    int test = 1;
+
+    progname = argv[0];
+
+    if (argc > 1) {
+        fprintf(stderr, "Usage: %s\n", progname);
+        return EXIT_FAILURE;
+    }
+
+    for (i = 0 ; i < NUM_FDS ; i++) {
+        if (pipe(handles[i].pipeFD) < 0) {
+            fprintf(stderr, "Cannot create pipe: %d", errno);
+            return EXIT_FAILURE;
+        }
+    }
+
+    if (virThreadInitialize() < 0)
+        return EXIT_FAILURE;
+    char *debugEnv = getenv("LIBVIRT_DEBUG");
+    if (debugEnv && *debugEnv && *debugEnv != '0') {
+        if (STREQ(debugEnv, "2") || STREQ(debugEnv, "info"))
+            virLogSetDefaultPriority(VIR_LOG_INFO);
+        else if (STREQ(debugEnv, "3") || STREQ(debugEnv, "warning"))
+            virLogSetDefaultPriority(VIR_LOG_WARN);
+        else if (STREQ(debugEnv, "4") || STREQ(debugEnv, "error"))
+            virLogSetDefaultPriority(VIR_LOG_ERROR);
+        else
+            virLogSetDefaultPriority(VIR_LOG_DEBUG);
+    }
+
+    virEventInit();
+
+    for (i = 0 ; i < NUM_FDS ; i++) {
+        handles[i].delete = -1;
+        handles[i].watch =
+            virEventAddHandleImpl(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 =
+            virEventAddTimeoutImpl(timers[i].timeout,
+                                   testTimer,
+                                   &timers[i], NULL);
+    }
+
+    pthread_create(&eventThread, NULL, eventThreadLoop, NULL);
+
+    pthread_mutex_lock(&eventThreadMutex);
+
+    /* First time, is easy - just try triggering one of our
+     * registered handles */
+    startJob("Simple write", &test);
+    ret = write(handles[1].pipeFD[1], &one, 1);
+    if (finishJob(1, -1) != EXIT_SUCCESS)
+        return EXIT_FAILURE;
+
+    resetAll();
+
+    /* Now lets delete one before starting poll(), and
+     * try triggering another handle */
+    virEventRemoveHandleImpl(handles[0].watch);
+    startJob("Deleted before poll", &test);
+    ret = write(handles[1].pipeFD[1], &one, 1);
+    if (finishJob(1, -1) != EXIT_SUCCESS)
+        return EXIT_FAILURE;
+
+    resetAll();
+
+    /* Next lets delete *during* poll, which should interrupt
+     * the loop with no event showing */
+
+    /* NB: this case is subject to a bit of a race condition.
+     * We yield & sleep, and pray that the other thread gets
+     * scheduled before we run EventRemoveHandleImpl */
+    startJob("Interrupted during poll", &test);
+    pthread_mutex_unlock(&eventThreadMutex);
+    sched_yield();
+    usleep(100 * 1000);
+    pthread_mutex_lock(&eventThreadMutex);
+    virEventRemoveHandleImpl(handles[1].watch);
+    if (finishJob(-1, -1) != EXIT_SUCCESS)
+        return EXIT_FAILURE;
+
+    resetAll();
+
+    /* Getting more fun, lets delete a later handle during dispatch */
+
+    /* NB: this case is subject to a bit of a race condition.
+     * Only 1 time in 3 does the 2nd write get triggered by
+     * before poll() exits for the first write(). We don't
+     * see a hard failure in other cases, so nothing to worry
+     * about */
+    startJob("Deleted during dispatch", &test);
+    handles[2].delete = handles[3].watch;
+    ret = write(handles[2].pipeFD[1], &one, 1);
+    ret = write(handles[3].pipeFD[1], &one, 1);
+    if (finishJob(2, -1) != EXIT_SUCCESS)
+        return EXIT_FAILURE;
+
+    resetAll();
+
+    /* Extreme fun, lets delete ourselves during dispatch */
+    startJob("Deleted during dispatch", &test);
+    handles[2].delete = handles[2].watch;
+    ret = write(handles[2].pipeFD[1], &one, 1);
+    if (finishJob(2, -1) != EXIT_SUCCESS)
+        return EXIT_FAILURE;
+
+    resetAll();
+
+
+
+    /* Run a timer on its own */
+    virEventUpdateTimeoutImpl(timers[1].timer, 100);
+    startJob("Firing a timer", &test);
+    if (finishJob(-1, 1) != EXIT_SUCCESS)
+        return EXIT_FAILURE;
+    virEventUpdateTimeoutImpl(timers[1].timer, -1);
+
+    resetAll();
+
+    /* Now lets delete one before starting poll(), and
+     * try triggering another timer */
+    virEventUpdateTimeoutImpl(timers[1].timer, 100);
+    virEventRemoveTimeoutImpl(timers[0].timer);
+    startJob("Deleted before poll", &test);
+    if (finishJob(-1, 1) != EXIT_SUCCESS)
+        return EXIT_FAILURE;
+    virEventUpdateTimeoutImpl(timers[1].timer, -1);
+
+    resetAll();
+
+    /* Next lets delete *during* poll, which should interrupt
+     * the loop with no event showing */
+
+    /* NB: this case is subject to a bit of a race condition.
+     * We yield & sleep, and pray that the other thread gets
+     * scheduled before we run EventRemoveTimeoutImpl */
+    startJob("Interrupted during poll", &test);
+    pthread_mutex_unlock(&eventThreadMutex);
+    sched_yield();
+    usleep(100 * 1000);
+    pthread_mutex_lock(&eventThreadMutex);
+    virEventRemoveTimeoutImpl(timers[1].timer);
+    if (finishJob(-1, -1) != EXIT_SUCCESS)
+        return EXIT_FAILURE;
+
+    resetAll();
+
+    /* Getting more fun, lets delete a later timer during dispatch */
+
+    /* NB: this case is subject to a bit of a race condition.
+     * Only 1 time in 3 does the 2nd write get triggered by
+     * before poll() exits for the first write(). We don't
+     * see a hard failure in other cases, so nothing to worry
+     * about */
+    virEventUpdateTimeoutImpl(timers[2].timer, 100);
+    virEventUpdateTimeoutImpl(timers[3].timer, 100);
+    startJob("Deleted during dispatch", &test);
+    timers[2].delete = timers[3].timer;
+    if (finishJob(-1, 2) != EXIT_SUCCESS)
+        return EXIT_FAILURE;
+    virEventUpdateTimeoutImpl(timers[2].timer, -1);
+
+    resetAll();
+
+    /* Extreme fun, lets delete ourselves during dispatch */
+    virEventUpdateTimeoutImpl(timers[2].timer, 100);
+    startJob("Deleted during dispatch", &test);
+    timers[2].delete = timers[2].timer;
+    if (finishJob(-1, 2) != EXIT_SUCCESS)
+        return EXIT_FAILURE;
+
+    for (i = 0 ; i < NUM_FDS ; i++)
+        virEventRemoveHandleImpl(handles[i].watch);
+    for (i = 0 ; i < NUM_TIME ; i++)
+        virEventRemoveTimeoutImpl(timers[i].timer);
+
+
+    //pthread_kill(eventThread, SIGTERM);
+
+    return EXIT_SUCCESS;
+}
+
+
+VIRT_TEST_MAIN(mymain)
index fd326aed7648c2c8a524b45bb0057fa5def17015..1f79c81da47cacea0ebf11f7609ddbdfaa46bd57 100644 (file)
@@ -11,6 +11,8 @@
 #ifndef __VIT_TEST_UTILS_H__
 #define __VIT_TEST_UTILS_H__
 
+#include <stdio.h>
+
 double virtTestCountAverage(double *items,
                             int nitems);