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 |
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 (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 | |
34 | static void |
35 | teardown (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 | |
45 | typedef enum { |
46 | NONE = 0, |
47 | INOTIFY = (1 << 1), |
48 | KQUEUE = (1 << 2) |
49 | } Environment; |
50 | |
51 | typedef 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 | |
66 | static void |
67 | free_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 | |
74 | typedef struct |
75 | { |
76 | GFile *file; |
77 | GFileMonitor *monitor; |
78 | GMainLoop *loop; |
79 | gint step; |
80 | GList *events; |
81 | GFileOutputStream *output_stream; |
82 | } TestData; |
83 | |
84 | static void |
85 | output_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 */ |
103 | static const gchar DONT_CARE[] = "" ; |
104 | |
105 | static Environment |
106 | get_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 | |
115 | static void |
116 | check_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 = 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 | |
270 | static void |
271 | record_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 | |
288 | static void |
289 | monitor_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 | |
310 | static gboolean |
311 | atomic_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 */ |
344 | static 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 | |
356 | static void |
357 | test_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 | |
394 | static gboolean |
395 | change_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 */ |
446 | static 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 | |
461 | static void |
462 | test_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 | |
499 | static gboolean |
500 | dir_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 */ |
565 | static 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 | |
578 | static void |
579 | test_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 | |
617 | static gboolean |
618 | nodir_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 | |
661 | static 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 | |
675 | static void |
676 | test_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 | |
714 | static gboolean |
715 | cross_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 | |
762 | static 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 | |
773 | static 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 | }; |
784 | static void |
785 | test_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 | |
846 | static gboolean |
847 | file_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 | |
930 | static 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 | |
953 | static void |
954 | test_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 | |
1009 | int |
1010 | main (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 | |