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

source code of gtk/subprojects/glib/gio/tests/testfilemonitor.c