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