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 <glib/gstdio.h>
31 QFILE_MONITOR_TEST_OP_ADD_WATCH
,
32 QFILE_MONITOR_TEST_OP_DEL_WATCH
,
33 QFILE_MONITOR_TEST_OP_EVENT
,
34 QFILE_MONITOR_TEST_OP_CREATE
,
35 QFILE_MONITOR_TEST_OP_APPEND
,
36 QFILE_MONITOR_TEST_OP_TRUNC
,
37 QFILE_MONITOR_TEST_OP_RENAME
,
38 QFILE_MONITOR_TEST_OP_TOUCH
,
39 QFILE_MONITOR_TEST_OP_UNLINK
,
40 QFILE_MONITOR_TEST_OP_MKDIR
,
41 QFILE_MONITOR_TEST_OP_RMDIR
,
51 * Only valid with OP_EVENT - this event might be
52 * swapped with the next OP_EVENT
59 QFileMonitorEvent event
;
61 } QFileMonitorTestRecord
;
67 } QFileMonitorTestData
;
69 static QemuMutex evlock
;
70 static bool evstopping
;
71 static bool evrunning
;
75 * Main function for a background thread that is
76 * running the event loop during the test
79 qemu_file_monitor_test_event_loop(void *opaque G_GNUC_UNUSED
)
81 qemu_mutex_lock(&evlock
);
84 qemu_mutex_unlock(&evlock
);
86 qemu_mutex_lock(&evlock
);
90 qemu_mutex_unlock(&evlock
);
96 * File monitor event handler which simply maintains
97 * an ordered list of all events that it receives
100 qemu_file_monitor_test_handler(int64_t id
,
101 QFileMonitorEvent event
,
102 const char *filename
,
105 QFileMonitorTestData
*data
= opaque
;
106 QFileMonitorTestRecord
*rec
= g_new0(QFileMonitorTestRecord
, 1);
109 g_printerr("Queue event id %" PRIx64
" event %d file %s\n",
110 id
, event
, filename
);
114 rec
->filename
= g_strdup(filename
);
116 qemu_mutex_lock(&data
->lock
);
117 data
->records
= g_list_append(data
->records
, rec
);
118 qemu_mutex_unlock(&data
->lock
);
123 qemu_file_monitor_test_record_free(QFileMonitorTestRecord
*rec
)
125 g_free(rec
->filename
);
131 * Get the next event record that has been received by
132 * the file monitor event handler. Since events are
133 * emitted in the background thread running the event
134 * loop, we can't assume there is a record available
135 * immediately. Thus we will sleep for up to 5 seconds
136 * to wait for the event to be queued for us.
138 static QFileMonitorTestRecord
*
139 qemu_file_monitor_test_next_record(QFileMonitorTestData
*data
,
140 QFileMonitorTestRecord
*pushback
)
142 GTimer
*timer
= g_timer_new();
143 QFileMonitorTestRecord
*record
= NULL
;
146 qemu_mutex_lock(&data
->lock
);
147 while (!data
->records
&& g_timer_elapsed(timer
, NULL
) < 5) {
148 qemu_mutex_unlock(&data
->lock
);
150 qemu_mutex_lock(&data
->lock
);
153 record
= data
->records
->data
;
155 data
->records
->data
= pushback
;
158 data
->records
= g_list_remove_link(data
->records
, tmp
);
161 } else if (pushback
) {
162 qemu_file_monitor_test_record_free(pushback
);
164 qemu_mutex_unlock(&data
->lock
);
166 g_timer_destroy(timer
);
172 * Check whether the event record we retrieved matches
173 * data we were expecting to see for the event
176 qemu_file_monitor_test_expect(QFileMonitorTestData
*data
,
178 QFileMonitorEvent event
,
179 const char *filename
,
182 QFileMonitorTestRecord
*rec
;
185 rec
= qemu_file_monitor_test_next_record(data
, NULL
);
189 g_printerr("Missing event watch id %" PRIx64
" event %d file %s\n",
190 id
, event
, filename
);
196 rec
= qemu_file_monitor_test_next_record(data
, rec
);
200 g_printerr("Expected watch id %" PRIx64
" but got %" PRIx64
"\n",
205 if (event
!= rec
->event
) {
206 g_printerr("Expected event %d but got %d\n", event
, rec
->event
);
210 if (!g_str_equal(filename
, rec
->filename
)) {
211 g_printerr("Expected filename %s but got %s\n",
212 filename
, rec
->filename
);
219 qemu_file_monitor_test_record_free(rec
);
225 test_file_monitor_events(void)
233 QFileMonitorTestOp ops
[] = {
234 { .type
= QFILE_MONITOR_TEST_OP_ADD_WATCH
,
235 .filesrc
= NULL
, .watchid
= &watch0
},
236 { .type
= QFILE_MONITOR_TEST_OP_ADD_WATCH
,
237 .filesrc
= "one.txt", .watchid
= &watch1
},
238 { .type
= QFILE_MONITOR_TEST_OP_ADD_WATCH
,
239 .filesrc
= "two.txt", .watchid
= &watch2
},
242 { .type
= QFILE_MONITOR_TEST_OP_CREATE
,
243 .filesrc
= "one.txt", },
244 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
245 .filesrc
= "one.txt", .watchid
= &watch0
,
246 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
247 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
248 .filesrc
= "one.txt", .watchid
= &watch1
,
249 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
252 { .type
= QFILE_MONITOR_TEST_OP_CREATE
,
253 .filesrc
= "two.txt", },
254 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
255 .filesrc
= "two.txt", .watchid
= &watch0
,
256 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
257 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
258 .filesrc
= "two.txt", .watchid
= &watch2
,
259 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
262 { .type
= QFILE_MONITOR_TEST_OP_CREATE
,
263 .filesrc
= "three.txt", },
264 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
265 .filesrc
= "three.txt", .watchid
= &watch0
,
266 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
269 { .type
= QFILE_MONITOR_TEST_OP_UNLINK
,
270 .filesrc
= "three.txt", },
271 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
272 .filesrc
= "three.txt", .watchid
= &watch0
,
273 .eventid
= QFILE_MONITOR_EVENT_DELETED
},
276 { .type
= QFILE_MONITOR_TEST_OP_RENAME
,
277 .filesrc
= "one.txt", .filedst
= "two.txt" },
278 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
279 .filesrc
= "one.txt", .watchid
= &watch0
,
280 .eventid
= QFILE_MONITOR_EVENT_DELETED
},
281 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
282 .filesrc
= "one.txt", .watchid
= &watch1
,
283 .eventid
= QFILE_MONITOR_EVENT_DELETED
},
284 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
285 .filesrc
= "two.txt", .watchid
= &watch0
,
286 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
287 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
288 .filesrc
= "two.txt", .watchid
= &watch2
,
289 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
292 { .type
= QFILE_MONITOR_TEST_OP_APPEND
,
293 .filesrc
= "two.txt", },
294 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
295 .filesrc
= "two.txt", .watchid
= &watch0
,
296 .eventid
= QFILE_MONITOR_EVENT_MODIFIED
},
297 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
298 .filesrc
= "two.txt", .watchid
= &watch2
,
299 .eventid
= QFILE_MONITOR_EVENT_MODIFIED
},
302 { .type
= QFILE_MONITOR_TEST_OP_TOUCH
,
303 .filesrc
= "two.txt", },
304 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
305 .filesrc
= "two.txt", .watchid
= &watch0
,
306 .eventid
= QFILE_MONITOR_EVENT_ATTRIBUTES
},
307 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
308 .filesrc
= "two.txt", .watchid
= &watch2
,
309 .eventid
= QFILE_MONITOR_EVENT_ATTRIBUTES
},
312 { .type
= QFILE_MONITOR_TEST_OP_DEL_WATCH
,
313 .filesrc
= "one.txt", .watchid
= &watch1
},
314 { .type
= QFILE_MONITOR_TEST_OP_ADD_WATCH
,
315 .filesrc
= "one.txt", .watchid
= &watch3
},
316 { .type
= QFILE_MONITOR_TEST_OP_CREATE
,
317 .filesrc
= "one.txt", },
318 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
319 .filesrc
= "one.txt", .watchid
= &watch0
,
320 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
321 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
322 .filesrc
= "one.txt", .watchid
= &watch3
,
323 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
326 { .type
= QFILE_MONITOR_TEST_OP_DEL_WATCH
,
327 .filesrc
= "one.txt", .watchid
= &watch3
},
328 { .type
= QFILE_MONITOR_TEST_OP_UNLINK
,
329 .filesrc
= "one.txt", },
330 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
331 .filesrc
= "one.txt", .watchid
= &watch0
,
332 .eventid
= QFILE_MONITOR_EVENT_DELETED
},
335 { .type
= QFILE_MONITOR_TEST_OP_MKDIR
,
336 .filesrc
= "fish", },
337 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
338 .filesrc
= "fish", .watchid
= &watch0
,
339 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
342 { .type
= QFILE_MONITOR_TEST_OP_ADD_WATCH
,
343 .filesrc
= "fish/", .watchid
= &watch4
},
344 { .type
= QFILE_MONITOR_TEST_OP_ADD_WATCH
,
345 .filesrc
= "fish/one.txt", .watchid
= &watch5
},
346 { .type
= QFILE_MONITOR_TEST_OP_CREATE
,
347 .filesrc
= "fish/one.txt", },
348 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
349 .filesrc
= "one.txt", .watchid
= &watch4
,
350 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
351 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
352 .filesrc
= "one.txt", .watchid
= &watch5
,
353 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
356 { .type
= QFILE_MONITOR_TEST_OP_DEL_WATCH
,
357 .filesrc
= "fish/one.txt", .watchid
= &watch5
},
358 { .type
= QFILE_MONITOR_TEST_OP_RENAME
,
359 .filesrc
= "fish/one.txt", .filedst
= "two.txt", },
360 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
361 .filesrc
= "one.txt", .watchid
= &watch4
,
362 .eventid
= QFILE_MONITOR_EVENT_DELETED
},
363 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
364 .filesrc
= "two.txt", .watchid
= &watch0
,
365 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
366 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
367 .filesrc
= "two.txt", .watchid
= &watch2
,
368 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
371 { .type
= QFILE_MONITOR_TEST_OP_RMDIR
,
372 .filesrc
= "fish", },
373 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
374 .filesrc
= "", .watchid
= &watch4
,
375 .eventid
= QFILE_MONITOR_EVENT_IGNORED
,
377 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
378 .filesrc
= "fish", .watchid
= &watch0
,
379 .eventid
= QFILE_MONITOR_EVENT_DELETED
},
380 { .type
= QFILE_MONITOR_TEST_OP_DEL_WATCH
,
381 .filesrc
= "fish", .watchid
= &watch4
},
384 { .type
= QFILE_MONITOR_TEST_OP_UNLINK
,
385 .filesrc
= "two.txt", },
386 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
387 .filesrc
= "two.txt", .watchid
= &watch0
,
388 .eventid
= QFILE_MONITOR_EVENT_DELETED
},
389 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
390 .filesrc
= "two.txt", .watchid
= &watch2
,
391 .eventid
= QFILE_MONITOR_EVENT_DELETED
},
394 { .type
= QFILE_MONITOR_TEST_OP_DEL_WATCH
,
395 .filesrc
= "two.txt", .watchid
= &watch2
},
396 { .type
= QFILE_MONITOR_TEST_OP_DEL_WATCH
,
397 .filesrc
= NULL
, .watchid
= &watch0
},
399 Error
*local_err
= NULL
;
401 QFileMonitor
*mon
= qemu_file_monitor_new(&local_err
);
407 char *pathsrc
= NULL
;
408 char *pathdst
= NULL
;
409 QFileMonitorTestData data
;
410 GHashTable
*ids
= g_hash_table_new(g_int64_hash
, g_int64_equal
);
413 qemu_mutex_init(&data
.lock
);
417 * This test does not work on Travis LXD containers since some
418 * syscalls are blocked in that environment.
420 travis_arch
= getenv("TRAVIS_ARCH");
421 if (travis_arch
&& !g_str_equal(travis_arch
, "x86_64")) {
422 g_test_skip("Test does not work on non-x86 Travis containers.");
427 * The file monitor needs the main loop running in
428 * order to receive events from inotify. We must
429 * thus spawn a background thread to run an event
430 * loop impl, while this thread triggers the
431 * actual file operations we're testing
435 qemu_thread_create(&th
, "event-loop",
436 qemu_file_monitor_test_event_loop
, NULL
,
437 QEMU_THREAD_JOINABLE
);
440 g_printerr("File monitoring not available: %s",
441 error_get_pretty(local_err
));
442 error_free(local_err
);
446 dir
= g_dir_make_tmp("test-util-filemonitor-XXXXXX",
449 g_printerr("Unable to create tmp dir %s",
456 * Run through the operation sequence validating events
459 for (i
= 0; i
< G_N_ELEMENTS(ops
); i
++) {
460 const QFileMonitorTestOp
*op
= &(ops
[i
]);
464 const char *watchfile
;
466 pathsrc
= g_strdup_printf("%s/%s", dir
, op
->filesrc
);
468 pathdst
= g_strdup_printf("%s/%s", dir
, op
->filedst
);
472 case QFILE_MONITOR_TEST_OP_ADD_WATCH
:
474 g_printerr("Add watch %s %s\n",
477 if (op
->filesrc
&& strchr(op
->filesrc
, '/')) {
478 watchdir
= g_strdup_printf("%s/%s", dir
, op
->filesrc
);
479 watchfile
= strrchr(watchdir
, '/');
480 *(char *)watchfile
= '\0';
482 if (*watchfile
== '\0') {
486 watchdir
= g_strdup(dir
);
487 watchfile
= op
->filesrc
;
490 qemu_file_monitor_add_watch(mon
,
493 qemu_file_monitor_test_handler
,
497 if (*op
->watchid
< 0) {
498 g_printerr("Unable to add watch %s",
499 error_get_pretty(local_err
));
500 error_free(local_err
);
504 g_printerr("Watch ID %" PRIx64
"\n", *op
->watchid
);
506 if (g_hash_table_contains(ids
, op
->watchid
)) {
507 g_printerr("Watch ID %" PRIx64
"already exists", *op
->watchid
);
510 g_hash_table_add(ids
, op
->watchid
);
512 case QFILE_MONITOR_TEST_OP_DEL_WATCH
:
514 g_printerr("Del watch %s %" PRIx64
"\n", dir
, *op
->watchid
);
516 if (op
->filesrc
&& strchr(op
->filesrc
, '/')) {
517 watchdir
= g_strdup_printf("%s/%s", dir
, op
->filesrc
);
518 watchfile
= strrchr(watchdir
, '/');
519 *(char *)watchfile
= '\0';
521 watchdir
= g_strdup(dir
);
523 g_hash_table_remove(ids
, op
->watchid
);
524 qemu_file_monitor_remove_watch(mon
,
529 case QFILE_MONITOR_TEST_OP_EVENT
:
531 g_printerr("Event id=%" PRIx64
" event=%d file=%s\n",
532 *op
->watchid
, op
->eventid
, op
->filesrc
);
534 if (!qemu_file_monitor_test_expect(&data
, *op
->watchid
,
535 op
->eventid
, op
->filesrc
,
539 case QFILE_MONITOR_TEST_OP_CREATE
:
541 g_printerr("Create %s\n", pathsrc
);
543 fd
= open(pathsrc
, O_WRONLY
| O_CREAT
, 0700);
545 g_printerr("Unable to create %s: %s",
546 pathsrc
, strerror(errno
));
552 case QFILE_MONITOR_TEST_OP_APPEND
:
554 g_printerr("Append %s\n", pathsrc
);
556 fd
= open(pathsrc
, O_WRONLY
| O_APPEND
, 0700);
558 g_printerr("Unable to open %s: %s",
559 pathsrc
, strerror(errno
));
563 if (write(fd
, "Hello World", 10) != 10) {
564 g_printerr("Unable to write %s: %s",
565 pathsrc
, strerror(errno
));
572 case QFILE_MONITOR_TEST_OP_TRUNC
:
574 g_printerr("Truncate %s\n", pathsrc
);
576 if (truncate(pathsrc
, 4) < 0) {
577 g_printerr("Unable to truncate %s: %s",
578 pathsrc
, strerror(errno
));
583 case QFILE_MONITOR_TEST_OP_RENAME
:
585 g_printerr("Rename %s -> %s\n", pathsrc
, pathdst
);
587 if (rename(pathsrc
, pathdst
) < 0) {
588 g_printerr("Unable to rename %s to %s: %s",
589 pathsrc
, pathdst
, strerror(errno
));
594 case QFILE_MONITOR_TEST_OP_UNLINK
:
596 g_printerr("Unlink %s\n", pathsrc
);
598 if (unlink(pathsrc
) < 0) {
599 g_printerr("Unable to unlink %s: %s",
600 pathsrc
, strerror(errno
));
605 case QFILE_MONITOR_TEST_OP_TOUCH
:
607 g_printerr("Touch %s\n", pathsrc
);
611 if (utime(pathsrc
, &ubuf
) < 0) {
612 g_printerr("Unable to touch %s: %s",
613 pathsrc
, strerror(errno
));
618 case QFILE_MONITOR_TEST_OP_MKDIR
:
620 g_printerr("Mkdir %s\n", pathsrc
);
622 if (g_mkdir_with_parents(pathsrc
, 0700) < 0) {
623 g_printerr("Unable to mkdir %s: %s",
624 pathsrc
, strerror(errno
));
629 case QFILE_MONITOR_TEST_OP_RMDIR
:
631 g_printerr("Rmdir %s\n", pathsrc
);
633 if (rmdir(pathsrc
) < 0) {
634 g_printerr("Unable to rmdir %s: %s",
635 pathsrc
, strerror(errno
));
641 g_assert_not_reached();
646 pathsrc
= pathdst
= NULL
;
649 g_assert_cmpint(g_hash_table_size(ids
), ==, 0);
657 qemu_mutex_lock(&evlock
);
659 timer
= g_timer_new();
660 while (evrunning
&& g_timer_elapsed(timer
, NULL
) < 5) {
661 qemu_mutex_unlock(&evlock
);
663 qemu_mutex_lock(&evlock
);
665 qemu_mutex_unlock(&evlock
);
667 if (g_timer_elapsed(timer
, NULL
) >= 5) {
668 g_printerr("Event loop failed to quit after 5 seconds\n");
670 g_timer_destroy(timer
);
672 qemu_file_monitor_free(mon
);
673 g_list_foreach(data
.records
,
674 (GFunc
)qemu_file_monitor_test_record_free
, NULL
);
675 g_list_free(data
.records
);
676 qemu_mutex_destroy(&data
.lock
);
678 for (i
= 0; i
< G_N_ELEMENTS(ops
); i
++) {
679 const QFileMonitorTestOp
*op
= &(ops
[i
]);
680 char *path
= g_strdup_printf("%s/%s",
682 if (op
->type
== QFILE_MONITOR_TEST_OP_MKDIR
) {
689 path
= g_strdup_printf("%s/%s",
696 if (rmdir(dir
) < 0) {
697 g_printerr("Failed to remove %s: %s\n",
698 dir
, strerror(errno
));
702 g_hash_table_unref(ids
);
708 int main(int argc
, char **argv
)
710 g_test_init(&argc
, &argv
, NULL
);
712 qemu_init_main_loop(&error_abort
);
714 qemu_mutex_init(&evlock
);
716 debug
= getenv("FILEMONITOR_DEBUG") != NULL
;
717 g_test_add_func("/util/filemonitor", test_file_monitor_events
);