1 #include "config.h"
2
3 #include <errno.h>
4 #include <stdlib.h>
5 #include <gio/gio.h>
6
7 /* These tests were written for the inotify implementation.
8 * Other implementations may require slight adjustments in
9 * the tests, e.g. the length of timeouts
10 */
11
12 typedef struct
13 {
14 GFile *tmp_dir;
15 } Fixture;
16
17 static void
setup(Fixture * fixture,gconstpointer user_data)18 setup (Fixture *fixture,
19 gconstpointer user_data)
20 {
21 gchar *path = NULL;
22 GError *local_error = NULL;
23
24 path = g_dir_make_tmp ("gio-test-testfilemonitor_XXXXXX", &local_error);
25 g_assert_no_error (local_error);
26
27 fixture->tmp_dir = g_file_new_for_path (path);
28
29 g_test_message ("Using temporary directory: %s", path);
30 }
31
32 static void
teardown(Fixture * fixture,gconstpointer user_data)33 teardown (Fixture *fixture,
34 gconstpointer user_data)
35 {
36 GError *local_error = NULL;
37
38 g_file_delete (fixture->tmp_dir, NULL, &local_error);
39 g_assert_no_error (local_error);
40 g_clear_object (&fixture->tmp_dir);
41 }
42
43 typedef enum {
44 NONE = 0,
45 INOTIFY = (1 << 1),
46 KQUEUE = (1 << 2)
47 } Environment;
48
49 typedef struct
50 {
51 gint event_type;
52 gchar *file;
53 gchar *other_file;
54 gint step;
55
56 /* Since different file monitor implementation has different capabilities,
57 * we cannot expect all implementations to report all kind of events without
58 * any loss. This 'optional' field is a bit mask used to mark events which
59 * may be lost under specific platforms.
60 */
61 Environment optional;
62 } RecordedEvent;
63
64 static void
free_recorded_event(RecordedEvent * event)65 free_recorded_event (RecordedEvent *event)
66 {
67 g_free (event->file);
68 g_free (event->other_file);
69 g_free (event);
70 }
71
72 typedef struct
73 {
74 GFile *file;
75 GFileMonitor *monitor;
76 GMainLoop *loop;
77 gint step;
78 GList *events;
79 GFileOutputStream *output_stream;
80 } TestData;
81
82 static void
output_event(const RecordedEvent * event)83 output_event (const RecordedEvent *event)
84 {
85 if (event->step >= 0)
86 g_test_message (">>>> step %d", event->step);
87 else
88 {
89 GTypeClass *class;
90
91 class = g_type_class_ref (g_type_from_name ("GFileMonitorEvent"));
92 g_test_message ("%s file=%s other_file=%s\n",
93 g_enum_get_value (G_ENUM_CLASS (class), event->event_type)->value_nick,
94 event->file,
95 event->other_file);
96 g_type_class_unref (class);
97 }
98 }
99
100 /* a placeholder for temp file names we don't want to compare */
101 static const gchar DONT_CARE[] = "";
102
103 static Environment
get_environment(GFileMonitor * monitor)104 get_environment (GFileMonitor *monitor)
105 {
106 if (g_str_equal (G_OBJECT_TYPE_NAME (monitor), "GInotifyFileMonitor"))
107 return INOTIFY;
108 if (g_str_equal (G_OBJECT_TYPE_NAME (monitor), "GKqueueFileMonitor"))
109 return KQUEUE;
110 return NONE;
111 }
112
113 static void
check_expected_events(RecordedEvent * expected,gsize n_expected,GList * recorded,Environment env)114 check_expected_events (RecordedEvent *expected,
115 gsize n_expected,
116 GList *recorded,
117 Environment env)
118 {
119 gint i, li;
120 GList *l;
121
122 for (i = 0, li = 0, l = recorded; i < n_expected && l != NULL;)
123 {
124 RecordedEvent *e1 = &expected[i];
125 RecordedEvent *e2 = l->data;
126 gboolean mismatch = TRUE;
127 gboolean l_extra_step = FALSE;
128
129 do
130 {
131 gboolean ignore_other_file = FALSE;
132
133 if (e1->step != e2->step)
134 break;
135
136 /* Kqueue isn't good at detecting file renaming, so
137 * G_FILE_MONITOR_WATCH_MOVES is mostly useless there. */
138 if (e1->event_type != e2->event_type && env & KQUEUE)
139 {
140 /* It is possible for kqueue file monitor to emit 'RENAMED' event,
141 * but most of the time it is reported as a 'DELETED' event and
142 * a 'CREATED' event. */
143 if (e1->event_type == G_FILE_MONITOR_EVENT_RENAMED)
144 {
145 RecordedEvent *e2_next;
146
147 if (l->next == NULL)
148 break;
149 e2_next = l->next->data;
150
151 if (e2->event_type != G_FILE_MONITOR_EVENT_DELETED)
152 break;
153 if (e2_next->event_type != G_FILE_MONITOR_EVENT_CREATED)
154 break;
155
156 if (e1->step != e2_next->step)
157 break;
158
159 if (e1->file != DONT_CARE &&
160 (g_strcmp0 (e1->file, e2->file) != 0 ||
161 e2->other_file != NULL))
162 break;
163
164 if (e1->other_file != DONT_CARE &&
165 (g_strcmp0 (e1->other_file, e2_next->file) != 0 ||
166 e2_next->other_file != NULL))
167 break;
168
169 l_extra_step = TRUE;
170 mismatch = FALSE;
171 break;
172 }
173 /* Kqueue won't report 'MOVED_IN' and 'MOVED_OUT' events. We set
174 * 'ignore_other_file' here to let the following code know that
175 * 'other_file' may not match. */
176 else if (e1->event_type == G_FILE_MONITOR_EVENT_MOVED_IN)
177 {
178 if (e2->event_type != G_FILE_MONITOR_EVENT_CREATED)
179 break;
180 ignore_other_file = TRUE;
181 }
182 else if (e1->event_type == G_FILE_MONITOR_EVENT_MOVED_OUT)
183 {
184 if (e2->event_type != G_FILE_MONITOR_EVENT_DELETED)
185 break;
186 ignore_other_file = TRUE;
187 }
188 else
189 break;
190 }
191
192 if (e1->file != DONT_CARE &&
193 g_strcmp0 (e1->file, e2->file) != 0)
194 break;
195
196 if (e1->other_file != DONT_CARE && !ignore_other_file &&
197 g_strcmp0 (e1->other_file, e2->other_file) != 0)
198 break;
199
200 mismatch = FALSE;
201 }
202 while (0);
203
204 if (mismatch)
205 {
206 /* Sometimes the emission of 'CHANGES_DONE_HINT' may be late because
207 * it depends on the ability of file monitor implementation to report
208 * 'CHANGES_DONE_HINT' itself. If the file monitor implementation
209 * doesn't report 'CHANGES_DONE_HINT' itself, it may be emitted by
210 * GLocalFileMonitor after a few seconds, which causes the event to
211 * mix with results from different steps. Since 'CHANGES_DONE_HINT'
212 * is just a hint, we don't require it to be reliable and we simply
213 * ignore unexpected 'CHANGES_DONE_HINT' events here. */
214 if (e1->event_type != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT &&
215 e2->event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
216 {
217 g_test_message ("Event CHANGES_DONE_HINT ignored at "
218 "expected index %d, recorded index %d", i, li);
219 li++, l = l->next;
220 continue;
221 }
222 /* If an event is marked as optional in the current environment and
223 * the event doesn't match, it means the expected event has lost. */
224 else if (env & e1->optional)
225 {
226 g_test_message ("Event %d at expected index %d skipped because "
227 "it is marked as optional", e1->event_type, i);
228 i++;
229 continue;
230 }
231 /* Run above checks under g_assert_* again to provide more useful
232 * error messages. Print the expected and actual events first. */
233 else
234 {
235 GList *l;
236 gsize j;
237
238 g_test_message ("Recorded events:");
239 for (l = recorded; l != NULL; l = l->next)
240 output_event ((RecordedEvent *) l->data);
241
242 g_test_message ("Expected events:");
243 for (j = 0; j < n_expected; j++)
244 output_event (&expected[j]);
245
246 g_assert_cmpint (e1->step, ==, e2->step);
247 g_assert_cmpint (e1->event_type, ==, e2->event_type);
248
249 if (e1->file != DONT_CARE)
250 g_assert_cmpstr (e1->file, ==, e2->file);
251
252 if (e1->other_file != DONT_CARE)
253 g_assert_cmpstr (e1->other_file, ==, e2->other_file);
254
255 g_assert_not_reached ();
256 }
257 }
258
259 i++, li++, l = l->next;
260 if (l_extra_step)
261 li++, l = l->next;
262 }
263
264 g_assert_cmpint (i, ==, n_expected);
265 g_assert_cmpint (li, ==, g_list_length (recorded));
266 }
267
268 static void
record_event(TestData * data,gint event_type,const gchar * file,const gchar * other_file,gint step)269 record_event (TestData *data,
270 gint event_type,
271 const gchar *file,
272 const gchar *other_file,
273 gint step)
274 {
275 RecordedEvent *event;
276
277 event = g_new0 (RecordedEvent, 1);
278 event->event_type = event_type;
279 event->file = g_strdup (file);
280 event->other_file = g_strdup (other_file);
281 event->step = step;
282
283 data->events = g_list_append (data->events, event);
284 }
285
286 static void
monitor_changed(GFileMonitor * monitor,GFile * file,GFile * other_file,GFileMonitorEvent event_type,gpointer user_data)287 monitor_changed (GFileMonitor *monitor,
288 GFile *file,
289 GFile *other_file,
290 GFileMonitorEvent event_type,
291 gpointer user_data)
292 {
293 TestData *data = user_data;
294 gchar *basename, *other_base;
295
296 basename = g_file_get_basename (file);
297 if (other_file)
298 other_base = g_file_get_basename (other_file);
299 else
300 other_base = NULL;
301
302 record_event (data, event_type, basename, other_base, -1);
303
304 g_free (basename);
305 g_free (other_base);
306 }
307
308 static gboolean
atomic_replace_step(gpointer user_data)309 atomic_replace_step (gpointer user_data)
310 {
311 TestData *data = user_data;
312 GError *error = NULL;
313
314 switch (data->step)
315 {
316 case 0:
317 record_event (data, -1, NULL, NULL, 0);
318 g_file_replace_contents (data->file, "step 0", 6, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, &error);
319 g_assert_no_error (error);
320 break;
321 case 1:
322 record_event (data, -1, NULL, NULL, 1);
323 g_file_replace_contents (data->file, "step 1", 6, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, &error);
324 g_assert_no_error (error);
325 break;
326 case 2:
327 record_event (data, -1, NULL, NULL, 2);
328 g_file_delete (data->file, NULL, NULL);
329 break;
330 case 3:
331 record_event (data, -1, NULL, NULL, 3);
332 g_main_loop_quit (data->loop);
333 return G_SOURCE_REMOVE;
334 }
335
336 data->step++;
337
338 return G_SOURCE_CONTINUE;
339 }
340
341 /* this is the output we expect from the above steps */
342 static RecordedEvent atomic_replace_output[] = {
343 { -1, NULL, NULL, 0, NONE },
344 { G_FILE_MONITOR_EVENT_CREATED, "atomic_replace_file", NULL, -1, NONE },
345 { G_FILE_MONITOR_EVENT_CHANGED, "atomic_replace_file", NULL, -1, KQUEUE },
346 { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "atomic_replace_file", NULL, -1, KQUEUE },
347 { -1, NULL, NULL, 1, NONE },
348 { G_FILE_MONITOR_EVENT_RENAMED, (gchar*)DONT_CARE, "atomic_replace_file", -1, NONE },
349 { -1, NULL, NULL, 2, NONE },
350 { G_FILE_MONITOR_EVENT_DELETED, "atomic_replace_file", NULL, -1, NONE },
351 { -1, NULL, NULL, 3, NONE }
352 };
353
354 static void
test_atomic_replace(Fixture * fixture,gconstpointer user_data)355 test_atomic_replace (Fixture *fixture,
356 gconstpointer user_data)
357 {
358 GError *error = NULL;
359 TestData data;
360
361 data.step = 0;
362 data.events = NULL;
363
364 data.file = g_file_get_child (fixture->tmp_dir, "atomic_replace_file");
365 g_file_delete (data.file, NULL, NULL);
366
367 data.monitor = g_file_monitor_file (data.file, G_FILE_MONITOR_WATCH_MOVES, NULL, &error);
368 g_assert_no_error (error);
369
370 g_test_message ("Using GFileMonitor %s", G_OBJECT_TYPE_NAME (data.monitor));
371
372 g_file_monitor_set_rate_limit (data.monitor, 200);
373 g_signal_connect (data.monitor, "changed", G_CALLBACK (monitor_changed), &data);
374
375 data.loop = g_main_loop_new (NULL, TRUE);
376
377 g_timeout_add (500, atomic_replace_step, &data);
378
379 g_main_loop_run (data.loop);
380
381 check_expected_events (atomic_replace_output,
382 G_N_ELEMENTS (atomic_replace_output),
383 data.events,
384 get_environment (data.monitor));
385
386 g_list_free_full (data.events, (GDestroyNotify)free_recorded_event);
387 g_main_loop_unref (data.loop);
388 g_object_unref (data.monitor);
389 g_object_unref (data.file);
390 }
391
392 static gboolean
change_step(gpointer user_data)393 change_step (gpointer user_data)
394 {
395 TestData *data = user_data;
396 GOutputStream *stream;
397 GError *error = NULL;
398 guint32 mode = 0660;
399
400 switch (data->step)
401 {
402 case 0:
403 record_event (data, -1, NULL, NULL, 0);
404 g_file_replace_contents (data->file, "step 0", 6, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, &error);
405 g_assert_no_error (error);
406 break;
407 case 1:
408 record_event (data, -1, NULL, NULL, 1);
409 stream = (GOutputStream *)g_file_append_to (data->file, G_FILE_CREATE_NONE, NULL, &error);
410 g_assert_no_error (error);
411 g_output_stream_write_all (stream, " step 1", 7, NULL, NULL, &error);
412 g_assert_no_error (error);
413 g_output_stream_close (stream, NULL, &error);
414 g_assert_no_error (error);
415 g_object_unref (stream);
416 break;
417 case 2:
418 record_event (data, -1, NULL, NULL, 2);
419 g_file_set_attribute (data->file,
420 G_FILE_ATTRIBUTE_UNIX_MODE,
421 G_FILE_ATTRIBUTE_TYPE_UINT32,
422 &mode,
423 G_FILE_QUERY_INFO_NONE,
424 NULL,
425 &error);
426 g_assert_no_error (error);
427 break;
428 case 3:
429 record_event (data, -1, NULL, NULL, 3);
430 g_file_delete (data->file, NULL, NULL);
431 break;
432 case 4:
433 record_event (data, -1, NULL, NULL, 4);
434 g_main_loop_quit (data->loop);
435 return G_SOURCE_REMOVE;
436 }
437
438 data->step++;
439
440 return G_SOURCE_CONTINUE;
441 }
442
443 /* this is the output we expect from the above steps */
444 static RecordedEvent change_output[] = {
445 { -1, NULL, NULL, 0, NONE },
446 { G_FILE_MONITOR_EVENT_CREATED, "change_file", NULL, -1, NONE },
447 { G_FILE_MONITOR_EVENT_CHANGED, "change_file", NULL, -1, KQUEUE },
448 { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "change_file", NULL, -1, KQUEUE },
449 { -1, NULL, NULL, 1, NONE },
450 { G_FILE_MONITOR_EVENT_CHANGED, "change_file", NULL, -1, NONE },
451 { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "change_file", NULL, -1, NONE },
452 { -1, NULL, NULL, 2, NONE },
453 { G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED, "change_file", NULL, -1, NONE },
454 { -1, NULL, NULL, 3, NONE },
455 { G_FILE_MONITOR_EVENT_DELETED, "change_file", NULL, -1, NONE },
456 { -1, NULL, NULL, 4, NONE }
457 };
458
459 static void
test_file_changes(Fixture * fixture,gconstpointer user_data)460 test_file_changes (Fixture *fixture,
461 gconstpointer user_data)
462 {
463 GError *error = NULL;
464 TestData data;
465
466 data.step = 0;
467 data.events = NULL;
468
469 data.file = g_file_get_child (fixture->tmp_dir, "change_file");
470 g_file_delete (data.file, NULL, NULL);
471
472 data.monitor = g_file_monitor_file (data.file, G_FILE_MONITOR_WATCH_MOVES, NULL, &error);
473 g_assert_no_error (error);
474
475 g_test_message ("Using GFileMonitor %s", G_OBJECT_TYPE_NAME (data.monitor));
476
477 g_file_monitor_set_rate_limit (data.monitor, 200);
478 g_signal_connect (data.monitor, "changed", G_CALLBACK (monitor_changed), &data);
479
480 data.loop = g_main_loop_new (NULL, TRUE);
481
482 g_timeout_add (500, change_step, &data);
483
484 g_main_loop_run (data.loop);
485
486 check_expected_events (change_output,
487 G_N_ELEMENTS (change_output),
488 data.events,
489 get_environment (data.monitor));
490
491 g_list_free_full (data.events, (GDestroyNotify)free_recorded_event);
492 g_main_loop_unref (data.loop);
493 g_object_unref (data.monitor);
494 g_object_unref (data.file);
495 }
496
497 static gboolean
dir_step(gpointer user_data)498 dir_step (gpointer user_data)
499 {
500 TestData *data = user_data;
501 GFile *parent, *file, *file2;
502 GError *error = NULL;
503
504 switch (data->step)
505 {
506 case 1:
507 record_event (data, -1, NULL, NULL, 1);
508 parent = g_file_get_parent (data->file);
509 file = g_file_get_child (parent, "dir_test_file");
510 g_file_replace_contents (file, "step 1", 6, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, &error);
511 g_assert_no_error (error);
512 g_object_unref (file);
513 g_object_unref (parent);
514 break;
515 case 2:
516 record_event (data, -1, NULL, NULL, 2);
517 parent = g_file_get_parent (data->file);
518 file = g_file_get_child (parent, "dir_test_file");
519 file2 = g_file_get_child (data->file, "dir_test_file");
520 g_file_move (file, file2, G_FILE_COPY_NONE, NULL, NULL, NULL, &error);
521 g_assert_no_error (error);
522 g_object_unref (file);
523 g_object_unref (file2);
524 g_object_unref (parent);
525 break;
526 case 3:
527 record_event (data, -1, NULL, NULL, 3);
528 file = g_file_get_child (data->file, "dir_test_file");
529 file2 = g_file_get_child (data->file, "dir_test_file2");
530 g_file_move (file, file2, G_FILE_COPY_NONE, NULL, NULL, NULL, &error);
531 g_assert_no_error (error);
532 g_object_unref (file);
533 g_object_unref (file2);
534 break;
535 case 4:
536 record_event (data, -1, NULL, NULL, 4);
537 parent = g_file_get_parent (data->file);
538 file = g_file_get_child (data->file, "dir_test_file2");
539 file2 = g_file_get_child (parent, "dir_test_file2");
540 g_file_move (file, file2, G_FILE_COPY_NONE, NULL, NULL, NULL, &error);
541 g_assert_no_error (error);
542 g_file_delete (file2, NULL, NULL);
543 g_object_unref (file);
544 g_object_unref (file2);
545 g_object_unref (parent);
546 break;
547 case 5:
548 record_event (data, -1, NULL, NULL, 5);
549 g_file_delete (data->file, NULL, NULL);
550 break;
551 case 6:
552 record_event (data, -1, NULL, NULL, 6);
553 g_main_loop_quit (data->loop);
554 return G_SOURCE_REMOVE;
555 }
556
557 data->step++;
558
559 return G_SOURCE_CONTINUE;
560 }
561
562 /* this is the output we expect from the above steps */
563 static RecordedEvent dir_output[] = {
564 { -1, NULL, NULL, 1, NONE },
565 { -1, NULL, NULL, 2, NONE },
566 { G_FILE_MONITOR_EVENT_MOVED_IN, "dir_test_file", NULL, -1, NONE },
567 { -1, NULL, NULL, 3, NONE },
568 { G_FILE_MONITOR_EVENT_RENAMED, "dir_test_file", "dir_test_file2", -1, NONE },
569 { -1, NULL, NULL, 4, NONE },
570 { G_FILE_MONITOR_EVENT_MOVED_OUT, "dir_test_file2", NULL, -1, NONE },
571 { -1, NULL, NULL, 5, NONE },
572 { G_FILE_MONITOR_EVENT_DELETED, "dir_monitor_test", NULL, -1, NONE },
573 { -1, NULL, NULL, 6, NONE }
574 };
575
576 static void
test_dir_monitor(Fixture * fixture,gconstpointer user_data)577 test_dir_monitor (Fixture *fixture,
578 gconstpointer user_data)
579 {
580 GError *error = NULL;
581 TestData data;
582
583 data.step = 0;
584 data.events = NULL;
585
586 data.file = g_file_get_child (fixture->tmp_dir, "dir_monitor_test");
587 g_file_delete (data.file, NULL, NULL);
588 g_file_make_directory (data.file, NULL, &error);
589
590 data.monitor = g_file_monitor_directory (data.file, G_FILE_MONITOR_WATCH_MOVES, NULL, &error);
591 g_assert_no_error (error);
592
593 g_test_message ("Using GFileMonitor %s", G_OBJECT_TYPE_NAME (data.monitor));
594
595 g_file_monitor_set_rate_limit (data.monitor, 200);
596 g_signal_connect (data.monitor, "changed", G_CALLBACK (monitor_changed), &data);
597
598 data.loop = g_main_loop_new (NULL, TRUE);
599
600 g_timeout_add (500, dir_step, &data);
601
602 g_main_loop_run (data.loop);
603
604 check_expected_events (dir_output,
605 G_N_ELEMENTS (dir_output),
606 data.events,
607 get_environment (data.monitor));
608
609 g_list_free_full (data.events, (GDestroyNotify)free_recorded_event);
610 g_main_loop_unref (data.loop);
611 g_object_unref (data.monitor);
612 g_object_unref (data.file);
613 }
614
615 static gboolean
nodir_step(gpointer user_data)616 nodir_step (gpointer user_data)
617 {
618 TestData *data = user_data;
619 GFile *parent;
620 GError *error = NULL;
621
622 switch (data->step)
623 {
624 case 0:
625 record_event (data, -1, NULL, NULL, 0);
626 parent = g_file_get_parent (data->file);
627 g_file_make_directory (parent, NULL, &error);
628 g_assert_no_error (error);
629 g_object_unref (parent);
630 break;
631 case 1:
632 record_event (data, -1, NULL, NULL, 1);
633 g_file_replace_contents (data->file, "step 1", 6, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, &error);
634 g_assert_no_error (error);
635 break;
636 case 2:
637 record_event (data, -1, NULL, NULL, 2);
638 g_file_delete (data->file, NULL, &error);
639 g_assert_no_error (error);
640 break;
641 case 3:
642 record_event (data, -1, NULL, NULL, 3);
643 parent = g_file_get_parent (data->file);
644 g_file_delete (parent, NULL, &error);
645 g_assert_no_error (error);
646 g_object_unref (parent);
647 break;
648 case 4:
649 record_event (data, -1, NULL, NULL, 4);
650 g_main_loop_quit (data->loop);
651 return G_SOURCE_REMOVE;
652 }
653
654 data->step++;
655
656 return G_SOURCE_CONTINUE;
657 }
658
659 static RecordedEvent nodir_output[] = {
660 { -1, NULL, NULL, 0, NONE },
661 { G_FILE_MONITOR_EVENT_CREATED, "nosuchfile", NULL, -1, KQUEUE },
662 { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "nosuchfile", NULL, -1, KQUEUE },
663 { -1, NULL, NULL, 1, NONE },
664 { G_FILE_MONITOR_EVENT_CREATED, "nosuchfile", NULL, -1, NONE },
665 { G_FILE_MONITOR_EVENT_CHANGED, "nosuchfile", NULL, -1, KQUEUE },
666 { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "nosuchfile", NULL, -1, KQUEUE },
667 { -1, NULL, NULL, 2, NONE },
668 { G_FILE_MONITOR_EVENT_DELETED, "nosuchfile", NULL, -1, NONE },
669 { -1, NULL, NULL, 3, NONE },
670 { -1, NULL, NULL, 4, NONE }
671 };
672
673 static void
test_dir_non_existent(Fixture * fixture,gconstpointer user_data)674 test_dir_non_existent (Fixture *fixture,
675 gconstpointer user_data)
676 {
677 TestData data;
678 GError *error = NULL;
679
680 data.step = 0;
681 data.events = NULL;
682
683 data.file = g_file_get_child (fixture->tmp_dir, "nosuchdir/nosuchfile");
684 data.monitor = g_file_monitor_file (data.file, G_FILE_MONITOR_WATCH_MOVES, NULL, &error);
685 g_assert_no_error (error);
686
687 g_test_message ("Using GFileMonitor %s", G_OBJECT_TYPE_NAME (data.monitor));
688
689 g_file_monitor_set_rate_limit (data.monitor, 200);
690 g_signal_connect (data.monitor, "changed", G_CALLBACK (monitor_changed), &data);
691
692 data.loop = g_main_loop_new (NULL, TRUE);
693
694 /* we need a long timeout here, since the inotify implementation only scans
695 * for missing files every 4 seconds.
696 */
697 g_timeout_add (5000, nodir_step, &data);
698
699 g_main_loop_run (data.loop);
700
701 check_expected_events (nodir_output,
702 G_N_ELEMENTS (nodir_output),
703 data.events,
704 get_environment (data.monitor));
705
706 g_list_free_full (data.events, (GDestroyNotify)free_recorded_event);
707 g_main_loop_unref (data.loop);
708 g_object_unref (data.monitor);
709 g_object_unref (data.file);
710 }
711
712 static gboolean
cross_dir_step(gpointer user_data)713 cross_dir_step (gpointer user_data)
714 {
715 TestData *data = user_data;
716 GFile *file, *file2;
717 GError *error = NULL;
718
719 switch (data[0].step)
720 {
721 case 0:
722 record_event (&data[0], -1, NULL, NULL, 0);
723 record_event (&data[1], -1, NULL, NULL, 0);
724 file = g_file_get_child (data[1].file, "a");
725 g_file_replace_contents (file, "step 0", 6, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, &error);
726 g_assert_no_error (error);
727 g_object_unref (file);
728 break;
729 case 1:
730 record_event (&data[0], -1, NULL, NULL, 1);
731 record_event (&data[1], -1, NULL, NULL, 1);
732 file = g_file_get_child (data[1].file, "a");
733 file2 = g_file_get_child (data[0].file, "a");
734 g_file_move (file, file2, 0, NULL, NULL, NULL, &error);
735 g_assert_no_error (error);
736 g_object_unref (file);
737 g_object_unref (file2);
738 break;
739 case 2:
740 record_event (&data[0], -1, NULL, NULL, 2);
741 record_event (&data[1], -1, NULL, NULL, 2);
742 file2 = g_file_get_child (data[0].file, "a");
743 g_file_delete (file2, NULL, NULL);
744 g_file_delete (data[0].file, NULL, NULL);
745 g_file_delete (data[1].file, NULL, NULL);
746 g_object_unref (file2);
747 break;
748 case 3:
749 record_event (&data[0], -1, NULL, NULL, 3);
750 record_event (&data[1], -1, NULL, NULL, 3);
751 g_main_loop_quit (data->loop);
752 return G_SOURCE_REMOVE;
753 }
754
755 data->step++;
756
757 return G_SOURCE_CONTINUE;
758 }
759
760 static RecordedEvent cross_dir_a_output[] = {
761 { -1, NULL, NULL, 0, NONE },
762 { -1, NULL, NULL, 1, NONE },
763 { G_FILE_MONITOR_EVENT_CREATED, "a", NULL, -1, NONE },
764 { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "a", NULL, -1, KQUEUE },
765 { -1, NULL, NULL, 2, NONE },
766 { G_FILE_MONITOR_EVENT_DELETED, "a", NULL, -1, NONE },
767 { G_FILE_MONITOR_EVENT_DELETED, "cross_dir_a", NULL, -1, NONE },
768 { -1, NULL, NULL, 3, NONE },
769 };
770
771 static RecordedEvent cross_dir_b_output[] = {
772 { -1, NULL, NULL, 0, NONE },
773 { G_FILE_MONITOR_EVENT_CREATED, "a", NULL, -1, NONE },
774 { G_FILE_MONITOR_EVENT_CHANGED, "a", NULL, -1, KQUEUE },
775 { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "a", NULL, -1, KQUEUE },
776 { -1, NULL, NULL, 1, NONE },
777 { G_FILE_MONITOR_EVENT_MOVED_OUT, "a", "a", -1, NONE },
778 { -1, NULL, NULL, 2, NONE },
779 { G_FILE_MONITOR_EVENT_DELETED, "cross_dir_b", NULL, -1, NONE },
780 { -1, NULL, NULL, 3, NONE },
781 };
782 static void
test_cross_dir_moves(Fixture * fixture,gconstpointer user_data)783 test_cross_dir_moves (Fixture *fixture,
784 gconstpointer user_data)
785 {
786 GError *error = NULL;
787 TestData data[2];
788
789 data[0].step = 0;
790 data[0].events = NULL;
791
792 data[0].file = g_file_get_child (fixture->tmp_dir, "cross_dir_a");
793 g_file_delete (data[0].file, NULL, NULL);
794 g_file_make_directory (data[0].file, NULL, &error);
795
796 data[0].monitor = g_file_monitor_directory (data[0].file, 0, NULL, &error);
797 g_assert_no_error (error);
798
799 g_test_message ("Using GFileMonitor 0 %s", G_OBJECT_TYPE_NAME (data[0].monitor));
800
801 g_file_monitor_set_rate_limit (data[0].monitor, 200);
802 g_signal_connect (data[0].monitor, "changed", G_CALLBACK (monitor_changed), &data[0]);
803
804 data[1].step = 0;
805 data[1].events = NULL;
806
807 data[1].file = g_file_get_child (fixture->tmp_dir, "cross_dir_b");
808 g_file_delete (data[1].file, NULL, NULL);
809 g_file_make_directory (data[1].file, NULL, &error);
810
811 data[1].monitor = g_file_monitor_directory (data[1].file, G_FILE_MONITOR_WATCH_MOVES, NULL, &error);
812 g_assert_no_error (error);
813
814 g_test_message ("Using GFileMonitor 1 %s", G_OBJECT_TYPE_NAME (data[1].monitor));
815
816 g_file_monitor_set_rate_limit (data[1].monitor, 200);
817 g_signal_connect (data[1].monitor, "changed", G_CALLBACK (monitor_changed), &data[1]);
818
819 data[0].loop = g_main_loop_new (NULL, TRUE);
820
821 g_timeout_add (500, cross_dir_step, data);
822
823 g_main_loop_run (data[0].loop);
824
825 check_expected_events (cross_dir_a_output,
826 G_N_ELEMENTS (cross_dir_a_output),
827 data[0].events,
828 get_environment (data[0].monitor));
829 check_expected_events (cross_dir_b_output,
830 G_N_ELEMENTS (cross_dir_b_output),
831 data[1].events,
832 get_environment (data[1].monitor));
833
834 g_list_free_full (data[0].events, (GDestroyNotify)free_recorded_event);
835 g_main_loop_unref (data[0].loop);
836 g_object_unref (data[0].monitor);
837 g_object_unref (data[0].file);
838
839 g_list_free_full (data[1].events, (GDestroyNotify)free_recorded_event);
840 g_object_unref (data[1].monitor);
841 g_object_unref (data[1].file);
842 }
843
844 static gboolean
file_hard_links_step(gpointer user_data)845 file_hard_links_step (gpointer user_data)
846 {
847 gboolean retval = G_SOURCE_CONTINUE;
848 TestData *data = user_data;
849 GError *error = NULL;
850
851 gchar *filename = g_file_get_path (data->file);
852 gchar *hard_link_name = g_strdup_printf ("%s2", filename);
853 GFile *hard_link_file = g_file_new_for_path (hard_link_name);
854
855 switch (data->step)
856 {
857 case 0:
858 record_event (data, -1, NULL, NULL, 0);
859 g_output_stream_write_all (G_OUTPUT_STREAM (data->output_stream),
860 "hello, step 0", 13, NULL, NULL, &error);
861 g_assert_no_error (error);
862 g_output_stream_close (G_OUTPUT_STREAM (data->output_stream), NULL, &error);
863 g_assert_no_error (error);
864 break;
865 case 1:
866 record_event (data, -1, NULL, NULL, 1);
867 g_file_replace_contents (data->file, "step 1", 6, NULL, FALSE,
868 G_FILE_CREATE_NONE, NULL, NULL, &error);
869 g_assert_no_error (error);
870 break;
871 case 2:
872 record_event (data, -1, NULL, NULL, 2);
873 #ifdef HAVE_LINK
874 if (link (filename, hard_link_name) < 0)
875 {
876 g_error ("link(%s, %s) failed: %s", filename, hard_link_name, g_strerror (errno));
877 }
878 #endif /* HAVE_LINK */
879 break;
880 case 3:
881 record_event (data, -1, NULL, NULL, 3);
882 #ifdef HAVE_LINK
883 {
884 GOutputStream *hard_link_stream = NULL;
885
886 /* Deliberately don’t do an atomic swap on the hard-linked file. */
887 hard_link_stream = G_OUTPUT_STREAM (g_file_append_to (hard_link_file,
888 G_FILE_CREATE_NONE,
889 NULL, &error));
890 g_assert_no_error (error);
891 g_output_stream_write_all (hard_link_stream, " step 3", 7, NULL, NULL, &error);
892 g_assert_no_error (error);
893 g_output_stream_close (hard_link_stream, NULL, &error);
894 g_assert_no_error (error);
895 g_object_unref (hard_link_stream);
896 }
897 #endif /* HAVE_LINK */
898 break;
899 case 4:
900 record_event (data, -1, NULL, NULL, 4);
901 g_file_delete (data->file, NULL, &error);
902 g_assert_no_error (error);
903 break;
904 case 5:
905 record_event (data, -1, NULL, NULL, 5);
906 #ifdef HAVE_LINK
907 g_file_delete (hard_link_file, NULL, &error);
908 g_assert_no_error (error);
909 #endif /* HAVE_LINK */
910 break;
911 case 6:
912 record_event (data, -1, NULL, NULL, 6);
913 g_main_loop_quit (data->loop);
914 retval = G_SOURCE_REMOVE;
915 break;
916 }
917
918 if (retval != G_SOURCE_REMOVE)
919 data->step++;
920
921 g_object_unref (hard_link_file);
922 g_free (hard_link_name);
923 g_free (filename);
924
925 return retval;
926 }
927
928 static RecordedEvent file_hard_links_output[] = {
929 { -1, NULL, NULL, 0, NONE },
930 { G_FILE_MONITOR_EVENT_CHANGED, "testfilemonitor.db", NULL, -1, NONE },
931 { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "testfilemonitor.db", NULL, -1, NONE },
932 { -1, NULL, NULL, 1, NONE },
933 { G_FILE_MONITOR_EVENT_RENAMED, (gchar*)DONT_CARE /* .goutputstream-XXXXXX */, "testfilemonitor.db", -1, NONE },
934 { -1, NULL, NULL, 2, NONE },
935 { -1, NULL, NULL, 3, NONE },
936 /* Kqueue is based on file descriptors. You can get events from all hard
937 * links by just monitoring one open file descriptor, and it is not possible
938 * to know whether it is done on the file name we use to open the file. Since
939 * the hard link count of 'testfilemonitor.db' is 2, it is expected to see
940 * two 'DELETED' events reported here. You have to call 'unlink' twice on
941 * different file names to remove 'testfilemonitor.db' from the file system,
942 * and each 'unlink' call generates a 'DELETED' event. */
943 { G_FILE_MONITOR_EVENT_CHANGED, "testfilemonitor.db", NULL, -1, INOTIFY },
944 { -1, NULL, NULL, 4, NONE },
945 { G_FILE_MONITOR_EVENT_DELETED, "testfilemonitor.db", NULL, -1, NONE },
946 { -1, NULL, NULL, 5, NONE },
947 { G_FILE_MONITOR_EVENT_DELETED, "testfilemonitor.db", NULL, -1, INOTIFY },
948 { -1, NULL, NULL, 6, NONE },
949 };
950
951 static void
test_file_hard_links(Fixture * fixture,gconstpointer user_data)952 test_file_hard_links (Fixture *fixture,
953 gconstpointer user_data)
954 {
955 GError *error = NULL;
956 TestData data;
957
958 g_test_bug ("755721");
959
960 #ifdef HAVE_LINK
961 g_test_message ("Running with hard link tests");
962 #else /* if !HAVE_LINK */
963 g_test_message ("Running without hard link tests");
964 #endif /* !HAVE_LINK */
965
966 data.step = 0;
967 data.events = NULL;
968
969 /* Create a file which exists and is not a directory. */
970 data.file = g_file_get_child (fixture->tmp_dir, "testfilemonitor.db");
971 data.output_stream = g_file_replace (data.file, NULL, FALSE,
972 G_FILE_CREATE_NONE, NULL, &error);
973 g_assert_no_error (error);
974
975 /* Monitor it. Creating the monitor should not crash (bug #755721). */
976 data.monitor = g_file_monitor_file (data.file,
977 G_FILE_MONITOR_WATCH_MOUNTS |
978 G_FILE_MONITOR_WATCH_MOVES |
979 G_FILE_MONITOR_WATCH_HARD_LINKS,
980 NULL,
981 &error);
982 g_assert_no_error (error);
983 g_assert_nonnull (data.monitor);
984
985 g_test_message ("Using GFileMonitor %s", G_OBJECT_TYPE_NAME (data.monitor));
986
987 /* Change the file a bit. */
988 g_file_monitor_set_rate_limit (data.monitor, 200);
989 g_signal_connect (data.monitor, "changed", (GCallback) monitor_changed, &data);
990
991 data.loop = g_main_loop_new (NULL, TRUE);
992 g_timeout_add (500, file_hard_links_step, &data);
993 g_main_loop_run (data.loop);
994
995 check_expected_events (file_hard_links_output,
996 G_N_ELEMENTS (file_hard_links_output),
997 data.events,
998 get_environment (data.monitor));
999
1000 g_list_free_full (data.events, (GDestroyNotify) free_recorded_event);
1001 g_main_loop_unref (data.loop);
1002 g_object_unref (data.monitor);
1003 g_object_unref (data.file);
1004 g_object_unref (data.output_stream);
1005 }
1006
1007 int
main(int argc,char * argv[])1008 main (int argc, char *argv[])
1009 {
1010 g_test_init (&argc, &argv, NULL);
1011
1012 g_test_bug_base ("https://bugzilla.gnome.org/show_bug.cgi?id=");
1013
1014 g_test_add ("/monitor/atomic-replace", Fixture, NULL, setup, test_atomic_replace, teardown);
1015 g_test_add ("/monitor/file-changes", Fixture, NULL, setup, test_file_changes, teardown);
1016 g_test_add ("/monitor/dir-monitor", Fixture, NULL, setup, test_dir_monitor, teardown);
1017 g_test_add ("/monitor/dir-not-existent", Fixture, NULL, setup, test_dir_non_existent, teardown);
1018 g_test_add ("/monitor/cross-dir-moves", Fixture, NULL, setup, test_cross_dir_moves, teardown);
1019 g_test_add ("/monitor/file/hard-links", Fixture, NULL, setup, test_file_hard_links, teardown);
1020
1021 return g_test_run ();
1022 }
1023