tests: refactor file monitor test to make it more understandable
[qemu.git] / tests / test-util-filemonitor.c
blobea3715a8f4437d14d3cddc1059147abe65b7315a
1 /*
2 * Tests for util/filemonitor-*.c
4 * Copyright 2018 Red Hat, Inc.
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this library; if not, see <http://www.gnu.org/licenses/>.
21 #include "qemu/osdep.h"
22 #include "qemu/main-loop.h"
23 #include "qapi/error.h"
24 #include "qemu/filemonitor.h"
26 #include <utime.h>
28 enum {
29 QFILE_MONITOR_TEST_OP_ADD_WATCH,
30 QFILE_MONITOR_TEST_OP_DEL_WATCH,
31 QFILE_MONITOR_TEST_OP_EVENT,
32 QFILE_MONITOR_TEST_OP_CREATE,
33 QFILE_MONITOR_TEST_OP_APPEND,
34 QFILE_MONITOR_TEST_OP_TRUNC,
35 QFILE_MONITOR_TEST_OP_RENAME,
36 QFILE_MONITOR_TEST_OP_TOUCH,
37 QFILE_MONITOR_TEST_OP_UNLINK,
40 typedef struct {
41 int type;
42 const char *filesrc;
43 const char *filedst;
44 int watchid;
45 int eventid;
46 } QFileMonitorTestOp;
48 typedef struct {
49 int id;
50 QFileMonitorEvent event;
51 char *filename;
52 } QFileMonitorTestRecord;
55 typedef struct {
56 QemuMutex lock;
57 GList *records;
58 } QFileMonitorTestData;
60 static QemuMutex evlock;
61 static bool evstopping;
62 static bool evrunning;
63 static bool debug;
66 * Main function for a background thread that is
67 * running the event loop during the test
69 static void *
70 qemu_file_monitor_test_event_loop(void *opaque G_GNUC_UNUSED)
72 qemu_mutex_lock(&evlock);
74 while (!evstopping) {
75 qemu_mutex_unlock(&evlock);
76 main_loop_wait(true);
77 qemu_mutex_lock(&evlock);
80 evrunning = false;
81 qemu_mutex_unlock(&evlock);
82 return NULL;
87 * File monitor event handler which simply maintains
88 * an ordered list of all events that it receives
90 static void
91 qemu_file_monitor_test_handler(int id,
92 QFileMonitorEvent event,
93 const char *filename,
94 void *opaque)
96 QFileMonitorTestData *data = opaque;
97 QFileMonitorTestRecord *rec = g_new0(QFileMonitorTestRecord, 1);
99 rec->id = id;
100 rec->event = event;
101 rec->filename = g_strdup(filename);
103 qemu_mutex_lock(&data->lock);
104 data->records = g_list_append(data->records, rec);
105 qemu_mutex_unlock(&data->lock);
109 static void
110 qemu_file_monitor_test_record_free(QFileMonitorTestRecord *rec)
112 g_free(rec->filename);
113 g_free(rec);
118 * Get the next event record that has been received by
119 * the file monitor event handler. Since events are
120 * emitted in the background thread running the event
121 * loop, we can't assume there is a record available
122 * immediately. Thus we will sleep for upto 5 seconds
123 * to wait for the event to be queued for us.
125 static QFileMonitorTestRecord *
126 qemu_file_monitor_test_next_record(QFileMonitorTestData *data)
128 GTimer *timer = g_timer_new();
129 QFileMonitorTestRecord *record = NULL;
130 GList *tmp;
132 qemu_mutex_lock(&data->lock);
133 while (!data->records && g_timer_elapsed(timer, NULL) < 5) {
134 qemu_mutex_unlock(&data->lock);
135 usleep(10 * 1000);
136 qemu_mutex_lock(&data->lock);
138 if (data->records) {
139 record = data->records->data;
140 tmp = data->records;
141 data->records = g_list_remove_link(data->records, tmp);
142 g_list_free(tmp);
144 qemu_mutex_unlock(&data->lock);
146 g_timer_destroy(timer);
147 return record;
152 * Check whether the event record we retrieved matches
153 * data we were expecting to see for the event
155 static bool
156 qemu_file_monitor_test_expect(QFileMonitorTestData *data,
157 int id,
158 QFileMonitorEvent event,
159 const char *filename)
161 QFileMonitorTestRecord *rec;
162 bool ret = false;
164 rec = qemu_file_monitor_test_next_record(data);
166 if (!rec) {
167 g_printerr("Missing event watch id %d event %d file %s\n",
168 id, event, filename);
169 return false;
172 if (id != rec->id) {
173 g_printerr("Expected watch id %d but got %d\n", id, rec->id);
174 goto cleanup;
177 if (event != rec->event) {
178 g_printerr("Expected event %d but got %d\n", event, rec->event);
179 goto cleanup;
182 if (!g_str_equal(filename, rec->filename)) {
183 g_printerr("Expected filename %s but got %s\n",
184 filename, rec->filename);
185 goto cleanup;
188 ret = true;
190 cleanup:
191 qemu_file_monitor_test_record_free(rec);
192 return ret;
196 static void
197 test_file_monitor_events(void)
199 QFileMonitorTestOp ops[] = {
200 { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
201 .filesrc = NULL, .watchid = 0 },
202 { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
203 .filesrc = "one.txt", .watchid = 1 },
204 { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
205 .filesrc = "two.txt", .watchid = 2 },
208 { .type = QFILE_MONITOR_TEST_OP_CREATE,
209 .filesrc = "one.txt", },
210 { .type = QFILE_MONITOR_TEST_OP_EVENT,
211 .filesrc = "one.txt", .watchid = 0,
212 .eventid = QFILE_MONITOR_EVENT_CREATED },
213 { .type = QFILE_MONITOR_TEST_OP_EVENT,
214 .filesrc = "one.txt", .watchid = 1,
215 .eventid = QFILE_MONITOR_EVENT_CREATED },
218 { .type = QFILE_MONITOR_TEST_OP_CREATE,
219 .filesrc = "two.txt", },
220 { .type = QFILE_MONITOR_TEST_OP_EVENT,
221 .filesrc = "two.txt", .watchid = 0,
222 .eventid = QFILE_MONITOR_EVENT_CREATED },
223 { .type = QFILE_MONITOR_TEST_OP_EVENT,
224 .filesrc = "two.txt", .watchid = 2,
225 .eventid = QFILE_MONITOR_EVENT_CREATED },
228 { .type = QFILE_MONITOR_TEST_OP_CREATE,
229 .filesrc = "three.txt", },
230 { .type = QFILE_MONITOR_TEST_OP_EVENT,
231 .filesrc = "three.txt", .watchid = 0,
232 .eventid = QFILE_MONITOR_EVENT_CREATED },
235 { .type = QFILE_MONITOR_TEST_OP_UNLINK,
236 .filesrc = "three.txt", },
237 { .type = QFILE_MONITOR_TEST_OP_EVENT,
238 .filesrc = "three.txt", .watchid = 0,
239 .eventid = QFILE_MONITOR_EVENT_DELETED },
242 { .type = QFILE_MONITOR_TEST_OP_RENAME,
243 .filesrc = "one.txt", .filedst = "two.txt" },
244 { .type = QFILE_MONITOR_TEST_OP_EVENT,
245 .filesrc = "one.txt", .watchid = 0,
246 .eventid = QFILE_MONITOR_EVENT_DELETED },
247 { .type = QFILE_MONITOR_TEST_OP_EVENT,
248 .filesrc = "one.txt", .watchid = 1,
249 .eventid = QFILE_MONITOR_EVENT_DELETED },
250 { .type = QFILE_MONITOR_TEST_OP_EVENT,
251 .filesrc = "two.txt", .watchid = 0,
252 .eventid = QFILE_MONITOR_EVENT_CREATED },
253 { .type = QFILE_MONITOR_TEST_OP_EVENT,
254 .filesrc = "two.txt", .watchid = 2,
255 .eventid = QFILE_MONITOR_EVENT_CREATED },
258 { .type = QFILE_MONITOR_TEST_OP_APPEND,
259 .filesrc = "two.txt", },
260 { .type = QFILE_MONITOR_TEST_OP_EVENT,
261 .filesrc = "two.txt", .watchid = 0,
262 .eventid = QFILE_MONITOR_EVENT_MODIFIED },
263 { .type = QFILE_MONITOR_TEST_OP_EVENT,
264 .filesrc = "two.txt", .watchid = 2,
265 .eventid = QFILE_MONITOR_EVENT_MODIFIED },
268 { .type = QFILE_MONITOR_TEST_OP_TOUCH,
269 .filesrc = "two.txt", },
270 { .type = QFILE_MONITOR_TEST_OP_EVENT,
271 .filesrc = "two.txt", .watchid = 0,
272 .eventid = QFILE_MONITOR_EVENT_ATTRIBUTES },
273 { .type = QFILE_MONITOR_TEST_OP_EVENT,
274 .filesrc = "two.txt", .watchid = 2,
275 .eventid = QFILE_MONITOR_EVENT_ATTRIBUTES },
278 { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
279 .filesrc = "one.txt", .watchid = 1 },
280 { .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
281 .filesrc = "one.txt", .watchid = 3 },
282 { .type = QFILE_MONITOR_TEST_OP_CREATE,
283 .filesrc = "one.txt", },
284 { .type = QFILE_MONITOR_TEST_OP_EVENT,
285 .filesrc = "one.txt", .watchid = 0,
286 .eventid = QFILE_MONITOR_EVENT_CREATED },
287 { .type = QFILE_MONITOR_TEST_OP_EVENT,
288 .filesrc = "one.txt", .watchid = 3,
289 .eventid = QFILE_MONITOR_EVENT_CREATED },
292 { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
293 .filesrc = "one.txt", .watchid = 3 },
294 { .type = QFILE_MONITOR_TEST_OP_UNLINK,
295 .filesrc = "one.txt", },
296 { .type = QFILE_MONITOR_TEST_OP_EVENT,
297 .filesrc = "one.txt", .watchid = 0,
298 .eventid = QFILE_MONITOR_EVENT_DELETED },
301 { .type = QFILE_MONITOR_TEST_OP_UNLINK,
302 .filesrc = "two.txt", },
303 { .type = QFILE_MONITOR_TEST_OP_EVENT,
304 .filesrc = "two.txt", .watchid = 0,
305 .eventid = QFILE_MONITOR_EVENT_DELETED },
306 { .type = QFILE_MONITOR_TEST_OP_EVENT,
307 .filesrc = "two.txt", .watchid = 2,
308 .eventid = QFILE_MONITOR_EVENT_DELETED },
311 { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
312 .filesrc = "two.txt", .watchid = 2 },
313 { .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
314 .filesrc = NULL, .watchid = 0 },
316 Error *local_err = NULL;
317 GError *gerr = NULL;
318 QFileMonitor *mon = qemu_file_monitor_new(&local_err);
319 QemuThread th;
320 GTimer *timer;
321 gchar *dir = NULL;
322 int err = -1;
323 gsize i;
324 char *pathsrc = NULL;
325 char *pathdst = NULL;
326 QFileMonitorTestData data;
328 qemu_mutex_init(&data.lock);
329 data.records = NULL;
332 * The file monitor needs the main loop running in
333 * order to receive events from inotify. We must
334 * thus spawn a background thread to run an event
335 * loop impl, while this thread triggers the
336 * actual file operations we're testing
338 evrunning = 1;
339 evstopping = 0;
340 qemu_thread_create(&th, "event-loop",
341 qemu_file_monitor_test_event_loop, NULL,
342 QEMU_THREAD_JOINABLE);
344 if (local_err) {
345 g_printerr("File monitoring not available: %s",
346 error_get_pretty(local_err));
347 error_free(local_err);
348 return;
351 dir = g_dir_make_tmp("test-util-filemonitor-XXXXXX",
352 &gerr);
353 if (!dir) {
354 g_printerr("Unable to create tmp dir %s",
355 gerr->message);
356 g_error_free(gerr);
357 abort();
361 * Run through the operation sequence validating events
362 * as we go
364 for (i = 0; i < G_N_ELEMENTS(ops); i++) {
365 const QFileMonitorTestOp *op = &(ops[i]);
366 int fd;
367 int watchid;
368 struct utimbuf ubuf;
370 pathsrc = g_strdup_printf("%s/%s", dir, op->filesrc);
371 if (op->filedst) {
372 pathdst = g_strdup_printf("%s/%s", dir, op->filedst);
375 switch (op->type) {
376 case QFILE_MONITOR_TEST_OP_ADD_WATCH:
377 if (debug) {
378 g_printerr("Add watch %s %s %d\n",
379 dir, op->filesrc, op->watchid);
381 watchid =
382 qemu_file_monitor_add_watch(mon,
383 dir,
384 op->filesrc,
385 qemu_file_monitor_test_handler,
386 &data,
387 &local_err);
388 if (watchid < 0) {
389 g_printerr("Unable to add watch %s",
390 error_get_pretty(local_err));
391 goto cleanup;
393 if (watchid != op->watchid) {
394 g_printerr("Unexpected watch ID %d, wanted %d\n",
395 watchid, op->watchid);
396 goto cleanup;
398 break;
399 case QFILE_MONITOR_TEST_OP_DEL_WATCH:
400 if (debug) {
401 g_printerr("Del watch %s %d\n", dir, op->watchid);
403 qemu_file_monitor_remove_watch(mon,
404 dir,
405 op->watchid);
406 break;
407 case QFILE_MONITOR_TEST_OP_EVENT:
408 if (debug) {
409 g_printerr("Event id=%d event=%d file=%s\n",
410 op->watchid, op->eventid, op->filesrc);
412 if (!qemu_file_monitor_test_expect(
413 &data, op->watchid, op->eventid, op->filesrc))
414 goto cleanup;
415 break;
416 case QFILE_MONITOR_TEST_OP_CREATE:
417 if (debug) {
418 g_printerr("Create %s\n", pathsrc);
420 fd = open(pathsrc, O_WRONLY | O_CREAT, 0700);
421 if (fd < 0) {
422 g_printerr("Unable to create %s: %s",
423 pathsrc, strerror(errno));
424 goto cleanup;
426 close(fd);
427 break;
429 case QFILE_MONITOR_TEST_OP_APPEND:
430 if (debug) {
431 g_printerr("Append %s\n", pathsrc);
433 fd = open(pathsrc, O_WRONLY | O_APPEND, 0700);
434 if (fd < 0) {
435 g_printerr("Unable to open %s: %s",
436 pathsrc, strerror(errno));
437 goto cleanup;
440 if (write(fd, "Hello World", 10) != 10) {
441 g_printerr("Unable to write %s: %s",
442 pathsrc, strerror(errno));
443 close(fd);
444 goto cleanup;
446 close(fd);
447 break;
449 case QFILE_MONITOR_TEST_OP_TRUNC:
450 if (debug) {
451 g_printerr("Truncate %s\n", pathsrc);
453 if (truncate(pathsrc, 4) < 0) {
454 g_printerr("Unable to truncate %s: %s",
455 pathsrc, strerror(errno));
456 goto cleanup;
458 break;
460 case QFILE_MONITOR_TEST_OP_RENAME:
461 if (debug) {
462 g_printerr("Rename %s -> %s\n", pathsrc, pathdst);
464 if (rename(pathsrc, pathdst) < 0) {
465 g_printerr("Unable to rename %s to %s: %s",
466 pathsrc, pathdst, strerror(errno));
467 goto cleanup;
469 break;
471 case QFILE_MONITOR_TEST_OP_UNLINK:
472 if (debug) {
473 g_printerr("Unlink %s\n", pathsrc);
475 if (unlink(pathsrc) < 0) {
476 g_printerr("Unable to unlink %s: %s",
477 pathsrc, strerror(errno));
478 goto cleanup;
480 break;
482 case QFILE_MONITOR_TEST_OP_TOUCH:
483 if (debug) {
484 g_printerr("Touch %s\n", pathsrc);
486 ubuf.actime = 1024;
487 ubuf.modtime = 1025;
488 if (utime(pathsrc, &ubuf) < 0) {
489 g_printerr("Unable to touch %s: %s",
490 pathsrc, strerror(errno));
491 goto cleanup;
493 break;
495 default:
496 g_assert_not_reached();
499 g_free(pathsrc);
500 g_free(pathdst);
501 pathsrc = pathdst = NULL;
504 err = 0;
506 cleanup:
507 g_free(pathsrc);
508 g_free(pathdst);
510 qemu_mutex_lock(&evlock);
511 evstopping = 1;
512 timer = g_timer_new();
513 while (evrunning && g_timer_elapsed(timer, NULL) < 5) {
514 qemu_mutex_unlock(&evlock);
515 usleep(10 * 1000);
516 qemu_mutex_lock(&evlock);
518 qemu_mutex_unlock(&evlock);
520 if (g_timer_elapsed(timer, NULL) >= 5) {
521 g_printerr("Event loop failed to quit after 5 seconds\n");
523 g_timer_destroy(timer);
525 qemu_file_monitor_free(mon);
526 g_list_foreach(data.records,
527 (GFunc)qemu_file_monitor_test_record_free, NULL);
528 g_list_free(data.records);
529 qemu_mutex_destroy(&data.lock);
530 if (dir) {
531 for (i = 0; i < G_N_ELEMENTS(ops); i++) {
532 const QFileMonitorTestOp *op = &(ops[i]);
533 char *path = g_strdup_printf("%s/%s",
534 dir, op->filesrc);
535 unlink(path);
536 g_free(path);
537 if (op->filedst) {
538 path = g_strdup_printf("%s/%s",
539 dir, op->filedst);
540 unlink(path);
541 g_free(path);
544 if (rmdir(dir) < 0) {
545 g_printerr("Failed to remove %s: %s\n",
546 dir, strerror(errno));
547 abort();
550 g_free(dir);
551 g_assert(err == 0);
555 int main(int argc, char **argv)
557 g_test_init(&argc, &argv, NULL);
559 qemu_init_main_loop(&error_abort);
561 qemu_mutex_init(&evlock);
563 debug = getenv("FILEMONITOR_DEBUG") != NULL;
564 g_test_add_func("/util/filemonitor", test_file_monitor_events);
566 return g_test_run();