summaryrefslogtreecommitdiffstats
path: root/tests/unit/test-util-filemonitor.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unit/test-util-filemonitor.c')
-rw-r--r--tests/unit/test-util-filemonitor.c720
1 files changed, 720 insertions, 0 deletions
diff --git a/tests/unit/test-util-filemonitor.c b/tests/unit/test-util-filemonitor.c
new file mode 100644
index 0000000000..b629e10857
--- /dev/null
+++ b/tests/unit/test-util-filemonitor.c
@@ -0,0 +1,720 @@
+/*
+ * Tests for util/filemonitor-*.c
+ *
+ * Copyright 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/main-loop.h"
+#include "qapi/error.h"
+#include "qemu/filemonitor.h"
+
+#include <glib/gstdio.h>
+
+#include <utime.h>
+
+enum {
+ QFILE_MONITOR_TEST_OP_ADD_WATCH,
+ QFILE_MONITOR_TEST_OP_DEL_WATCH,
+ QFILE_MONITOR_TEST_OP_EVENT,
+ QFILE_MONITOR_TEST_OP_CREATE,
+ QFILE_MONITOR_TEST_OP_APPEND,
+ QFILE_MONITOR_TEST_OP_TRUNC,
+ QFILE_MONITOR_TEST_OP_RENAME,
+ QFILE_MONITOR_TEST_OP_TOUCH,
+ QFILE_MONITOR_TEST_OP_UNLINK,
+ QFILE_MONITOR_TEST_OP_MKDIR,
+ QFILE_MONITOR_TEST_OP_RMDIR,
+};
+
+typedef struct {
+ int type;
+ const char *filesrc;
+ const char *filedst;
+ int64_t *watchid;
+ int eventid;
+ /*
+ * Only valid with OP_EVENT - this event might be
+ * swapped with the next OP_EVENT
+ */
+ bool swapnext;
+} QFileMonitorTestOp;
+
+typedef struct {
+ int64_t id;
+ QFileMonitorEvent event;
+ char *filename;
+} QFileMonitorTestRecord;
+
+
+typedef struct {
+ QemuMutex lock;
+ GList *records;
+} QFileMonitorTestData;
+
+static QemuMutex evlock;
+static bool evstopping;
+static bool evrunning;
+static bool debug;
+
+/*
+ * Main function for a background thread that is
+ * running the event loop during the test
+ */
+static void *
+qemu_file_monitor_test_event_loop(void *opaque G_GNUC_UNUSED)
+{
+ qemu_mutex_lock(&evlock);
+
+ while (!evstopping) {
+ qemu_mutex_unlock(&evlock);
+ main_loop_wait(true);
+ qemu_mutex_lock(&evlock);
+ }
+
+ evrunning = false;
+ qemu_mutex_unlock(&evlock);
+ return NULL;
+}
+
+
+/*
+ * File monitor event handler which simply maintains
+ * an ordered list of all events that it receives
+ */
+static void
+qemu_file_monitor_test_handler(int64_t id,
+ QFileMonitorEvent event,
+ const char *filename,
+ void *opaque)
+{
+ QFileMonitorTestData *data = opaque;
+ QFileMonitorTestRecord *rec = g_new0(QFileMonitorTestRecord, 1);
+
+ if (debug) {
+ g_printerr("Queue event id %" PRIx64 " event %d file %s\n",
+ id, event, filename);
+ }
+ rec->id = id;
+ rec->event = event;
+ rec->filename = g_strdup(filename);
+
+ qemu_mutex_lock(&data->lock);
+ data->records = g_list_append(data->records, rec);
+ qemu_mutex_unlock(&data->lock);
+}
+
+
+static void
+qemu_file_monitor_test_record_free(QFileMonitorTestRecord *rec)
+{
+ g_free(rec->filename);
+ g_free(rec);
+}
+
+
+/*
+ * Get the next event record that has been received by
+ * the file monitor event handler. Since events are
+ * emitted in the background thread running the event
+ * loop, we can't assume there is a record available
+ * immediately. Thus we will sleep for upto 5 seconds
+ * to wait for the event to be queued for us.
+ */
+static QFileMonitorTestRecord *
+qemu_file_monitor_test_next_record(QFileMonitorTestData *data,
+ QFileMonitorTestRecord *pushback)
+{
+ GTimer *timer = g_timer_new();
+ QFileMonitorTestRecord *record = NULL;
+ GList *tmp;
+
+ qemu_mutex_lock(&data->lock);
+ while (!data->records && g_timer_elapsed(timer, NULL) < 5) {
+ qemu_mutex_unlock(&data->lock);
+ usleep(10 * 1000);
+ qemu_mutex_lock(&data->lock);
+ }
+ if (data->records) {
+ record = data->records->data;
+ if (pushback) {
+ data->records->data = pushback;
+ } else {
+ tmp = data->records;
+ data->records = g_list_remove_link(data->records, tmp);
+ g_list_free(tmp);
+ }
+ } else if (pushback) {
+ qemu_file_monitor_test_record_free(pushback);
+ }
+ qemu_mutex_unlock(&data->lock);
+
+ g_timer_destroy(timer);
+ return record;
+}
+
+
+/*
+ * Check whether the event record we retrieved matches
+ * data we were expecting to see for the event
+ */
+static bool
+qemu_file_monitor_test_expect(QFileMonitorTestData *data,
+ int64_t id,
+ QFileMonitorEvent event,
+ const char *filename,
+ bool swapnext)
+{
+ QFileMonitorTestRecord *rec;
+ bool ret = false;
+
+ rec = qemu_file_monitor_test_next_record(data, NULL);
+
+ retry:
+ if (!rec) {
+ g_printerr("Missing event watch id %" PRIx64 " event %d file %s\n",
+ id, event, filename);
+ return false;
+ }
+
+ if (id != rec->id) {
+ if (swapnext) {
+ rec = qemu_file_monitor_test_next_record(data, rec);
+ swapnext = false;
+ goto retry;
+ }
+ g_printerr("Expected watch id %" PRIx64 " but got %" PRIx64 "\n",
+ id, rec->id);
+ goto cleanup;
+ }
+
+ if (event != rec->event) {
+ g_printerr("Expected event %d but got %d\n", event, rec->event);
+ goto cleanup;
+ }
+
+ if (!g_str_equal(filename, rec->filename)) {
+ g_printerr("Expected filename %s but got %s\n",
+ filename, rec->filename);
+ goto cleanup;
+ }
+
+ ret = true;
+
+ cleanup:
+ qemu_file_monitor_test_record_free(rec);
+ return ret;
+}
+
+
+static void
+test_file_monitor_events(void)
+{
+ int64_t watch0 = 0;
+ int64_t watch1 = 0;
+ int64_t watch2 = 0;
+ int64_t watch3 = 0;
+ int64_t watch4 = 0;
+ int64_t watch5 = 0;
+ QFileMonitorTestOp ops[] = {
+ { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
+ .filesrc = NULL, .watchid = &watch0 },
+ { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
+ .filesrc = "one.txt", .watchid = &watch1 },
+ { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
+ .filesrc = "two.txt", .watchid = &watch2 },
+
+
+ { .type = QFILE_MONITOR_TEST_OP_CREATE,
+ .filesrc = "one.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "one.txt", .watchid = &watch0,
+ .eventid = QFILE_MONITOR_EVENT_CREATED },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "one.txt", .watchid = &watch1,
+ .eventid = QFILE_MONITOR_EVENT_CREATED },
+
+
+ { .type = QFILE_MONITOR_TEST_OP_CREATE,
+ .filesrc = "two.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "two.txt", .watchid = &watch0,
+ .eventid = QFILE_MONITOR_EVENT_CREATED },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "two.txt", .watchid = &watch2,
+ .eventid = QFILE_MONITOR_EVENT_CREATED },
+
+
+ { .type = QFILE_MONITOR_TEST_OP_CREATE,
+ .filesrc = "three.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "three.txt", .watchid = &watch0,
+ .eventid = QFILE_MONITOR_EVENT_CREATED },
+
+
+ { .type = QFILE_MONITOR_TEST_OP_UNLINK,
+ .filesrc = "three.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "three.txt", .watchid = &watch0,
+ .eventid = QFILE_MONITOR_EVENT_DELETED },
+
+
+ { .type = QFILE_MONITOR_TEST_OP_RENAME,
+ .filesrc = "one.txt", .filedst = "two.txt" },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "one.txt", .watchid = &watch0,
+ .eventid = QFILE_MONITOR_EVENT_DELETED },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "one.txt", .watchid = &watch1,
+ .eventid = QFILE_MONITOR_EVENT_DELETED },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "two.txt", .watchid = &watch0,
+ .eventid = QFILE_MONITOR_EVENT_CREATED },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "two.txt", .watchid = &watch2,
+ .eventid = QFILE_MONITOR_EVENT_CREATED },
+
+
+ { .type = QFILE_MONITOR_TEST_OP_APPEND,
+ .filesrc = "two.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "two.txt", .watchid = &watch0,
+ .eventid = QFILE_MONITOR_EVENT_MODIFIED },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "two.txt", .watchid = &watch2,
+ .eventid = QFILE_MONITOR_EVENT_MODIFIED },
+
+
+ { .type = QFILE_MONITOR_TEST_OP_TOUCH,
+ .filesrc = "two.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "two.txt", .watchid = &watch0,
+ .eventid = QFILE_MONITOR_EVENT_ATTRIBUTES },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "two.txt", .watchid = &watch2,
+ .eventid = QFILE_MONITOR_EVENT_ATTRIBUTES },
+
+
+ { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
+ .filesrc = "one.txt", .watchid = &watch1 },
+ { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
+ .filesrc = "one.txt", .watchid = &watch3 },
+ { .type = QFILE_MONITOR_TEST_OP_CREATE,
+ .filesrc = "one.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "one.txt", .watchid = &watch0,
+ .eventid = QFILE_MONITOR_EVENT_CREATED },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "one.txt", .watchid = &watch3,
+ .eventid = QFILE_MONITOR_EVENT_CREATED },
+
+
+ { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
+ .filesrc = "one.txt", .watchid = &watch3 },
+ { .type = QFILE_MONITOR_TEST_OP_UNLINK,
+ .filesrc = "one.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "one.txt", .watchid = &watch0,
+ .eventid = QFILE_MONITOR_EVENT_DELETED },
+
+
+ { .type = QFILE_MONITOR_TEST_OP_MKDIR,
+ .filesrc = "fish", },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "fish", .watchid = &watch0,
+ .eventid = QFILE_MONITOR_EVENT_CREATED },
+
+
+ { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
+ .filesrc = "fish/", .watchid = &watch4 },
+ { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
+ .filesrc = "fish/one.txt", .watchid = &watch5 },
+ { .type = QFILE_MONITOR_TEST_OP_CREATE,
+ .filesrc = "fish/one.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "one.txt", .watchid = &watch4,
+ .eventid = QFILE_MONITOR_EVENT_CREATED },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "one.txt", .watchid = &watch5,
+ .eventid = QFILE_MONITOR_EVENT_CREATED },
+
+
+ { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
+ .filesrc = "fish/one.txt", .watchid = &watch5 },
+ { .type = QFILE_MONITOR_TEST_OP_RENAME,
+ .filesrc = "fish/one.txt", .filedst = "two.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "one.txt", .watchid = &watch4,
+ .eventid = QFILE_MONITOR_EVENT_DELETED },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "two.txt", .watchid = &watch0,
+ .eventid = QFILE_MONITOR_EVENT_CREATED },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "two.txt", .watchid = &watch2,
+ .eventid = QFILE_MONITOR_EVENT_CREATED },
+
+
+ { .type = QFILE_MONITOR_TEST_OP_RMDIR,
+ .filesrc = "fish", },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "", .watchid = &watch4,
+ .eventid = QFILE_MONITOR_EVENT_IGNORED,
+ .swapnext = true },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "fish", .watchid = &watch0,
+ .eventid = QFILE_MONITOR_EVENT_DELETED },
+ { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
+ .filesrc = "fish", .watchid = &watch4 },
+
+
+ { .type = QFILE_MONITOR_TEST_OP_UNLINK,
+ .filesrc = "two.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "two.txt", .watchid = &watch0,
+ .eventid = QFILE_MONITOR_EVENT_DELETED },
+ { .type = QFILE_MONITOR_TEST_OP_EVENT,
+ .filesrc = "two.txt", .watchid = &watch2,
+ .eventid = QFILE_MONITOR_EVENT_DELETED },
+
+
+ { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
+ .filesrc = "two.txt", .watchid = &watch2 },
+ { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
+ .filesrc = NULL, .watchid = &watch0 },
+ };
+ Error *local_err = NULL;
+ GError *gerr = NULL;
+ QFileMonitor *mon = qemu_file_monitor_new(&local_err);
+ QemuThread th;
+ GTimer *timer;
+ gchar *dir = NULL;
+ int err = -1;
+ gsize i;
+ char *pathsrc = NULL;
+ char *pathdst = NULL;
+ QFileMonitorTestData data;
+ GHashTable *ids = g_hash_table_new(g_int64_hash, g_int64_equal);
+ char *travis_arch;
+
+ qemu_mutex_init(&data.lock);
+ data.records = NULL;
+
+ /*
+ * This test does not work on Travis LXD containers since some
+ * syscalls are blocked in that environment.
+ */
+ travis_arch = getenv("TRAVIS_ARCH");
+ if (travis_arch && !g_str_equal(travis_arch, "x86_64")) {
+ g_test_skip("Test does not work on non-x86 Travis containers.");
+ return;
+ }
+
+ /*
+ * The file monitor needs the main loop running in
+ * order to receive events from inotify. We must
+ * thus spawn a background thread to run an event
+ * loop impl, while this thread triggers the
+ * actual file operations we're testing
+ */
+ evrunning = 1;
+ evstopping = 0;
+ qemu_thread_create(&th, "event-loop",
+ qemu_file_monitor_test_event_loop, NULL,
+ QEMU_THREAD_JOINABLE);
+
+ if (local_err) {
+ g_printerr("File monitoring not available: %s",
+ error_get_pretty(local_err));
+ error_free(local_err);
+ return;
+ }
+
+ dir = g_dir_make_tmp("test-util-filemonitor-XXXXXX",
+ &gerr);
+ if (!dir) {
+ g_printerr("Unable to create tmp dir %s",
+ gerr->message);
+ g_error_free(gerr);
+ abort();
+ }
+
+ /*
+ * Run through the operation sequence validating events
+ * as we go
+ */
+ for (i = 0; i < G_N_ELEMENTS(ops); i++) {
+ const QFileMonitorTestOp *op = &(ops[i]);
+ int fd;
+ struct utimbuf ubuf;
+ char *watchdir;
+ const char *watchfile;
+
+ pathsrc = g_strdup_printf("%s/%s", dir, op->filesrc);
+ if (op->filedst) {
+ pathdst = g_strdup_printf("%s/%s", dir, op->filedst);
+ }
+
+ switch (op->type) {
+ case QFILE_MONITOR_TEST_OP_ADD_WATCH:
+ if (debug) {
+ g_printerr("Add watch %s %s\n",
+ dir, op->filesrc);
+ }
+ if (op->filesrc && strchr(op->filesrc, '/')) {
+ watchdir = g_strdup_printf("%s/%s", dir, op->filesrc);
+ watchfile = strrchr(watchdir, '/');
+ *(char *)watchfile = '\0';
+ watchfile++;
+ if (*watchfile == '\0') {
+ watchfile = NULL;
+ }
+ } else {
+ watchdir = g_strdup(dir);
+ watchfile = op->filesrc;
+ }
+ *op->watchid =
+ qemu_file_monitor_add_watch(mon,
+ watchdir,
+ watchfile,
+ qemu_file_monitor_test_handler,
+ &data,
+ &local_err);
+ g_free(watchdir);
+ if (*op->watchid < 0) {
+ g_printerr("Unable to add watch %s",
+ error_get_pretty(local_err));
+ error_free(local_err);
+ goto cleanup;
+ }
+ if (debug) {
+ g_printerr("Watch ID %" PRIx64 "\n", *op->watchid);
+ }
+ if (g_hash_table_contains(ids, op->watchid)) {
+ g_printerr("Watch ID %" PRIx64 "already exists", *op->watchid);
+ goto cleanup;
+ }
+ g_hash_table_add(ids, op->watchid);
+ break;
+ case QFILE_MONITOR_TEST_OP_DEL_WATCH:
+ if (debug) {
+ g_printerr("Del watch %s %" PRIx64 "\n", dir, *op->watchid);
+ }
+ if (op->filesrc && strchr(op->filesrc, '/')) {
+ watchdir = g_strdup_printf("%s/%s", dir, op->filesrc);
+ watchfile = strrchr(watchdir, '/');
+ *(char *)watchfile = '\0';
+ } else {
+ watchdir = g_strdup(dir);
+ }
+ g_hash_table_remove(ids, op->watchid);
+ qemu_file_monitor_remove_watch(mon,
+ watchdir,
+ *op->watchid);
+ g_free(watchdir);
+ break;
+ case QFILE_MONITOR_TEST_OP_EVENT:
+ if (debug) {
+ g_printerr("Event id=%" PRIx64 " event=%d file=%s\n",
+ *op->watchid, op->eventid, op->filesrc);
+ }
+ if (!qemu_file_monitor_test_expect(&data, *op->watchid,
+ op->eventid, op->filesrc,
+ op->swapnext))
+ goto cleanup;
+ break;
+ case QFILE_MONITOR_TEST_OP_CREATE:
+ if (debug) {
+ g_printerr("Create %s\n", pathsrc);
+ }
+ fd = open(pathsrc, O_WRONLY | O_CREAT, 0700);
+ if (fd < 0) {
+ g_printerr("Unable to create %s: %s",
+ pathsrc, strerror(errno));
+ goto cleanup;
+ }
+ close(fd);
+ break;
+
+ case QFILE_MONITOR_TEST_OP_APPEND:
+ if (debug) {
+ g_printerr("Append %s\n", pathsrc);
+ }
+ fd = open(pathsrc, O_WRONLY | O_APPEND, 0700);
+ if (fd < 0) {
+ g_printerr("Unable to open %s: %s",
+ pathsrc, strerror(errno));
+ goto cleanup;
+ }
+
+ if (write(fd, "Hello World", 10) != 10) {
+ g_printerr("Unable to write %s: %s",
+ pathsrc, strerror(errno));
+ close(fd);
+ goto cleanup;
+ }
+ close(fd);
+ break;
+
+ case QFILE_MONITOR_TEST_OP_TRUNC:
+ if (debug) {
+ g_printerr("Truncate %s\n", pathsrc);
+ }
+ if (truncate(pathsrc, 4) < 0) {
+ g_printerr("Unable to truncate %s: %s",
+ pathsrc, strerror(errno));
+ goto cleanup;
+ }
+ break;
+
+ case QFILE_MONITOR_TEST_OP_RENAME:
+ if (debug) {
+ g_printerr("Rename %s -> %s\n", pathsrc, pathdst);
+ }
+ if (rename(pathsrc, pathdst) < 0) {
+ g_printerr("Unable to rename %s to %s: %s",
+ pathsrc, pathdst, strerror(errno));
+ goto cleanup;
+ }
+ break;
+
+ case QFILE_MONITOR_TEST_OP_UNLINK:
+ if (debug) {
+ g_printerr("Unlink %s\n", pathsrc);
+ }
+ if (unlink(pathsrc) < 0) {
+ g_printerr("Unable to unlink %s: %s",
+ pathsrc, strerror(errno));
+ goto cleanup;
+ }
+ break;
+
+ case QFILE_MONITOR_TEST_OP_TOUCH:
+ if (debug) {
+ g_printerr("Touch %s\n", pathsrc);
+ }
+ ubuf.actime = 1024;
+ ubuf.modtime = 1025;
+ if (utime(pathsrc, &ubuf) < 0) {
+ g_printerr("Unable to touch %s: %s",
+ pathsrc, strerror(errno));
+ goto cleanup;
+ }
+ break;
+
+ case QFILE_MONITOR_TEST_OP_MKDIR:
+ if (debug) {
+ g_printerr("Mkdir %s\n", pathsrc);
+ }
+ if (g_mkdir_with_parents(pathsrc, 0700) < 0) {
+ g_printerr("Unable to mkdir %s: %s",
+ pathsrc, strerror(errno));
+ goto cleanup;
+ }
+ break;
+
+ case QFILE_MONITOR_TEST_OP_RMDIR:
+ if (debug) {
+ g_printerr("Rmdir %s\n", pathsrc);
+ }
+ if (rmdir(pathsrc) < 0) {
+ g_printerr("Unable to rmdir %s: %s",
+ pathsrc, strerror(errno));
+ goto cleanup;
+ }
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+
+ g_free(pathsrc);
+ g_free(pathdst);
+ pathsrc = pathdst = NULL;
+ }
+
+ g_assert_cmpint(g_hash_table_size(ids), ==, 0);
+
+ err = 0;
+
+ cleanup:
+ g_free(pathsrc);
+ g_free(pathdst);
+
+ qemu_mutex_lock(&evlock);
+ evstopping = 1;
+ timer = g_timer_new();
+ while (evrunning && g_timer_elapsed(timer, NULL) < 5) {
+ qemu_mutex_unlock(&evlock);
+ usleep(10 * 1000);
+ qemu_mutex_lock(&evlock);
+ }
+ qemu_mutex_unlock(&evlock);
+
+ if (g_timer_elapsed(timer, NULL) >= 5) {
+ g_printerr("Event loop failed to quit after 5 seconds\n");
+ }
+ g_timer_destroy(timer);
+
+ qemu_file_monitor_free(mon);
+ g_list_foreach(data.records,
+ (GFunc)qemu_file_monitor_test_record_free, NULL);
+ g_list_free(data.records);
+ qemu_mutex_destroy(&data.lock);
+ if (dir) {
+ for (i = 0; i < G_N_ELEMENTS(ops); i++) {
+ const QFileMonitorTestOp *op = &(ops[i]);
+ char *path = g_strdup_printf("%s/%s",
+ dir, op->filesrc);
+ if (op->type == QFILE_MONITOR_TEST_OP_MKDIR) {
+ rmdir(path);
+ g_free(path);
+ } else {
+ unlink(path);
+ g_free(path);
+ if (op->filedst) {
+ path = g_strdup_printf("%s/%s",
+ dir, op->filedst);
+ unlink(path);
+ g_free(path);
+ }
+ }
+ }
+ if (rmdir(dir) < 0) {
+ g_printerr("Failed to remove %s: %s\n",
+ dir, strerror(errno));
+ abort();
+ }
+ }
+ g_hash_table_unref(ids);
+ g_free(dir);
+ g_assert(err == 0);
+}
+
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qemu_init_main_loop(&error_abort);
+
+ qemu_mutex_init(&evlock);
+
+ debug = getenv("FILEMONITOR_DEBUG") != NULL;
+ g_test_add_func("/util/filemonitor", test_file_monitor_events);
+
+ return g_test_run();
+}