]> git.proxmox.com Git - mirror_qemu.git/blame - tests/test-util-filemonitor.c
tests: refactor file monitor test to make it more understandable
[mirror_qemu.git] / tests / test-util-filemonitor.c
CommitLineData
90e33dfe
DB
1/*
2 * Tests for util/filemonitor-*.c
3 *
4 * Copyright 2018 Red Hat, Inc.
5 *
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.
10 *
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.
15 *
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/>.
18 *
19 */
20
21#include "qemu/osdep.h"
22#include "qemu/main-loop.h"
23#include "qapi/error.h"
24#include "qemu/filemonitor.h"
25
26#include <utime.h>
27
28enum {
b26c3f9c
DB
29 QFILE_MONITOR_TEST_OP_ADD_WATCH,
30 QFILE_MONITOR_TEST_OP_DEL_WATCH,
31 QFILE_MONITOR_TEST_OP_EVENT,
90e33dfe
DB
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,
38};
39
40typedef struct {
41 int type;
42 const char *filesrc;
43 const char *filedst;
b26c3f9c
DB
44 int watchid;
45 int eventid;
90e33dfe
DB
46} QFileMonitorTestOp;
47
90e33dfe
DB
48typedef struct {
49 int id;
50 QFileMonitorEvent event;
51 char *filename;
52} QFileMonitorTestRecord;
53
54
55typedef struct {
56 QemuMutex lock;
57 GList *records;
58} QFileMonitorTestData;
59
60static QemuMutex evlock;
61static bool evstopping;
62static bool evrunning;
b26c3f9c 63static bool debug;
90e33dfe
DB
64
65/*
66 * Main function for a background thread that is
67 * running the event loop during the test
68 */
69static void *
70qemu_file_monitor_test_event_loop(void *opaque G_GNUC_UNUSED)
71{
72 qemu_mutex_lock(&evlock);
73
74 while (!evstopping) {
75 qemu_mutex_unlock(&evlock);
76 main_loop_wait(true);
77 qemu_mutex_lock(&evlock);
78 }
79
80 evrunning = false;
81 qemu_mutex_unlock(&evlock);
82 return NULL;
83}
84
85
86/*
87 * File monitor event handler which simply maintains
88 * an ordered list of all events that it receives
89 */
90static void
91qemu_file_monitor_test_handler(int id,
92 QFileMonitorEvent event,
93 const char *filename,
94 void *opaque)
95{
96 QFileMonitorTestData *data = opaque;
97 QFileMonitorTestRecord *rec = g_new0(QFileMonitorTestRecord, 1);
98
99 rec->id = id;
100 rec->event = event;
101 rec->filename = g_strdup(filename);
102
103 qemu_mutex_lock(&data->lock);
104 data->records = g_list_append(data->records, rec);
105 qemu_mutex_unlock(&data->lock);
106}
107
108
109static void
110qemu_file_monitor_test_record_free(QFileMonitorTestRecord *rec)
111{
112 g_free(rec->filename);
113 g_free(rec);
114}
115
116
117/*
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.
124 */
125static QFileMonitorTestRecord *
126qemu_file_monitor_test_next_record(QFileMonitorTestData *data)
127{
128 GTimer *timer = g_timer_new();
129 QFileMonitorTestRecord *record = NULL;
130 GList *tmp;
131
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);
137 }
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);
143 }
144 qemu_mutex_unlock(&data->lock);
145
146 g_timer_destroy(timer);
147 return record;
148}
149
150
151/*
152 * Check whether the event record we retrieved matches
153 * data we were expecting to see for the event
154 */
155static bool
156qemu_file_monitor_test_expect(QFileMonitorTestData *data,
157 int id,
158 QFileMonitorEvent event,
159 const char *filename)
160{
161 QFileMonitorTestRecord *rec;
162 bool ret = false;
163
164 rec = qemu_file_monitor_test_next_record(data);
165
166 if (!rec) {
167 g_printerr("Missing event watch id %d event %d file %s\n",
168 id, event, filename);
169 return false;
170 }
171
172 if (id != rec->id) {
173 g_printerr("Expected watch id %d but got %d\n", id, rec->id);
174 goto cleanup;
175 }
176
177 if (event != rec->event) {
178 g_printerr("Expected event %d but got %d\n", event, rec->event);
179 goto cleanup;
180 }
181
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;
186 }
187
188 ret = true;
189
190 cleanup:
191 qemu_file_monitor_test_record_free(rec);
192 return ret;
193}
194
195
196static void
b26c3f9c 197test_file_monitor_events(void)
90e33dfe 198{
b26c3f9c
DB
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 },
206
207
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 },
216
217
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 },
226
227
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 },
233
234
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 },
240
241
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 },
256
257
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 },
266
267
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 },
276
277
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 },
290
291
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 },
299
300
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 },
309
310
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 },
315 };
90e33dfe
DB
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;
b26c3f9c 323 gsize i;
90e33dfe
DB
324 char *pathsrc = NULL;
325 char *pathdst = NULL;
326 QFileMonitorTestData data;
327
328 qemu_mutex_init(&data.lock);
329 data.records = NULL;
330
331 /*
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
337 */
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);
343
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;
349 }
350
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();
358 }
359
360 /*
b26c3f9c
DB
361 * Run through the operation sequence validating events
362 * as we go
90e33dfe 363 */
b26c3f9c
DB
364 for (i = 0; i < G_N_ELEMENTS(ops); i++) {
365 const QFileMonitorTestOp *op = &(ops[i]);
90e33dfe 366 int fd;
b26c3f9c 367 int watchid;
90e33dfe
DB
368 struct utimbuf ubuf;
369
370 pathsrc = g_strdup_printf("%s/%s", dir, op->filesrc);
371 if (op->filedst) {
372 pathdst = g_strdup_printf("%s/%s", dir, op->filedst);
373 }
374
375 switch (op->type) {
b26c3f9c
DB
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);
380 }
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;
392 }
393 if (watchid != op->watchid) {
394 g_printerr("Unexpected watch ID %d, wanted %d\n",
395 watchid, op->watchid);
396 goto cleanup;
397 }
398 break;
399 case QFILE_MONITOR_TEST_OP_DEL_WATCH:
400 if (debug) {
401 g_printerr("Del watch %s %d\n", dir, op->watchid);
402 }
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);
411 }
412 if (!qemu_file_monitor_test_expect(
413 &data, op->watchid, op->eventid, op->filesrc))
414 goto cleanup;
415 break;
90e33dfe 416 case QFILE_MONITOR_TEST_OP_CREATE:
b26c3f9c
DB
417 if (debug) {
418 g_printerr("Create %s\n", pathsrc);
419 }
90e33dfe
DB
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;
425 }
426 close(fd);
427 break;
428
429 case QFILE_MONITOR_TEST_OP_APPEND:
b26c3f9c
DB
430 if (debug) {
431 g_printerr("Append %s\n", pathsrc);
432 }
90e33dfe
DB
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;
438 }
439
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;
445 }
446 close(fd);
447 break;
448
449 case QFILE_MONITOR_TEST_OP_TRUNC:
b26c3f9c
DB
450 if (debug) {
451 g_printerr("Truncate %s\n", pathsrc);
452 }
90e33dfe
DB
453 if (truncate(pathsrc, 4) < 0) {
454 g_printerr("Unable to truncate %s: %s",
455 pathsrc, strerror(errno));
456 goto cleanup;
457 }
458 break;
459
460 case QFILE_MONITOR_TEST_OP_RENAME:
b26c3f9c
DB
461 if (debug) {
462 g_printerr("Rename %s -> %s\n", pathsrc, pathdst);
463 }
90e33dfe
DB
464 if (rename(pathsrc, pathdst) < 0) {
465 g_printerr("Unable to rename %s to %s: %s",
466 pathsrc, pathdst, strerror(errno));
467 goto cleanup;
468 }
469 break;
470
471 case QFILE_MONITOR_TEST_OP_UNLINK:
b26c3f9c
DB
472 if (debug) {
473 g_printerr("Unlink %s\n", pathsrc);
474 }
90e33dfe
DB
475 if (unlink(pathsrc) < 0) {
476 g_printerr("Unable to unlink %s: %s",
477 pathsrc, strerror(errno));
478 goto cleanup;
479 }
480 break;
481
482 case QFILE_MONITOR_TEST_OP_TOUCH:
b26c3f9c
DB
483 if (debug) {
484 g_printerr("Touch %s\n", pathsrc);
485 }
90e33dfe
DB
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;
492 }
493 break;
494
495 default:
496 g_assert_not_reached();
497 }
498
499 g_free(pathsrc);
500 g_free(pathdst);
501 pathsrc = pathdst = NULL;
502 }
503
90e33dfe
DB
504 err = 0;
505
506 cleanup:
507 g_free(pathsrc);
508 g_free(pathdst);
509
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);
517 }
518 qemu_mutex_unlock(&evlock);
519
520 if (g_timer_elapsed(timer, NULL) >= 5) {
521 g_printerr("Event loop failed to quit after 5 seconds\n");
522 }
523 g_timer_destroy(timer);
524
90e33dfe
DB
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) {
b26c3f9c
DB
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);
542 }
543 }
544 if (rmdir(dir) < 0) {
545 g_printerr("Failed to remove %s: %s\n",
546 dir, strerror(errno));
547 abort();
548 }
90e33dfe
DB
549 }
550 g_free(dir);
551 g_assert(err == 0);
552}
553
554
90e33dfe
DB
555int main(int argc, char **argv)
556{
557 g_test_init(&argc, &argv, NULL);
558
559 qemu_init_main_loop(&error_abort);
560
561 qemu_mutex_init(&evlock);
562
b26c3f9c
DB
563 debug = getenv("FILEMONITOR_DEBUG") != NULL;
564 g_test_add_func("/util/filemonitor", test_file_monitor_events);
90e33dfe
DB
565
566 return g_test_run();
567}