• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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