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"
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
,
38 QFILE_MONITOR_TEST_OP_MKDIR
,
39 QFILE_MONITOR_TEST_OP_RMDIR
,
52 QFileMonitorEvent event
;
54 } QFileMonitorTestRecord
;
60 } QFileMonitorTestData
;
62 static QemuMutex evlock
;
63 static bool evstopping
;
64 static bool evrunning
;
68 * Main function for a background thread that is
69 * running the event loop during the test
72 qemu_file_monitor_test_event_loop(void *opaque G_GNUC_UNUSED
)
74 qemu_mutex_lock(&evlock
);
77 qemu_mutex_unlock(&evlock
);
79 qemu_mutex_lock(&evlock
);
83 qemu_mutex_unlock(&evlock
);
89 * File monitor event handler which simply maintains
90 * an ordered list of all events that it receives
93 qemu_file_monitor_test_handler(int64_t id
,
94 QFileMonitorEvent event
,
98 QFileMonitorTestData
*data
= opaque
;
99 QFileMonitorTestRecord
*rec
= g_new0(QFileMonitorTestRecord
, 1);
103 rec
->filename
= g_strdup(filename
);
105 qemu_mutex_lock(&data
->lock
);
106 data
->records
= g_list_append(data
->records
, rec
);
107 qemu_mutex_unlock(&data
->lock
);
112 qemu_file_monitor_test_record_free(QFileMonitorTestRecord
*rec
)
114 g_free(rec
->filename
);
120 * Get the next event record that has been received by
121 * the file monitor event handler. Since events are
122 * emitted in the background thread running the event
123 * loop, we can't assume there is a record available
124 * immediately. Thus we will sleep for upto 5 seconds
125 * to wait for the event to be queued for us.
127 static QFileMonitorTestRecord
*
128 qemu_file_monitor_test_next_record(QFileMonitorTestData
*data
)
130 GTimer
*timer
= g_timer_new();
131 QFileMonitorTestRecord
*record
= NULL
;
134 qemu_mutex_lock(&data
->lock
);
135 while (!data
->records
&& g_timer_elapsed(timer
, NULL
) < 5) {
136 qemu_mutex_unlock(&data
->lock
);
138 qemu_mutex_lock(&data
->lock
);
141 record
= data
->records
->data
;
143 data
->records
= g_list_remove_link(data
->records
, tmp
);
146 qemu_mutex_unlock(&data
->lock
);
148 g_timer_destroy(timer
);
154 * Check whether the event record we retrieved matches
155 * data we were expecting to see for the event
158 qemu_file_monitor_test_expect(QFileMonitorTestData
*data
,
160 QFileMonitorEvent event
,
161 const char *filename
)
163 QFileMonitorTestRecord
*rec
;
166 rec
= qemu_file_monitor_test_next_record(data
);
169 g_printerr("Missing event watch id %" PRIx64
" event %d file %s\n",
170 id
, event
, filename
);
175 g_printerr("Expected watch id %" PRIx64
" but got %" PRIx64
"\n",
180 if (event
!= rec
->event
) {
181 g_printerr("Expected event %d but got %d\n", event
, rec
->event
);
185 if (!g_str_equal(filename
, rec
->filename
)) {
186 g_printerr("Expected filename %s but got %s\n",
187 filename
, rec
->filename
);
194 qemu_file_monitor_test_record_free(rec
);
200 test_file_monitor_events(void)
208 QFileMonitorTestOp ops
[] = {
209 { .type
= QFILE_MONITOR_TEST_OP_ADD_WATCH
,
210 .filesrc
= NULL
, .watchid
= &watch0
},
211 { .type
= QFILE_MONITOR_TEST_OP_ADD_WATCH
,
212 .filesrc
= "one.txt", .watchid
= &watch1
},
213 { .type
= QFILE_MONITOR_TEST_OP_ADD_WATCH
,
214 .filesrc
= "two.txt", .watchid
= &watch2
},
217 { .type
= QFILE_MONITOR_TEST_OP_CREATE
,
218 .filesrc
= "one.txt", },
219 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
220 .filesrc
= "one.txt", .watchid
= &watch0
,
221 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
222 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
223 .filesrc
= "one.txt", .watchid
= &watch1
,
224 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
227 { .type
= QFILE_MONITOR_TEST_OP_CREATE
,
228 .filesrc
= "two.txt", },
229 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
230 .filesrc
= "two.txt", .watchid
= &watch0
,
231 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
232 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
233 .filesrc
= "two.txt", .watchid
= &watch2
,
234 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
237 { .type
= QFILE_MONITOR_TEST_OP_CREATE
,
238 .filesrc
= "three.txt", },
239 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
240 .filesrc
= "three.txt", .watchid
= &watch0
,
241 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
244 { .type
= QFILE_MONITOR_TEST_OP_UNLINK
,
245 .filesrc
= "three.txt", },
246 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
247 .filesrc
= "three.txt", .watchid
= &watch0
,
248 .eventid
= QFILE_MONITOR_EVENT_DELETED
},
251 { .type
= QFILE_MONITOR_TEST_OP_RENAME
,
252 .filesrc
= "one.txt", .filedst
= "two.txt" },
253 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
254 .filesrc
= "one.txt", .watchid
= &watch0
,
255 .eventid
= QFILE_MONITOR_EVENT_DELETED
},
256 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
257 .filesrc
= "one.txt", .watchid
= &watch1
,
258 .eventid
= QFILE_MONITOR_EVENT_DELETED
},
259 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
260 .filesrc
= "two.txt", .watchid
= &watch0
,
261 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
262 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
263 .filesrc
= "two.txt", .watchid
= &watch2
,
264 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
267 { .type
= QFILE_MONITOR_TEST_OP_APPEND
,
268 .filesrc
= "two.txt", },
269 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
270 .filesrc
= "two.txt", .watchid
= &watch0
,
271 .eventid
= QFILE_MONITOR_EVENT_MODIFIED
},
272 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
273 .filesrc
= "two.txt", .watchid
= &watch2
,
274 .eventid
= QFILE_MONITOR_EVENT_MODIFIED
},
277 { .type
= QFILE_MONITOR_TEST_OP_TOUCH
,
278 .filesrc
= "two.txt", },
279 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
280 .filesrc
= "two.txt", .watchid
= &watch0
,
281 .eventid
= QFILE_MONITOR_EVENT_ATTRIBUTES
},
282 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
283 .filesrc
= "two.txt", .watchid
= &watch2
,
284 .eventid
= QFILE_MONITOR_EVENT_ATTRIBUTES
},
287 { .type
= QFILE_MONITOR_TEST_OP_DEL_WATCH
,
288 .filesrc
= "one.txt", .watchid
= &watch1
},
289 { .type
= QFILE_MONITOR_TEST_OP_ADD_WATCH
,
290 .filesrc
= "one.txt", .watchid
= &watch3
},
291 { .type
= QFILE_MONITOR_TEST_OP_CREATE
,
292 .filesrc
= "one.txt", },
293 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
294 .filesrc
= "one.txt", .watchid
= &watch0
,
295 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
296 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
297 .filesrc
= "one.txt", .watchid
= &watch3
,
298 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
301 { .type
= QFILE_MONITOR_TEST_OP_DEL_WATCH
,
302 .filesrc
= "one.txt", .watchid
= &watch3
},
303 { .type
= QFILE_MONITOR_TEST_OP_UNLINK
,
304 .filesrc
= "one.txt", },
305 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
306 .filesrc
= "one.txt", .watchid
= &watch0
,
307 .eventid
= QFILE_MONITOR_EVENT_DELETED
},
310 { .type
= QFILE_MONITOR_TEST_OP_MKDIR
,
311 .filesrc
= "fish", },
312 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
313 .filesrc
= "fish", .watchid
= &watch0
,
314 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
317 { .type
= QFILE_MONITOR_TEST_OP_ADD_WATCH
,
318 .filesrc
= "fish/", .watchid
= &watch4
},
319 { .type
= QFILE_MONITOR_TEST_OP_ADD_WATCH
,
320 .filesrc
= "fish/one.txt", .watchid
= &watch5
},
321 { .type
= QFILE_MONITOR_TEST_OP_CREATE
,
322 .filesrc
= "fish/one.txt", },
323 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
324 .filesrc
= "one.txt", .watchid
= &watch4
,
325 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
326 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
327 .filesrc
= "one.txt", .watchid
= &watch5
,
328 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
331 { .type
= QFILE_MONITOR_TEST_OP_DEL_WATCH
,
332 .filesrc
= "fish/one.txt", .watchid
= &watch5
},
333 { .type
= QFILE_MONITOR_TEST_OP_RENAME
,
334 .filesrc
= "fish/one.txt", .filedst
= "two.txt", },
335 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
336 .filesrc
= "one.txt", .watchid
= &watch4
,
337 .eventid
= QFILE_MONITOR_EVENT_DELETED
},
338 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
339 .filesrc
= "two.txt", .watchid
= &watch0
,
340 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
341 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
342 .filesrc
= "two.txt", .watchid
= &watch2
,
343 .eventid
= QFILE_MONITOR_EVENT_CREATED
},
346 { .type
= QFILE_MONITOR_TEST_OP_RMDIR
,
347 .filesrc
= "fish", },
348 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
349 .filesrc
= "", .watchid
= &watch4
,
350 .eventid
= QFILE_MONITOR_EVENT_IGNORED
},
351 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
352 .filesrc
= "fish", .watchid
= &watch0
,
353 .eventid
= QFILE_MONITOR_EVENT_DELETED
},
354 { .type
= QFILE_MONITOR_TEST_OP_DEL_WATCH
,
355 .filesrc
= "fish", .watchid
= &watch4
},
358 { .type
= QFILE_MONITOR_TEST_OP_UNLINK
,
359 .filesrc
= "two.txt", },
360 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
361 .filesrc
= "two.txt", .watchid
= &watch0
,
362 .eventid
= QFILE_MONITOR_EVENT_DELETED
},
363 { .type
= QFILE_MONITOR_TEST_OP_EVENT
,
364 .filesrc
= "two.txt", .watchid
= &watch2
,
365 .eventid
= QFILE_MONITOR_EVENT_DELETED
},
368 { .type
= QFILE_MONITOR_TEST_OP_DEL_WATCH
,
369 .filesrc
= "two.txt", .watchid
= &watch2
},
370 { .type
= QFILE_MONITOR_TEST_OP_DEL_WATCH
,
371 .filesrc
= NULL
, .watchid
= &watch0
},
373 Error
*local_err
= NULL
;
375 QFileMonitor
*mon
= qemu_file_monitor_new(&local_err
);
381 char *pathsrc
= NULL
;
382 char *pathdst
= NULL
;
383 QFileMonitorTestData data
;
384 GHashTable
*ids
= g_hash_table_new(g_int64_hash
, g_int64_equal
);
386 qemu_mutex_init(&data
.lock
);
390 * The file monitor needs the main loop running in
391 * order to receive events from inotify. We must
392 * thus spawn a background thread to run an event
393 * loop impl, while this thread triggers the
394 * actual file operations we're testing
398 qemu_thread_create(&th
, "event-loop",
399 qemu_file_monitor_test_event_loop
, NULL
,
400 QEMU_THREAD_JOINABLE
);
403 g_printerr("File monitoring not available: %s",
404 error_get_pretty(local_err
));
405 error_free(local_err
);
409 dir
= g_dir_make_tmp("test-util-filemonitor-XXXXXX",
412 g_printerr("Unable to create tmp dir %s",
419 * Run through the operation sequence validating events
422 for (i
= 0; i
< G_N_ELEMENTS(ops
); i
++) {
423 const QFileMonitorTestOp
*op
= &(ops
[i
]);
427 const char *watchfile
;
429 pathsrc
= g_strdup_printf("%s/%s", dir
, op
->filesrc
);
431 pathdst
= g_strdup_printf("%s/%s", dir
, op
->filedst
);
435 case QFILE_MONITOR_TEST_OP_ADD_WATCH
:
437 g_printerr("Add watch %s %s\n",
440 if (op
->filesrc
&& strchr(op
->filesrc
, '/')) {
441 watchdir
= g_strdup_printf("%s/%s", dir
, op
->filesrc
);
442 watchfile
= strrchr(watchdir
, '/');
443 *(char *)watchfile
= '\0';
445 if (*watchfile
== '\0') {
449 watchdir
= g_strdup(dir
);
450 watchfile
= op
->filesrc
;
453 qemu_file_monitor_add_watch(mon
,
456 qemu_file_monitor_test_handler
,
460 if (*op
->watchid
< 0) {
461 g_printerr("Unable to add watch %s",
462 error_get_pretty(local_err
));
466 g_printerr("Watch ID %" PRIx64
"\n", *op
->watchid
);
468 if (g_hash_table_contains(ids
, op
->watchid
)) {
469 g_printerr("Watch ID %" PRIx64
"already exists", *op
->watchid
);
472 g_hash_table_add(ids
, op
->watchid
);
474 case QFILE_MONITOR_TEST_OP_DEL_WATCH
:
476 g_printerr("Del watch %s %" PRIx64
"\n", dir
, *op
->watchid
);
478 if (op
->filesrc
&& strchr(op
->filesrc
, '/')) {
479 watchdir
= g_strdup_printf("%s/%s", dir
, op
->filesrc
);
480 watchfile
= strrchr(watchdir
, '/');
481 *(char *)watchfile
= '\0';
483 watchdir
= g_strdup(dir
);
485 g_hash_table_remove(ids
, op
->watchid
);
486 qemu_file_monitor_remove_watch(mon
,
491 case QFILE_MONITOR_TEST_OP_EVENT
:
493 g_printerr("Event id=%" PRIx64
" event=%d file=%s\n",
494 *op
->watchid
, op
->eventid
, op
->filesrc
);
496 if (!qemu_file_monitor_test_expect(
497 &data
, *op
->watchid
, op
->eventid
, op
->filesrc
))
500 case QFILE_MONITOR_TEST_OP_CREATE
:
502 g_printerr("Create %s\n", pathsrc
);
504 fd
= open(pathsrc
, O_WRONLY
| O_CREAT
, 0700);
506 g_printerr("Unable to create %s: %s",
507 pathsrc
, strerror(errno
));
513 case QFILE_MONITOR_TEST_OP_APPEND
:
515 g_printerr("Append %s\n", pathsrc
);
517 fd
= open(pathsrc
, O_WRONLY
| O_APPEND
, 0700);
519 g_printerr("Unable to open %s: %s",
520 pathsrc
, strerror(errno
));
524 if (write(fd
, "Hello World", 10) != 10) {
525 g_printerr("Unable to write %s: %s",
526 pathsrc
, strerror(errno
));
533 case QFILE_MONITOR_TEST_OP_TRUNC
:
535 g_printerr("Truncate %s\n", pathsrc
);
537 if (truncate(pathsrc
, 4) < 0) {
538 g_printerr("Unable to truncate %s: %s",
539 pathsrc
, strerror(errno
));
544 case QFILE_MONITOR_TEST_OP_RENAME
:
546 g_printerr("Rename %s -> %s\n", pathsrc
, pathdst
);
548 if (rename(pathsrc
, pathdst
) < 0) {
549 g_printerr("Unable to rename %s to %s: %s",
550 pathsrc
, pathdst
, strerror(errno
));
555 case QFILE_MONITOR_TEST_OP_UNLINK
:
557 g_printerr("Unlink %s\n", pathsrc
);
559 if (unlink(pathsrc
) < 0) {
560 g_printerr("Unable to unlink %s: %s",
561 pathsrc
, strerror(errno
));
566 case QFILE_MONITOR_TEST_OP_TOUCH
:
568 g_printerr("Touch %s\n", pathsrc
);
572 if (utime(pathsrc
, &ubuf
) < 0) {
573 g_printerr("Unable to touch %s: %s",
574 pathsrc
, strerror(errno
));
579 case QFILE_MONITOR_TEST_OP_MKDIR
:
581 g_printerr("Mkdir %s\n", pathsrc
);
583 if (mkdir(pathsrc
, 0700) < 0) {
584 g_printerr("Unable to mkdir %s: %s",
585 pathsrc
, strerror(errno
));
590 case QFILE_MONITOR_TEST_OP_RMDIR
:
592 g_printerr("Rmdir %s\n", pathsrc
);
594 if (rmdir(pathsrc
) < 0) {
595 g_printerr("Unable to rmdir %s: %s",
596 pathsrc
, strerror(errno
));
602 g_assert_not_reached();
607 pathsrc
= pathdst
= NULL
;
610 g_assert_cmpint(g_hash_table_size(ids
), ==, 0);
618 qemu_mutex_lock(&evlock
);
620 timer
= g_timer_new();
621 while (evrunning
&& g_timer_elapsed(timer
, NULL
) < 5) {
622 qemu_mutex_unlock(&evlock
);
624 qemu_mutex_lock(&evlock
);
626 qemu_mutex_unlock(&evlock
);
628 if (g_timer_elapsed(timer
, NULL
) >= 5) {
629 g_printerr("Event loop failed to quit after 5 seconds\n");
631 g_timer_destroy(timer
);
633 qemu_file_monitor_free(mon
);
634 g_list_foreach(data
.records
,
635 (GFunc
)qemu_file_monitor_test_record_free
, NULL
);
636 g_list_free(data
.records
);
637 qemu_mutex_destroy(&data
.lock
);
639 for (i
= 0; i
< G_N_ELEMENTS(ops
); i
++) {
640 const QFileMonitorTestOp
*op
= &(ops
[i
]);
641 char *path
= g_strdup_printf("%s/%s",
643 if (op
->type
== QFILE_MONITOR_TEST_OP_MKDIR
) {
650 path
= g_strdup_printf("%s/%s",
657 if (rmdir(dir
) < 0) {
658 g_printerr("Failed to remove %s: %s\n",
659 dir
, strerror(errno
));
663 g_hash_table_unref(ids
);
669 int main(int argc
, char **argv
)
671 g_test_init(&argc
, &argv
, NULL
);
673 qemu_init_main_loop(&error_abort
);
675 qemu_mutex_init(&evlock
);
677 debug
= getenv("FILEMONITOR_DEBUG") != NULL
;
678 g_test_add_func("/util/filemonitor", test_file_monitor_events
);