Avoid use of link_whole in the gsettings backend
[dconf.git] / tests / engine.c
blobaa1db1c239ce78996f0baf5007753808adb36093
1 #define _GNU_SOURCE
3 #define GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_2_36 /* Suppress deprecation warnings */
5 #include "../engine/dconf-engine.h"
6 #include "../engine/dconf-engine-profile.h"
7 #include "../common/dconf-enums.h"
8 #include "dconf-mock.h"
10 #include <glib/gstdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <stdio.h>
14 #include <dlfcn.h>
15 #include <math.h>
17 /* Interpose to catch fopen("/etc/dconf/profile/user") */
18 static const gchar *filename_to_replace;
19 static const gchar *filename_to_replace_it_with;
21 FILE *
22 fopen (const char *filename,
23 const char *mode)
25 static FILE * (*real_fopen) (const char *, const char *);
27 if (!real_fopen)
28 real_fopen = dlsym (RTLD_NEXT, "fopen");
30 if (filename_to_replace && g_str_equal (filename, filename_to_replace))
32 /* Crash if this file was unexpectedly opened */
33 g_assert (filename_to_replace_it_with != NULL);
34 filename = filename_to_replace_it_with;
37 return (* real_fopen) (filename, mode);
40 static GThread *main_thread;
41 static GString *change_log;
43 void
44 dconf_engine_change_notify (DConfEngine *engine,
45 const gchar *prefix,
46 const gchar * const *changes,
47 const gchar *tag,
48 gboolean is_writability,
49 gpointer origin_tag,
50 gpointer user_data)
52 gchar *joined;
54 if (!change_log)
55 return;
57 if (is_writability)
58 g_string_append (change_log, "w:");
60 joined = g_strjoinv (",", (gchar **) changes);
61 g_string_append_printf (change_log, "%s:%d:%s:%s;",
62 prefix, g_strv_length ((gchar **) changes), joined,
63 tag ? tag : "nil");
64 g_free (joined);
67 static void
68 verify_and_free (DConfEngineSource **sources,
69 gint n_sources,
70 const gchar * const *expected_names,
71 gint n_expected)
73 gint i;
75 g_assert_cmpint (n_sources, ==, n_expected);
77 g_assert ((sources == NULL) == (n_sources == 0));
79 for (i = 0; i < n_sources; i++)
81 g_assert_cmpstr (sources[i]->name, ==, expected_names[i]);
82 dconf_engine_source_free (sources[i]);
85 g_free (sources);
88 static void
89 test_five_times (const gchar *filename,
90 gint n_expected,
91 ...)
93 const gchar **expected_names;
94 DConfEngineSource **sources;
95 gint n_sources;
96 va_list ap;
97 gint i;
99 expected_names = g_new (const gchar *, n_expected);
100 va_start (ap, n_expected);
101 for (i = 0; i < n_expected; i++)
102 expected_names[i] = va_arg (ap, const gchar *);
103 va_end (ap);
105 /* first try by supplying the profile filename via the API */
106 g_assert (g_getenv ("DCONF_PROFILE") == NULL);
107 g_assert (filename_to_replace == NULL);
108 sources = dconf_engine_profile_open (filename, &n_sources);
109 verify_and_free (sources, n_sources, expected_names, n_expected);
111 /* next try supplying it via the environment */
112 g_setenv ("DCONF_PROFILE", filename, TRUE);
113 g_assert (filename_to_replace == NULL);
114 sources = dconf_engine_profile_open (NULL, &n_sources);
115 verify_and_free (sources, n_sources, expected_names, n_expected);
116 g_unsetenv ("DCONF_PROFILE");
118 /* next try supplying a profile name via API and intercepting fopen */
119 filename_to_replace = "/etc/dconf/profile/myprofile";
120 filename_to_replace_it_with = filename;
121 g_assert (g_getenv ("DCONF_PROFILE") == NULL);
122 sources = dconf_engine_profile_open ("myprofile", &n_sources);
123 verify_and_free (sources, n_sources, expected_names, n_expected);
124 filename_to_replace = NULL;
126 /* next try the same, via the environment */
127 g_setenv ("DCONF_PROFILE", "myprofile", TRUE);
128 filename_to_replace = "/etc/dconf/profile/myprofile";
129 filename_to_replace_it_with = filename;
130 sources = dconf_engine_profile_open (NULL, &n_sources);
131 verify_and_free (sources, n_sources, expected_names, n_expected);
132 g_unsetenv ("DCONF_PROFILE");
133 filename_to_replace = NULL;
135 /* next try to have dconf pick it up as the default user profile */
136 filename_to_replace = "/etc/dconf/profile/user";
137 filename_to_replace_it_with = filename;
138 g_assert (g_getenv ("DCONF_PROFILE") == NULL);
139 sources = dconf_engine_profile_open (NULL, &n_sources);
140 verify_and_free (sources, n_sources, expected_names, n_expected);
141 filename_to_replace = NULL;
143 filename_to_replace_it_with = NULL;
144 g_free (expected_names);
147 static void
148 test_profile_parser (void)
150 DConfEngineSource **sources;
151 gint n_sources;
153 if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
155 g_log_set_always_fatal (G_LOG_LEVEL_ERROR);
157 sources = dconf_engine_profile_open (SRCDIR "/profile/this-file-does-not-exist", &n_sources);
158 g_assert_cmpint (n_sources, ==, 0);
159 g_assert (sources == NULL);
160 exit (0);
162 g_test_trap_assert_passed ();
163 g_test_trap_assert_stderr ("*WARNING*: unable to open named profile*");
165 if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
167 g_log_set_always_fatal (G_LOG_LEVEL_ERROR);
169 sources = dconf_engine_profile_open (SRCDIR "/profile/broken-profile", &n_sources);
170 g_assert_cmpint (n_sources, ==, 0);
171 g_assert (sources == NULL);
172 exit (0);
174 g_test_trap_assert_passed ();
175 g_test_trap_assert_stderr ("*WARNING*: unknown dconf database*unknown dconf database*");
177 if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
179 g_log_set_always_fatal (G_LOG_LEVEL_ERROR);
181 sources = dconf_engine_profile_open (SRCDIR "/profile/gdm", &n_sources);
182 g_assert_cmpint (n_sources, ==, 0);
183 g_assert (sources == NULL);
184 exit (0);
186 g_test_trap_assert_passed ();
187 g_test_trap_assert_stderr ("*WARNING*: unknown dconf database*unknown dconf database*");
189 test_five_times (SRCDIR "/profile/empty-profile", 0);
190 test_five_times (SRCDIR "/profile/test-profile", 1, "test");
191 test_five_times (SRCDIR "/profile/colourful", 4,
192 "user",
193 "other",
194 "verylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongname",
195 "nonewline");
196 test_five_times (SRCDIR "/profile/dos", 2, "user", "site");
197 test_five_times (SRCDIR "/profile/no-newline-longline", 0);
198 test_five_times (SRCDIR "/profile/many-sources", 10,
199 "user", "local", "room", "floor", "building",
200 "site", "region", "division", "country", "global");
202 /* finally, test that we get the default profile if the user profile
203 * file cannot be located and we do not specify another profile.
205 filename_to_replace = "/etc/dconf/profile/user";
206 filename_to_replace_it_with = SRCDIR "/profile/this-file-does-not-exist";
207 g_assert (g_getenv ("DCONF_PROFILE") == NULL);
208 sources = dconf_engine_profile_open (NULL, &n_sources);
209 filename_to_replace = NULL;
210 g_assert_cmpint (n_sources, ==, 1);
211 g_assert_cmpstr (sources[0]->name, ==, "user");
212 dconf_engine_source_free (sources[0]);
213 g_free (sources);
215 dconf_mock_shm_reset ();
218 static gpointer
219 test_signal_threadsafety_worker (gpointer user_data)
221 gint *finished = user_data;
222 gint i;
224 for (i = 0; i < 20000; i++)
226 DConfEngine *engine;
228 engine = dconf_engine_new (NULL, NULL, NULL);
229 dconf_engine_unref (engine);
232 g_atomic_int_inc (finished);
234 return NULL;
237 static void
238 test_signal_threadsafety (void)
240 #define N_WORKERS 4
241 GVariant *parameters;
242 gint finished = 0;
243 gint i;
245 parameters = g_variant_new_parsed ("('/test/key', [''], 'tag')");
246 g_variant_ref_sink (parameters);
248 for (i = 0; i < N_WORKERS; i++)
249 g_thread_unref (g_thread_new ("testcase worker", test_signal_threadsafety_worker, &finished));
251 while (g_atomic_int_get (&finished) < N_WORKERS)
252 dconf_engine_handle_dbus_signal (G_BUS_TYPE_SESSION,
253 ":1.2.3",
254 "/ca/desrt/dconf/Writer/user",
255 "Notify", parameters);
256 g_variant_unref (parameters);
258 dconf_mock_shm_reset ();
261 static void
262 test_user_source (void)
264 DConfEngineSource *source;
265 GvdbTable *table;
266 GvdbTable *locks;
267 gboolean reopened;
269 /* Create the source from a clean slate */
270 source = dconf_engine_source_new ("user-db:user");
271 g_assert (source != NULL);
272 g_assert (source->values == NULL);
273 g_assert (source->locks == NULL);
275 /* Refresh it the first time.
276 * This should cause it to open the shm.
277 * FALSE should be returned because there is no database file.
279 reopened = dconf_engine_source_refresh (source);
280 g_assert (!reopened);
281 dconf_mock_shm_assert_log ("open user;");
283 /* Try to refresh it. There must be no IO at this point. */
284 reopened = dconf_engine_source_refresh (source);
285 g_assert (!reopened);
286 dconf_mock_shm_assert_log ("");
288 /* Add a real database. */
289 table = dconf_mock_gvdb_table_new ();
290 dconf_mock_gvdb_table_insert (table, "/values/int32", g_variant_new_int32 (123456), NULL);
291 dconf_mock_gvdb_install ("/HOME/.config/dconf/user", table);
293 /* Try to refresh it again.
294 * Because we didn't flag the change there must still be no IO.
296 reopened = dconf_engine_source_refresh (source);
297 g_assert (!reopened);
298 g_assert (source->values == NULL);
299 g_assert (source->locks == NULL);
300 dconf_mock_shm_assert_log ("");
302 /* Now flag it and reopen. */
303 dconf_mock_shm_flag ("user");
304 reopened = dconf_engine_source_refresh (source);
305 g_assert (reopened);
306 g_assert (source->values != NULL);
307 g_assert (source->locks == NULL);
308 g_assert (gvdb_table_has_value (source->values, "/values/int32"));
309 dconf_mock_shm_assert_log ("close;open user;");
311 /* Do it again -- should get the same result, after some IO */
312 dconf_mock_shm_flag ("user");
313 reopened = dconf_engine_source_refresh (source);
314 g_assert (reopened);
315 g_assert (source->values != NULL);
316 g_assert (source->locks == NULL);
317 dconf_mock_shm_assert_log ("close;open user;");
319 /* "Delete" the gvdb and make sure dconf notices after a flag */
320 dconf_mock_gvdb_install ("/HOME/.config/dconf/user", NULL);
321 dconf_mock_shm_flag ("user");
322 reopened = dconf_engine_source_refresh (source);
323 g_assert (reopened);
324 g_assert (source->values == NULL);
325 g_assert (source->locks == NULL);
326 dconf_mock_shm_assert_log ("close;open user;");
328 /* Add a gvdb with a lock */
329 table = dconf_mock_gvdb_table_new ();
330 locks = dconf_mock_gvdb_table_new ();
331 dconf_mock_gvdb_table_insert (table, "/values/int32", g_variant_new_int32 (123456), NULL);
332 dconf_mock_gvdb_table_insert (locks, "/values/int32", g_variant_new_boolean (TRUE), NULL);
333 dconf_mock_gvdb_table_insert (table, ".locks", NULL, locks);
334 dconf_mock_gvdb_install ("/HOME/.config/dconf/user", table);
336 /* Reopen and check if we have the lock */
337 dconf_mock_shm_flag ("user");
338 reopened = dconf_engine_source_refresh (source);
339 g_assert (reopened);
340 g_assert (source->values != NULL);
341 g_assert (source->locks != NULL);
342 g_assert (gvdb_table_has_value (source->values, "/values/int32"));
343 g_assert (gvdb_table_has_value (source->locks, "/values/int32"));
344 dconf_mock_shm_assert_log ("close;open user;");
346 /* Reopen one last time */
347 dconf_mock_shm_flag ("user");
348 reopened = dconf_engine_source_refresh (source);
349 g_assert (reopened);
350 g_assert (source->values != NULL);
351 g_assert (source->locks != NULL);
352 dconf_mock_shm_assert_log ("close;open user;");
354 dconf_engine_source_free (source);
355 dconf_mock_shm_assert_log ("close;");
357 dconf_mock_gvdb_install ("/HOME/.config/dconf/user", NULL);
358 dconf_mock_shm_reset ();
361 static void
362 test_file_source (void)
364 DConfEngineSource *source;
365 gboolean reopened;
366 GvdbTable *table;
367 GVariant *value;
369 source = dconf_engine_source_new ("file-db:/path/to/db");
370 g_assert (source != NULL);
371 g_assert (source->values == NULL);
372 g_assert (source->locks == NULL);
373 g_test_expect_message ("dconf", G_LOG_LEVEL_WARNING, "*unable to open file '/path/to/db'*");
374 reopened = dconf_engine_source_refresh (source);
375 g_assert (source->values == NULL);
376 g_assert (source->locks == NULL);
377 dconf_engine_source_free (source);
379 source = dconf_engine_source_new ("file-db:/path/to/db");
380 g_assert (source != NULL);
381 g_assert (source->values == NULL);
382 g_assert (source->locks == NULL);
384 table = dconf_mock_gvdb_table_new ();
385 dconf_mock_gvdb_table_insert (table, "/value", g_variant_new_string ("first file"), NULL);
386 dconf_mock_gvdb_install ("/path/to/db", table);
388 reopened = dconf_engine_source_refresh (source);
389 g_assert (reopened);
390 g_assert (source->values);
391 g_assert (source->locks == NULL);
392 value = gvdb_table_get_value (source->values, "/value");
393 g_assert_cmpstr (g_variant_get_string (value, NULL), ==, "first file");
394 g_variant_unref (value);
396 /* Of course this should do nothing... */
397 reopened = dconf_engine_source_refresh (source);
398 g_assert (!reopened);
400 /* Invalidate and replace */
401 dconf_mock_gvdb_table_invalidate (table);
402 table = dconf_mock_gvdb_table_new ();
403 dconf_mock_gvdb_table_insert (table, "/value", g_variant_new_string ("second file"), NULL);
404 dconf_mock_gvdb_install ("/path/to/db", table);
406 /* Even when invalidated, this should still do nothing... */
407 reopened = dconf_engine_source_refresh (source);
408 g_assert (!reopened);
409 value = gvdb_table_get_value (source->values, "/value");
410 g_assert_cmpstr (g_variant_get_string (value, NULL), ==, "first file");
411 g_variant_unref (value);
413 dconf_mock_gvdb_install ("/path/to/db", NULL);
414 dconf_engine_source_free (source);
418 static gboolean service_db_created;
419 static GvdbTable *service_db_table;
421 static GVariant *
422 handle_service_request (GBusType bus_type,
423 const gchar *bus_name,
424 const gchar *object_path,
425 const gchar *interface_name,
426 const gchar *method_name,
427 GVariant *parameters,
428 const GVariantType *expected_type,
429 GError **error)
431 g_assert_cmpstr (bus_name, ==, "ca.desrt.dconf");
432 g_assert_cmpstr (interface_name, ==, "ca.desrt.dconf.Writer");
433 g_assert_cmpstr (method_name, ==, "Init");
434 g_assert_cmpstr (g_variant_get_type_string (parameters), ==, "()");
436 if (g_str_equal (object_path, "/ca/desrt/dconf/shm/nil"))
438 service_db_table = dconf_mock_gvdb_table_new ();
439 dconf_mock_gvdb_table_insert (service_db_table, "/values/int32", g_variant_new_int32 (123456), NULL);
440 dconf_mock_gvdb_install ("/RUNTIME/dconf-service/shm/nil", service_db_table);
442 /* Make sure this only happens the first time... */
443 g_assert (!service_db_created);
444 service_db_created = TRUE;
446 return g_variant_new ("()");
448 else
450 g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, "Unknown DB type");
451 return NULL;
455 static void
456 test_service_source (void)
458 DConfEngineSource *source;
459 gboolean reopened;
461 /* Make sure we deal with errors from the service sensibly */
462 if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
464 g_log_set_always_fatal (G_LOG_LEVEL_ERROR);
466 source = dconf_engine_source_new ("service-db:unknown/nil");
467 dconf_mock_dbus_sync_call_handler = handle_service_request;
468 g_assert (source != NULL);
469 g_assert (source->values == NULL);
470 g_assert (source->locks == NULL);
471 reopened = dconf_engine_source_refresh (source);
473 exit (0);
475 g_test_trap_assert_passed ();
476 g_test_trap_assert_stderr ("*WARNING*: unable to open file*unknown/nil*expect degraded performance*");
478 /* Set up one that will work */
479 source = dconf_engine_source_new ("service-db:shm/nil");
480 g_assert (source != NULL);
481 g_assert (source->values == NULL);
482 g_assert (source->locks == NULL);
484 /* Refresh it the first time.
486 * This should cause the service to be asked to create it.
488 * This should return TRUE because we just opened it.
490 dconf_mock_dbus_sync_call_handler = handle_service_request;
491 reopened = dconf_engine_source_refresh (source);
492 dconf_mock_dbus_sync_call_handler = NULL;
493 g_assert (service_db_created);
494 g_assert (reopened);
496 /* After that, a refresh should be a no-op. */
497 reopened = dconf_engine_source_refresh (source);
498 g_assert (!reopened);
500 /* Close it and reopen it, ensuring that we don't hit the service
501 * again (because the file already exists).
503 * Note: dconf_mock_dbus_sync_call_handler = NULL, so D-Bus calls will
504 * assert.
506 dconf_engine_source_free (source);
507 source = dconf_engine_source_new ("service-db:shm/nil");
508 g_assert (source != NULL);
509 reopened = dconf_engine_source_refresh (source);
510 g_assert (reopened);
512 /* Make sure it has the content we expect to see */
513 g_assert (gvdb_table_has_value (source->values, "/values/int32"));
515 /* Now invalidate it and replace it with an empty one */
516 dconf_mock_gvdb_table_invalidate (service_db_table);
517 service_db_table = dconf_mock_gvdb_table_new ();
518 dconf_mock_gvdb_install ("/RUNTIME/dconf-service/shm/nil", service_db_table);
520 /* Now reopening should get the new one */
521 reopened = dconf_engine_source_refresh (source);
522 g_assert (reopened);
524 /* ...and we should find it to be empty */
525 g_assert (!gvdb_table_has_value (source->values, "/values/int32"));
527 /* We're done. */
528 dconf_engine_source_free (source);
530 /* This should not have done any shm... */
531 dconf_mock_shm_assert_log ("");
533 dconf_mock_gvdb_install ("/RUNTIME/dconf-service/shm/nil", NULL);
534 service_db_table = NULL;
537 static void
538 test_system_source (void)
540 DConfEngineSource *source;
541 GvdbTable *first_table;
542 GvdbTable *next_table;
543 gboolean reopened;
545 source = dconf_engine_source_new ("system-db:site");
546 g_assert (source != NULL);
548 /* Check to see that we get the warning about the missing file. */
549 if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
551 g_log_set_always_fatal (G_LOG_LEVEL_ERROR);
553 /* Failing to open should return FALSE from refresh */
554 reopened = dconf_engine_source_refresh (source);
555 g_assert (!reopened);
556 g_assert (source->values == NULL);
558 /* Attempt the reopen to make sure we don't get two warnings.
559 * We should see FALSE again since we go from NULL to NULL.
561 reopened = dconf_engine_source_refresh (source);
562 g_assert (!reopened);
564 /* Create the file after the fact and make sure it opens properly */
565 first_table = dconf_mock_gvdb_table_new ();
566 dconf_mock_gvdb_install ("/etc/dconf/db/site", first_table);
568 reopened = dconf_engine_source_refresh (source);
569 g_assert (reopened);
570 g_assert (source->values != NULL);
572 dconf_engine_source_free (source);
574 exit (0);
576 g_test_trap_assert_passed ();
577 /* Check that we only saw the warning, but only one time. */
578 g_test_trap_assert_stderr ("*this gvdb does not exist; expect degraded performance*");
579 g_test_trap_assert_stderr_unmatched ("*degraded*degraded*");
581 /* Create the file before the first refresh attempt */
582 first_table = dconf_mock_gvdb_table_new ();
583 dconf_mock_gvdb_install ("/etc/dconf/db/site", first_table);
584 /* Hang on to a copy for ourselves for below... */
585 dconf_mock_gvdb_table_ref (first_table);
587 /* See that we get the database. */
588 reopened = dconf_engine_source_refresh (source);
589 g_assert (reopened);
590 g_assert (source->values == first_table);
592 /* Do a refresh, make sure there is no change. */
593 reopened = dconf_engine_source_refresh (source);
594 g_assert (!reopened);
595 g_assert (source->values == first_table);
597 /* Replace the table on "disk" but don't invalidate the old one */
598 next_table = dconf_mock_gvdb_table_new ();
599 dconf_mock_gvdb_install ("/etc/dconf/db/site", next_table);
601 /* Make sure the old table remains open (ie: no IO performed) */
602 reopened = dconf_engine_source_refresh (source);
603 g_assert (!reopened);
604 g_assert (source->values == first_table);
606 /* Now mark the first table invalid and reopen */
607 dconf_mock_gvdb_table_invalidate (first_table);
608 gvdb_table_free (first_table);
609 reopened = dconf_engine_source_refresh (source);
610 g_assert (reopened);
611 g_assert (source->values == next_table);
613 /* Remove the file entirely and do the same thing */
614 dconf_mock_gvdb_install ("/etc/dconf/db/site", NULL);
615 reopened = dconf_engine_source_refresh (source);
616 g_assert (!reopened);
618 dconf_engine_source_free (source);
621 static void
622 invalidate_state (guint n_sources,
623 guint source_types,
624 gpointer *state)
626 gint i;
628 for (i = 0; i < n_sources; i++)
629 if (source_types & (1u << i))
631 if (state[i])
633 dconf_mock_gvdb_table_invalidate (state[i]);
634 gvdb_table_free (state[i]);
637 else
639 dconf_mock_shm_flag (state[i]);
640 g_free (state[i]);
644 static void
645 setup_state (guint n_sources,
646 guint source_types,
647 guint database_state,
648 gpointer *state)
650 gint i;
652 for (i = 0; i < n_sources; i++)
654 guint contents = database_state % 7;
655 GvdbTable *table = NULL;
656 gchar *filename;
658 if (contents)
660 table = dconf_mock_gvdb_table_new ();
662 /* Even numbers get the value setup */
663 if ((contents & 1) == 0)
664 dconf_mock_gvdb_table_insert (table, "/value", g_variant_new_uint32 (i), NULL);
666 /* Numbers above 2 get the locks table */
667 if (contents > 2)
669 GvdbTable *locks;
671 locks = dconf_mock_gvdb_table_new ();
673 /* Numbers above 4 get the lock set */
674 if (contents > 4)
675 dconf_mock_gvdb_table_insert (locks, "/value", g_variant_new_boolean (TRUE), NULL);
677 dconf_mock_gvdb_table_insert (table, ".locks", NULL, locks);
681 if (source_types & (1u << i))
683 if (state)
685 if (table)
686 state[i] = dconf_mock_gvdb_table_ref (table);
687 else
688 state[i] = NULL;
691 filename = g_strdup_printf ("/etc/dconf/db/db%d", i);
693 else
695 if (state)
696 state[i] = g_strdup_printf ("db%d", i);
698 filename = g_strdup_printf ("/HOME/.config/dconf/db%d", i);
701 dconf_mock_gvdb_install (filename, table);
702 g_free (filename);
704 database_state /= 7;
708 static void
709 create_profile (const gchar *filename,
710 guint n_sources,
711 guint source_types)
713 GError *error = NULL;
714 GString *profile;
715 gint i;
717 profile = g_string_new (NULL);
718 for (i = 0; i < n_sources; i++)
719 if (source_types & (1u << i))
720 g_string_append_printf (profile, "system-db:db%d\n", i);
721 else
722 g_string_append_printf (profile, "user-db:db%d\n", i);
723 g_file_set_contents (filename, profile->str, profile->len, &error);
724 g_assert_no_error (error);
725 g_string_free (profile, TRUE);
728 static GQueue read_through_queues[12];
730 static void
731 check_read (DConfEngine *engine,
732 guint n_sources,
733 guint source_types,
734 guint database_state)
736 gboolean any_values = FALSE;
737 gboolean any_locks = FALSE;
738 guint first_contents;
739 gint underlying = -1;
740 gint expected = -1;
741 gboolean writable;
742 GVariant *value;
743 gchar **list;
744 guint i;
745 gint n;
747 /* The value we expect to read is number of the first source that has
748 * the value set (ie: odd digit in database_state) up to the lowest
749 * level lock.
751 * We go over each database. If 'expected' has not yet been set and
752 * we find that we should have a value in this database, we set it.
753 * If we find that we should have a lock in this database, we unset
754 * any previous values (since they should not have been written).
756 * We intentionally code this loop in a different way than the one in
757 * dconf itself is currently implemented...
759 * We also take note of if we saw any locks and cross-check that with
760 * dconf_engine_is_writable(). We check if we saw and values at all
761 * and cross-check that with dconf_engine_list() (which ignores
762 * locks).
764 first_contents = database_state % 7;
765 for (i = 0; i < n_sources; i++)
767 guint contents = database_state % 7;
769 /* A lock here should prevent higher reads */
770 if (contents > 4)
772 /* Locks in the first database don't count... */
773 if (i != 0)
774 any_locks = TRUE;
775 expected = -1;
778 /* A value here should be read */
779 if (contents && !(contents & 1))
781 if (i != 0 && underlying == -1)
782 underlying = i;
784 if (expected == -1)
786 any_values = TRUE;
787 expected = i;
791 database_state /= 7;
794 value = dconf_engine_read (engine, DCONF_READ_FLAGS_NONE, NULL, "/value");
796 if (expected != -1)
798 g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32));
799 g_assert_cmpint (g_variant_get_uint32 (value), ==, expected);
800 g_variant_unref (value);
802 else
803 g_assert (value == NULL);
805 /* We are writable if the first database is a user database and we
806 * didn't encounter any locks...
808 writable = dconf_engine_is_writable (engine, "/value");
809 g_assert_cmpint (writable, ==, n_sources && !(source_types & 1) && !any_locks);
811 /* Check various read-through scenarios. Read-through should only be
812 * effective if the database is writable.
814 for (i = 0; i < G_N_ELEMENTS (read_through_queues); i++)
816 gint our_expected = expected;
818 if (writable)
820 /* If writable, see what our changeset did.
822 * 0: nothing
823 * 1: reset value (should see underlying value)
824 * 2: set value to 123
826 if ((i % 3) == 1)
827 our_expected = underlying;
828 else if ((i % 3) == 2)
829 our_expected = 123;
832 value = dconf_engine_read (engine, DCONF_READ_FLAGS_NONE, &read_through_queues[i], "/value");
834 if (our_expected != -1)
836 g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32));
837 g_assert_cmpint (g_variant_get_uint32 (value), ==, our_expected);
838 g_variant_unref (value);
840 else
841 g_assert (value == NULL);
844 /* Check listing */
845 g_strfreev (dconf_engine_list (engine, "/", &n));
846 list = dconf_engine_list (engine, "/", NULL);
847 g_assert_cmpint (g_strv_length (list), ==, n);
848 if (any_values)
850 g_assert_cmpstr (list[0], ==, "value");
851 g_assert (list[1] == NULL);
853 else
854 g_assert (list[0] == NULL);
855 g_strfreev (list);
857 /* Check the user value.
859 * This should be set only in the case that the first database is a
860 * user database (ie: writable) and the contents of that database are
861 * set (ie: 2, 4 or 6). See the table in the comment below.
863 * Note: we do not consider locks.
865 value = dconf_engine_read (engine, DCONF_READ_USER_VALUE, NULL, "/value");
866 if (value)
868 g_assert (first_contents && !(first_contents & 1) && !(source_types & 1));
869 g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32));
870 g_assert_cmpint (g_variant_get_uint32 (value), ==, 0);
871 g_variant_unref (value);
873 else
875 /* Three possibilities for failure:
876 * - first db did not exist
877 * - value was missing from first db
878 * - first DB was system-db
880 g_assert (!first_contents || (first_contents & 1) || (source_types & 1));
883 /* Check read_through vs. user-value */
884 for (i = 0; i < G_N_ELEMENTS (read_through_queues); i++)
886 /* It is only possible here to see one of three possibilities:
888 * - NULL
889 * - 0 (value from user's DB)
890 * - 123 (value from queue)
892 * We see these values regardless of writability. We do however
893 * ensure that we have a writable database as the first one.
895 value = dconf_engine_read (engine, DCONF_READ_USER_VALUE, &read_through_queues[i], "/value");
897 /* If we have no first source, or the first source is non-user
898 * than we should always do nothing (since we can't queue changes
899 * against a system db or one that doesn't exist).
901 if (n_sources == 0 || (source_types & 1) || (i % 3) == 0)
903 /* Changeset did nothing, so it should be same as above. */
904 if (value)
906 g_assert (first_contents && !(first_contents & 1) && !(source_types & 1));
907 g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32));
908 g_assert_cmpint (g_variant_get_uint32 (value), ==, 0);
910 else
911 g_assert (!first_contents || (first_contents & 1) || (source_types & 1));
913 else if ((i % 3) == 1)
915 /* Changeset did a reset, so we should always see NULL */
916 g_assert (value == NULL);
918 else if ((i % 3) == 2)
920 /* Changeset set a value, so we should see it */
921 g_assert_cmpint (g_variant_get_uint32 (value), ==, 123);
924 if (value)
925 g_variant_unref (value);
929 static gboolean
930 is_expected (const gchar *log_domain,
931 GLogLevelFlags log_level,
932 const gchar *message)
934 return g_str_equal (log_domain, "dconf") &&
935 log_level == (G_LOG_LEVEL_WARNING | G_LOG_FLAG_FATAL) &&
936 strstr (message, "unable to open file '/etc/dconf/db");
939 static gboolean
940 fatal_handler (const gchar *log_domain,
941 GLogLevelFlags log_level,
942 const gchar *message,
943 gpointer user_data)
945 return !is_expected (log_domain, log_level, message);
948 static void
949 normal_handler (const gchar *log_domain,
950 GLogLevelFlags log_level,
951 const gchar *message,
952 gpointer user_data)
954 if (!is_expected (log_domain, log_level, message))
955 g_error ("unexpected error: %s\n", message);
958 static void
959 test_read (void)
961 #define MAX_N_SOURCES 2
962 gpointer state[MAX_N_SOURCES];
963 gchar *profile_filename;
964 GError *error = NULL;
965 DConfEngine *engine;
966 guint i, j, k;
967 guint n;
968 guint handler_id;
970 /* This test throws a lot of messages about missing databases.
971 * Capture and ignore them.
973 g_test_log_set_fatal_handler (fatal_handler, NULL);
974 handler_id = g_log_set_handler ("dconf", G_LOG_LEVEL_WARNING | G_LOG_FLAG_FATAL, normal_handler, NULL);
976 /* Our test strategy is as follows:
978 * We only test a single key name. It is assumed that gvdb is working
979 * properly already so we are only interested in interactions between
980 * multiple databases for a given key name.
982 * The outermost loop is over 'n'. This is how many sources are in
983 * our test. We test 0 to 3 (which should be enough to cover all
984 * 'interesting' possibilities). 4 takes too long to run (2*7*7 ~=
985 * 100 times as long as 3).
987 * The next loop is over 'i'. This goes from 0 to 2^n - 1, with each
988 * bit deciding the type of source of the i-th element
990 * 0: user
992 * 1: system
994 * The next loop is over 'j'. This goes from 0 to 7^n - 1, with each
995 * base-7 digit deciding the state of the database file associated
996 * with the i-th source:
998 * j file has value has ".locks" has lock
999 * ----------------------------------------------------
1000 * 0 0 - - -
1001 * 1 1 0 0 -
1002 * 2 1 1 0 -
1003 * 3 1 0 1 0
1004 * 4 1 1 1 0
1005 * 5 1 0 1 1
1006 * 6 1 1 1 1
1008 * Where 'file' is if the database file exists, 'has value' is if a
1009 * value exists at '/value' within the file, 'has ".locks"' is if
1010 * there is a ".locks" subtable and 'has lock' is if there is a lock
1011 * for '/value' within that table.
1013 * Finally, we loop over 'k' as a state to transition to ('k' works
1014 * the same way as 'j').
1016 * Once we know 'n' and 'i', we can write a profile file.
1018 * Once we know 'j' we can setup the initial state, create the engine
1019 * and check that we got the expected value. Then we transition to
1020 * state 'k' and make sure everything still works as expected.
1022 * Since we want to test all j->k transitions, we do the initial setup
1023 * of the engine (according to j) inside of the 'k' loop, since we
1024 * need to test all possible transitions from 'j'.
1026 * We additionally test the effect of read-through queues in 4
1027 * situations:
1029 * - NULL: no queue
1030 * - 0: queue with no effect
1031 * - 1: queue that resets the value
1032 * - 2: queue that sets the value to 123
1034 * For the cases (0, 1, 2) we can have multiple types of queue that
1035 * achieve the desired effect. We can put more than 3 items in
1036 * read_through_queues -- the expected behaviour is dictated by the
1037 * value of (i % 3) where i is the array index.
1040 /* We use a scheme to set up each queue. Again, we assume that
1041 * GHashTable is working OK, so we only bother having "/value" as a
1042 * changeset item (or not).
1044 * We have an array of strings, each string defining the
1045 * configuration of one queue. In each string, each character
1046 * represents the contents of a changeset within the queue, in
1047 * order.
1049 * ' ' - empty changeset
1050 * 's' - set value to 123
1051 * 'r' - reset value
1052 * 'x' - set value to 321
1054 const gchar *queue_configs[] = {
1055 "", "r", "s",
1056 " ", "rr", "ss",
1057 " ", "rs", "sr",
1058 " ", "rx", "sx"
1060 gint i;
1062 G_STATIC_ASSERT (G_N_ELEMENTS (queue_configs) == G_N_ELEMENTS (read_through_queues));
1063 for (i = 0; i < G_N_ELEMENTS (read_through_queues); i++)
1065 const gchar *conf = queue_configs[i];
1066 gint j;
1068 for (j = 0; conf[j]; j++)
1070 DConfChangeset *changeset;
1072 changeset = dconf_changeset_new ();
1074 switch (conf[j])
1076 case ' ':
1077 break;
1078 case 'r':
1079 dconf_changeset_set (changeset, "/value", NULL);
1080 break;
1081 case 's':
1082 dconf_changeset_set (changeset, "/value", g_variant_new_uint32 (123));
1083 break;
1084 case 'x':
1085 dconf_changeset_set (changeset, "/value", g_variant_new_uint32 (321));
1086 break;
1087 default:
1088 g_assert_not_reached ();
1091 g_queue_push_head (&read_through_queues[i], changeset);
1096 /* We need a place to put the profile files we use for this test */
1097 close (g_file_open_tmp ("dconf-testcase.XXXXXX", &profile_filename, &error));
1098 g_assert_no_error (error);
1100 for (n = 0; n <= MAX_N_SOURCES; n++)
1101 for (i = 0; i < pow (2, n); i++)
1103 gint n_possible_states = pow (7, n);
1105 /* Step 1: write out the profile file */
1106 create_profile (profile_filename, n, i);
1108 for (j = 0; j < n_possible_states; j++)
1109 for (k = 0; k < n_possible_states; k++)
1111 guint64 old_state, new_state;
1113 /* Step 2: setup the state */
1114 setup_state (n, i, j, (j != k) ? state : NULL);
1116 /* Step 3: create the engine */
1117 engine = dconf_engine_new (profile_filename, NULL, NULL);
1119 /* Step 4: read, and check result */
1120 check_read (engine, n, i, j);
1121 old_state = dconf_engine_get_state (engine);
1123 /* Step 5: change to the new state */
1124 if (j != k)
1126 setup_state (n, i, k, NULL);
1127 invalidate_state (n, i, state);
1130 /* Step 6: read, and check result */
1131 check_read (engine, n, i, k);
1132 new_state = dconf_engine_get_state (engine);
1134 g_assert ((j == k) == (new_state == old_state));
1136 /* Clean up */
1137 setup_state (n, i, 0, NULL);
1138 dconf_engine_unref (engine);
1142 /* Clean up the tempfile we were using... */
1143 g_unlink (profile_filename);
1144 g_free (profile_filename);
1145 dconf_mock_shm_reset ();
1147 g_log_remove_handler ("dconf", handler_id);
1150 static void
1151 test_watch_fast (void)
1153 DConfEngine *engine;
1154 GvdbTable *table;
1155 GVariant *triv;
1156 guint64 a, b, c;
1158 change_log = g_string_new (NULL);
1160 table = dconf_mock_gvdb_table_new ();
1161 dconf_mock_gvdb_install ("/HOME/.config/dconf/user", table);
1162 table = dconf_mock_gvdb_table_new ();
1163 dconf_mock_gvdb_install ("/etc/dconf/db/site", table);
1165 triv = g_variant_ref_sink (g_variant_new ("()"));
1167 engine = dconf_engine_new (SRCDIR "/profile/dos", NULL, NULL);
1169 /* Check that establishing a watch works properly in the normal case.
1171 a = dconf_engine_get_state (engine);
1172 dconf_engine_watch_fast (engine, "/a/b/c");
1173 /* watches do not count as outstanding changes */
1174 g_assert (!dconf_engine_has_outstanding (engine));
1175 dconf_engine_sync (engine);
1176 b = dconf_engine_get_state (engine);
1177 g_assert_cmpuint (a, ==, b);
1178 /* both AddMatch results come back before shm is flagged */
1179 dconf_mock_dbus_async_reply (triv, NULL);
1180 dconf_mock_dbus_async_reply (triv, NULL);
1181 dconf_mock_dbus_assert_no_async ();
1182 dconf_mock_shm_flag ("user");
1183 b = dconf_engine_get_state (engine);
1184 g_assert_cmpuint (a, !=, b);
1185 g_assert_cmpstr (change_log->str, ==, "");
1186 dconf_engine_unwatch_fast (engine, "/a/b/c");
1187 dconf_mock_dbus_async_reply (triv, NULL);
1188 dconf_mock_dbus_async_reply (triv, NULL);
1189 dconf_mock_dbus_assert_no_async ();
1191 /* Establish a watch and fail the race. */
1192 a = dconf_engine_get_state (engine);
1193 dconf_engine_watch_fast (engine, "/a/b/c");
1194 g_assert (!dconf_engine_has_outstanding (engine));
1195 dconf_engine_sync (engine);
1196 b = dconf_engine_get_state (engine);
1197 g_assert_cmpuint (a, ==, b);
1198 /* one AddMatch result comes back -after- shm is flagged */
1199 dconf_mock_dbus_async_reply (triv, NULL);
1200 dconf_mock_shm_flag ("user");
1201 dconf_mock_dbus_async_reply (triv, NULL);
1202 dconf_mock_dbus_assert_no_async ();
1203 b = dconf_engine_get_state (engine);
1204 g_assert_cmpuint (a, !=, b);
1205 g_assert_cmpstr (change_log->str, ==, "/a/b/c:1::nil;");
1206 /* Try to establish a watch again for the same path */
1207 dconf_engine_watch_fast (engine, "/a/b/c");
1208 g_assert (!dconf_engine_has_outstanding (engine));
1209 dconf_engine_sync (engine);
1210 c = dconf_engine_get_state (engine);
1211 g_assert_cmpuint (b, ==, c);
1212 /* The watch result was not sent, because the path was already watched */
1213 dconf_mock_dbus_assert_no_async();
1214 c = dconf_engine_get_state (engine);
1215 g_assert_cmpuint (b, ==, c);
1216 /* Since the path was already being watched,
1217 * do not expect a second false change notification */
1218 g_assert_cmpstr (change_log->str, ==, "/a/b/c:1::nil;");
1219 dconf_engine_unwatch_fast (engine, "/a/b/c");
1220 dconf_mock_dbus_async_reply (triv, NULL);
1221 dconf_mock_dbus_async_reply (triv, NULL);
1222 dconf_mock_dbus_assert_no_async ();
1224 dconf_mock_gvdb_install ("/HOME/.config/dconf/user", NULL);
1225 dconf_mock_gvdb_install ("/etc/dconf/db/site", NULL);
1226 dconf_engine_unref (engine);
1227 g_string_free (change_log, TRUE);
1228 change_log = NULL;
1229 g_variant_unref (triv);
1232 static const gchar *match_request_type;
1233 static gboolean got_match_request[5];
1235 static GVariant *
1236 handle_match_request (GBusType bus_type,
1237 const gchar *bus_name,
1238 const gchar *object_path,
1239 const gchar *interface_name,
1240 const gchar *method_name,
1241 GVariant *parameters,
1242 const GVariantType *expected_type,
1243 GError **error)
1245 const gchar *match_rule;
1247 g_assert_cmpstr (bus_name, ==, "org.freedesktop.DBus");
1248 /* any object path works... */
1249 g_assert_cmpstr (interface_name, ==, "org.freedesktop.DBus");
1250 g_assert_cmpstr (method_name, ==, match_request_type);
1251 g_assert_cmpstr (g_variant_get_type_string (parameters), ==, "(s)");
1252 g_variant_get (parameters, "(&s)", &match_rule);
1253 g_assert (strstr (match_rule, "arg0path='/a/b/c'"));
1254 g_assert (!got_match_request[bus_type]);
1255 got_match_request[bus_type] = TRUE;
1257 return g_variant_new ("()");
1260 static void
1261 test_watch_sync (void)
1263 DConfEngine *engine;
1265 dconf_mock_dbus_sync_call_handler = handle_match_request;
1267 engine = dconf_engine_new (SRCDIR "/profile/dos", NULL, NULL);
1269 match_request_type = "AddMatch";
1270 dconf_engine_watch_sync (engine, "/a/b/c");
1271 g_assert (got_match_request[G_BUS_TYPE_SESSION]);
1272 g_assert (got_match_request[G_BUS_TYPE_SYSTEM]);
1273 got_match_request[G_BUS_TYPE_SESSION] = FALSE;
1274 got_match_request[G_BUS_TYPE_SYSTEM] = FALSE;
1276 match_request_type = "RemoveMatch";
1277 dconf_engine_unwatch_sync (engine, "/a/b/c");
1278 g_assert (got_match_request[G_BUS_TYPE_SESSION]);
1279 g_assert (got_match_request[G_BUS_TYPE_SYSTEM]);
1280 got_match_request[G_BUS_TYPE_SESSION] = FALSE;
1281 got_match_request[G_BUS_TYPE_SYSTEM] = FALSE;
1283 dconf_engine_unref (engine);
1285 dconf_mock_dbus_sync_call_handler = NULL;
1286 match_request_type = NULL;
1289 static void
1290 test_watching (void)
1292 DConfEngine *engine;
1293 const gchar *apple = "apple";
1294 const gchar *orange = "orange";
1295 const gchar *banana = "banana";
1297 engine = dconf_engine_new (SRCDIR "/profile/dos", NULL, NULL);
1299 g_assert (!dconf_engine_is_watching(engine, apple, TRUE));
1300 g_assert (!dconf_engine_is_watching(engine, apple, FALSE));
1301 g_assert (!dconf_engine_is_watching(engine, orange, TRUE));
1302 g_assert (!dconf_engine_is_watching(engine, orange, FALSE));
1303 g_assert (!dconf_engine_is_watching(engine, banana, TRUE));
1304 g_assert (!dconf_engine_is_watching(engine, banana, FALSE));
1306 dconf_engine_set_watching (engine, apple, FALSE, FALSE);
1307 dconf_engine_set_watching (engine, orange, TRUE, FALSE);
1308 dconf_engine_set_watching (engine, banana, TRUE, TRUE);
1310 g_assert (!dconf_engine_is_watching(engine, apple, TRUE));
1311 g_assert (!dconf_engine_is_watching(engine, apple, FALSE));
1312 g_assert (!dconf_engine_is_watching(engine, orange, TRUE));
1313 g_assert (dconf_engine_is_watching(engine, orange, FALSE));
1314 g_assert (dconf_engine_is_watching(engine, banana, TRUE));
1315 g_assert (dconf_engine_is_watching(engine, banana, FALSE));
1317 dconf_engine_set_watching (engine, orange, TRUE, TRUE);
1318 dconf_engine_set_watching (engine, banana, FALSE, FALSE);
1320 g_assert (!dconf_engine_is_watching(engine, apple, TRUE));
1321 g_assert (!dconf_engine_is_watching(engine, apple, FALSE));
1322 g_assert (dconf_engine_is_watching(engine, orange, TRUE));
1323 g_assert (dconf_engine_is_watching(engine, orange, FALSE));
1324 g_assert (!dconf_engine_is_watching(engine, banana, TRUE));
1325 g_assert (!dconf_engine_is_watching(engine, banana, FALSE));
1327 dconf_engine_set_watching (engine, orange, FALSE, FALSE);
1329 g_assert (!dconf_engine_is_watching(engine, apple, TRUE));
1330 g_assert (!dconf_engine_is_watching(engine, apple, FALSE));
1331 g_assert (!dconf_engine_is_watching(engine, orange, TRUE));
1332 g_assert (!dconf_engine_is_watching(engine, orange, FALSE));
1333 g_assert (!dconf_engine_is_watching(engine, banana, TRUE));
1334 g_assert (!dconf_engine_is_watching(engine, banana, FALSE));
1337 static void
1338 test_change_fast (void)
1340 DConfChangeset *empty, *good_write, *bad_write, *very_good_write, *slightly_bad_write;
1341 GvdbTable *table, *locks;
1342 DConfEngine *engine;
1343 gboolean success;
1344 GError *error = NULL;
1345 GVariant *value;
1347 change_log = g_string_new (NULL);
1349 table = dconf_mock_gvdb_table_new ();
1350 locks = dconf_mock_gvdb_table_new ();
1351 dconf_mock_gvdb_table_insert (locks, "/locked", g_variant_new_boolean (TRUE), NULL);
1352 dconf_mock_gvdb_table_insert (table, ".locks", NULL, locks);
1353 dconf_mock_gvdb_install ("/etc/dconf/db/site", table);
1355 empty = dconf_changeset_new ();
1356 good_write = dconf_changeset_new_write ("/value", g_variant_new_string ("value"));
1357 bad_write = dconf_changeset_new_write ("/locked", g_variant_new_string ("value"));
1358 very_good_write = dconf_changeset_new_write ("/value", g_variant_new_string ("value"));
1359 dconf_changeset_set (very_good_write, "/to-reset", NULL);
1360 slightly_bad_write = dconf_changeset_new_write ("/locked", g_variant_new_string ("value"));
1361 dconf_changeset_set (slightly_bad_write, "/to-reset", NULL);
1363 engine = dconf_engine_new (SRCDIR "/profile/dos", NULL, NULL);
1365 success = dconf_engine_change_fast (engine, empty, NULL, &error);
1366 g_assert_no_error (error);
1367 g_assert (success);
1369 success = dconf_engine_change_fast (engine, empty, NULL, &error);
1370 g_assert_no_error (error);
1371 g_assert (success);
1373 success = dconf_engine_change_fast (engine, bad_write, NULL, &error);
1374 g_assert_error (error, DCONF_ERROR, DCONF_ERROR_NOT_WRITABLE);
1375 g_clear_error (&error);
1376 g_assert (!success);
1378 success = dconf_engine_change_fast (engine, slightly_bad_write, NULL, &error);
1379 g_assert_error (error, DCONF_ERROR, DCONF_ERROR_NOT_WRITABLE);
1380 g_clear_error (&error);
1381 g_assert (!success);
1383 /* Up to now, no D-Bus traffic should have been sent at all because we
1384 * only had trivial and non-writable attempts.
1386 * Now try some working cases
1388 dconf_mock_dbus_assert_no_async ();
1389 g_assert_cmpstr (change_log->str, ==, "");
1391 success = dconf_engine_change_fast (engine, good_write, NULL, &error);
1392 g_assert_no_error (error);
1393 g_assert (success);
1395 /* That should have emitted a synthetic change event */
1396 g_assert_cmpstr (change_log->str, ==, "/value:1::nil;");
1397 g_string_set_size (change_log, 0);
1399 /* Verify that the value is set */
1400 value = dconf_engine_read (engine, DCONF_READ_FLAGS_NONE, NULL, "/value");
1401 g_assert_cmpstr (g_variant_get_string (value, NULL), ==, "value");
1402 g_variant_unref (value);
1404 /* Fail the attempted write. This should cause a warning and a change. */
1405 g_test_expect_message ("dconf", G_LOG_LEVEL_WARNING, "failed to commit changes to dconf: something failed");
1406 error = g_error_new_literal (G_FILE_ERROR, G_FILE_ERROR_NOENT, "something failed");
1407 dconf_mock_dbus_async_reply (NULL, error);
1408 g_clear_error (&error);
1409 g_assert_cmpstr (change_log->str, ==, "/value:1::nil;");
1410 g_string_set_size (change_log, 0);
1412 /* Verify that the value became unset due to the failure */
1413 value = dconf_engine_read (engine, DCONF_READ_FLAGS_NONE, NULL, "value");
1414 g_assert (value == NULL);
1416 /* Now try a successful write */
1417 dconf_mock_dbus_assert_no_async ();
1418 g_assert_cmpstr (change_log->str, ==, "");
1420 success = dconf_engine_change_fast (engine, good_write, NULL, &error);
1421 g_assert_no_error (error);
1422 g_assert (success);
1424 /* That should have emitted a synthetic change event */
1425 g_assert_cmpstr (change_log->str, ==, "/value:1::nil;");
1426 g_string_set_size (change_log, 0);
1428 /* Verify that the value is set */
1429 value = dconf_engine_read (engine, DCONF_READ_FLAGS_NONE, NULL, "/value");
1430 g_assert_cmpstr (g_variant_get_string (value, NULL), ==, "value");
1431 g_variant_unref (value);
1433 /* ACK the write. */
1434 error = g_error_new_literal (G_FILE_ERROR, G_FILE_ERROR_NOENT, "something failed");
1435 dconf_mock_dbus_async_reply (g_variant_new ("(s)", "tag"), NULL);
1436 g_clear_error (&error);
1437 /* No change this time, since we already did it. */
1438 g_assert_cmpstr (change_log->str, ==, "");
1440 /* Verify that the value became unset due to the in-flight queue
1441 * clearing... */
1442 value = dconf_engine_read (engine, DCONF_READ_FLAGS_NONE, NULL, "value");
1443 g_assert (value == NULL);
1445 /* Do that all again for a changeset with more than one item */
1446 dconf_mock_dbus_assert_no_async ();
1447 g_assert_cmpstr (change_log->str, ==, "");
1448 success = dconf_engine_change_fast (engine, very_good_write, NULL, &error);
1449 g_assert_no_error (error);
1450 g_assert (success);
1451 g_assert_cmpstr (change_log->str, ==, "/:2:to-reset,value:nil;");
1452 g_string_set_size (change_log, 0);
1453 value = dconf_engine_read (engine, DCONF_READ_FLAGS_NONE, NULL, "/value");
1454 g_assert_cmpstr (g_variant_get_string (value, NULL), ==, "value");
1455 g_variant_unref (value);
1456 g_test_expect_message ("dconf", G_LOG_LEVEL_WARNING, "failed to commit changes to dconf: something failed");
1457 error = g_error_new_literal (G_FILE_ERROR, G_FILE_ERROR_NOENT, "something failed");
1458 dconf_mock_dbus_async_reply (NULL, error);
1459 g_clear_error (&error);
1460 g_assert_cmpstr (change_log->str, ==, "/:2:to-reset,value:nil;");
1461 g_string_set_size (change_log, 0);
1462 value = dconf_engine_read (engine, DCONF_READ_FLAGS_NONE, NULL, "value");
1463 g_assert (value == NULL);
1464 dconf_mock_dbus_assert_no_async ();
1465 g_assert_cmpstr (change_log->str, ==, "");
1466 success = dconf_engine_change_fast (engine, very_good_write, NULL, &error);
1467 g_assert_no_error (error);
1468 g_assert (success);
1469 g_assert_cmpstr (change_log->str, ==, "/:2:to-reset,value:nil;");
1470 g_string_set_size (change_log, 0);
1471 value = dconf_engine_read (engine, DCONF_READ_FLAGS_NONE, NULL, "/value");
1472 g_assert_cmpstr (g_variant_get_string (value, NULL), ==, "value");
1473 g_variant_unref (value);
1474 error = g_error_new_literal (G_FILE_ERROR, G_FILE_ERROR_NOENT, "something failed");
1475 dconf_mock_dbus_async_reply (g_variant_new ("(s)", "tag"), NULL);
1476 g_clear_error (&error);
1477 g_assert_cmpstr (change_log->str, ==, "");
1478 value = dconf_engine_read (engine, DCONF_READ_FLAGS_NONE, NULL, "value");
1479 g_assert (value == NULL);
1481 dconf_engine_unref (engine);
1483 dconf_changeset_unref (empty);
1484 dconf_changeset_unref (good_write);
1485 dconf_changeset_unref (very_good_write);
1486 dconf_changeset_unref (bad_write);
1487 dconf_changeset_unref (slightly_bad_write);
1488 g_string_free (change_log, TRUE);
1489 change_log = NULL;
1492 static GError *change_sync_error;
1493 static GVariant *change_sync_result;
1495 static GVariant *
1496 handle_write_request (GBusType bus_type,
1497 const gchar *bus_name,
1498 const gchar *object_path,
1499 const gchar *interface_name,
1500 const gchar *method_name,
1501 GVariant *parameters,
1502 const GVariantType *expected_type,
1503 GError **error)
1505 g_assert_cmpstr (bus_name, ==, "ca.desrt.dconf");
1506 g_assert_cmpstr (interface_name, ==, "ca.desrt.dconf.Writer");
1508 /* Assume that the engine can format the method call properly, but
1509 * test that it can properly handle weird replies.
1512 *error = change_sync_error;
1513 return change_sync_result;
1517 static void
1518 test_change_sync (void)
1520 DConfChangeset *empty, *good_write, *bad_write, *very_good_write, *slightly_bad_write;
1521 GvdbTable *table, *locks;
1522 DConfEngine *engine;
1523 gboolean success;
1524 GError *error = NULL;
1525 gchar *tag;
1527 table = dconf_mock_gvdb_table_new ();
1528 locks = dconf_mock_gvdb_table_new ();
1529 dconf_mock_gvdb_table_insert (locks, "/locked", g_variant_new_boolean (TRUE), NULL);
1530 dconf_mock_gvdb_table_insert (table, ".locks", NULL, locks);
1531 dconf_mock_gvdb_install ("/etc/dconf/db/site", table);
1533 empty = dconf_changeset_new ();
1534 good_write = dconf_changeset_new_write ("/value", g_variant_new_string ("value"));
1535 bad_write = dconf_changeset_new_write ("/locked", g_variant_new_string ("value"));
1536 very_good_write = dconf_changeset_new_write ("/value", g_variant_new_string ("value"));
1537 dconf_changeset_set (very_good_write, "/to-reset", NULL);
1538 slightly_bad_write = dconf_changeset_new_write ("/locked", g_variant_new_string ("value"));
1539 dconf_changeset_set (slightly_bad_write, "/to-reset", NULL);
1541 engine = dconf_engine_new (SRCDIR "/profile/dos", NULL, NULL);
1543 success = dconf_engine_change_sync (engine, empty, &tag, &error);
1544 g_assert_no_error (error);
1545 g_assert (success);
1546 g_free (tag);
1548 success = dconf_engine_change_sync (engine, empty, NULL, &error);
1549 g_assert_no_error (error);
1550 g_assert (success);
1552 success = dconf_engine_change_sync (engine, bad_write, &tag, &error);
1553 g_assert_error (error, DCONF_ERROR, DCONF_ERROR_NOT_WRITABLE);
1554 g_clear_error (&error);
1555 g_assert (!success);
1557 success = dconf_engine_change_sync (engine, slightly_bad_write, NULL, &error);
1558 g_assert_error (error, DCONF_ERROR, DCONF_ERROR_NOT_WRITABLE);
1559 g_clear_error (&error);
1560 g_assert (!success);
1562 /* Up to now, no D-Bus traffic should have been sent at all because we
1563 * only had trivial and non-writable attempts.
1565 * Now try some working cases
1567 dconf_mock_dbus_sync_call_handler = handle_write_request;
1568 change_sync_result = g_variant_new ("(s)", "mytag");
1570 success = dconf_engine_change_sync (engine, good_write, &tag, &error);
1571 g_assert_no_error (error);
1572 g_assert (success);
1573 g_assert_cmpstr (tag, ==, "mytag");
1574 g_free (tag);
1575 change_sync_result = NULL;
1577 change_sync_error = g_error_new_literal (G_FILE_ERROR, G_FILE_ERROR_NOENT, "something failed");
1578 success = dconf_engine_change_sync (engine, very_good_write, &tag, &error);
1579 g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT);
1580 g_assert (!success);
1581 g_clear_error (&error);
1582 change_sync_error = NULL;
1584 dconf_changeset_unref (empty);
1585 dconf_changeset_unref (good_write);
1586 dconf_changeset_unref (very_good_write);
1587 dconf_changeset_unref (bad_write);
1588 dconf_changeset_unref (slightly_bad_write);
1589 dconf_engine_unref (engine);
1592 static void
1593 send_signal (GBusType type,
1594 const gchar *name,
1595 const gchar *path,
1596 const gchar *signame,
1597 const gchar *args)
1599 GVariant *value;
1601 value = g_variant_ref_sink (g_variant_new_parsed (args));
1602 dconf_engine_handle_dbus_signal (type, name, path, signame, value);
1603 g_variant_unref (value);
1606 static void
1607 test_signals (void)
1609 DConfEngine *engine;
1611 change_log = g_string_new (NULL);
1613 engine = dconf_engine_new (SRCDIR "/profile/dos", NULL, NULL);
1615 /* Throw some non-sense at it to make sure it gets rejected */
1617 /* Invalid signal name */
1618 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "UnNotify", "('/', [''], 'tag')");
1619 send_signal (G_BUS_TYPE_SYSTEM, ":1.123", "/ca/desrt/dconf/Writer/site", "UnNotify", "('/', [''], 'tag')");
1620 g_assert_cmpstr (change_log->str, ==, "");
1621 /* Bad path */
1622 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/use", "Notify", "('/', [''], 'tag')");
1623 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/use", "WritabilityNotify", "('/',)");
1624 send_signal (G_BUS_TYPE_SYSTEM, ":1.123", "/ca/desrt/dconf/Writer/sit", "Notify", "('/', [''], 'tag')");
1625 send_signal (G_BUS_TYPE_SYSTEM, ":1.123", "/ca/desrt/dconf/Writer/sit", "WritabilityNotify", "('/',)");
1626 g_assert_cmpstr (change_log->str, ==, "");
1627 /* Wrong signature for signal */
1628 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify", "('/',)");
1629 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "WritabilityNotify", "('/', [''], '')");
1630 send_signal (G_BUS_TYPE_SYSTEM, ":1.123", "/ca/desrt/dconf/Writer/site", "Notify", "('/',)");
1631 send_signal (G_BUS_TYPE_SYSTEM, ":1.123", "/ca/desrt/dconf/Writer/site", "WritabilityNotify", "('/', [''], '')");
1632 g_assert_cmpstr (change_log->str, ==, "");
1633 /* Signal delivered on wrong bus type */
1634 send_signal (G_BUS_TYPE_SYSTEM, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify", "('/', [''], 'tag')");
1635 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/site", "Notify", "('/', [''], 'tag')");
1636 send_signal (G_BUS_TYPE_SYSTEM, ":1.123", "/ca/desrt/dconf/Writer/user", "WritabilityNotify", "('/',)");
1637 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/site", "WritabilityNotify", "('/',)");
1638 g_assert_cmpstr (change_log->str, ==, "");
1639 /* Empty changeset */
1640 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify", "('/a', @as [], 'tag')");
1641 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify", "('/a/', @as [], 'tag')");
1642 send_signal (G_BUS_TYPE_SYSTEM, ":1.123", "/ca/desrt/dconf/Writer/site", "Notify", "('/a', @as [], 'tag')");
1643 send_signal (G_BUS_TYPE_SYSTEM, ":1.123", "/ca/desrt/dconf/Writer/site", "Notify", "('/a/', @as [], 'tag')");
1644 /* Try to notify on some invalid paths to make sure they get properly
1645 * rejected by the engine and not passed onto the user...
1647 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify", "('', [''], 'tag')");
1648 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify", "('a', [''], 'tag')");
1649 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify", "('a/', [''], 'tag')");
1650 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify", "('/b//a/', [''], 'tag')");
1651 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify", "('/b//a', [''], 'tag')");
1652 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "WritabilityNotify", "('',)");
1653 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "WritabilityNotify", "('a',)");
1654 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "WritabilityNotify", "('a/',)");
1655 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "WritabilityNotify", "('/b//a/',)");
1656 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "WritabilityNotify", "('/b//a',)");
1657 g_assert_cmpstr (change_log->str, ==, "");
1658 /* Invalid gluing of segments: '/a' + 'b' != '/ab' */
1659 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify", "('/a', ['b'], 'tag')");
1660 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify", "('/a', ['b', 'c'], 'tag')");
1661 g_assert_cmpstr (change_log->str, ==, "");
1662 /* Also: '/a' + '/b' != '/a/b' */
1663 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify", "('/a', ['/b'], 'tag')");
1664 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify", "('/a', ['', '/b'], 'tag')");
1665 g_assert_cmpstr (change_log->str, ==, "");
1666 /* Invalid (non-relative) changes */
1667 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify", "('/', ['/'], 'tag')");
1668 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify", "('/', ['/a'], 'tag')");
1669 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify", "('/', ['a', '/a'], 'tag')");
1670 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify", "('/', ['a', 'a//b'], 'tag')");
1671 g_assert_cmpstr (change_log->str, ==, "");
1673 /* Now try some real cases */
1674 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify",
1675 "('/', [''], 'tag')");
1676 g_assert_cmpstr (change_log->str, ==, "/:1::tag;");
1677 g_string_set_size (change_log, 0);
1678 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify",
1679 "('/one/key', [''], 'tag')");
1680 g_assert_cmpstr (change_log->str, ==, "/one/key:1::tag;");
1681 g_string_set_size (change_log, 0);
1682 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify",
1683 "('/two/', ['keys', 'here'], 'tag')");
1684 g_assert_cmpstr (change_log->str, ==, "/two/:2:keys,here:tag;");
1685 g_string_set_size (change_log, 0);
1686 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "Notify",
1687 "('/some/path/', ['a', 'b/', 'c/d'], 'tag')");
1688 g_assert_cmpstr (change_log->str, ==, "/some/path/:3:a,b/,c/d:tag;");
1689 g_string_set_size (change_log, 0);
1690 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "WritabilityNotify", "('/other/key',)");
1691 g_assert_cmpstr (change_log->str, ==, "w:/other/key:1::;");
1692 g_string_set_size (change_log, 0);
1693 send_signal (G_BUS_TYPE_SESSION, ":1.123", "/ca/desrt/dconf/Writer/user", "WritabilityNotify", "('/other/dir/',)");
1694 g_assert_cmpstr (change_log->str, ==, "w:/other/dir/:1::;");
1695 g_string_set_size (change_log, 0);
1697 dconf_engine_unref (engine);
1700 static gboolean it_is_good_to_be_done;
1702 static gpointer
1703 waiter_thread (gpointer user_data)
1705 DConfEngine *engine = user_data;
1707 dconf_engine_sync (engine);
1709 g_assert (g_atomic_int_get (&it_is_good_to_be_done));
1711 return NULL;
1714 static void
1715 test_sync (void)
1717 GThread *waiter_threads[5];
1718 DConfChangeset *change;
1719 DConfEngine *engine;
1720 GError *error = NULL;
1721 gboolean success;
1722 gint i;
1724 engine = dconf_engine_new (SRCDIR "/profile/dos", NULL, NULL);
1726 /* Make sure a waiter thread returns straight away if nothing is
1727 * outstanding.
1729 g_atomic_int_set (&it_is_good_to_be_done, TRUE);
1730 g_thread_join (g_thread_new ("waiter", waiter_thread, engine));
1731 g_atomic_int_set (&it_is_good_to_be_done, FALSE);
1733 /* The write will try to check the system-db for a lock. That will
1734 * fail because it doesn't exist...
1736 g_test_expect_message ("dconf", G_LOG_LEVEL_WARNING, "*unable to open file*");
1737 change = dconf_changeset_new_write ("/value", g_variant_new_boolean (TRUE));
1738 success = dconf_engine_change_fast (engine, change, NULL, &error);
1739 g_assert_no_error (error);
1740 g_assert (success);
1742 /* Spin up some waiters */
1743 for (i = 0; i < G_N_ELEMENTS (waiter_threads); i++)
1744 waiter_threads[i] = g_thread_new ("test waiter", waiter_thread, engine);
1745 g_usleep(100 * G_TIME_SPAN_MILLISECOND);
1746 /* Release them by completing the pending async call */
1747 g_atomic_int_set (&it_is_good_to_be_done, TRUE);
1748 dconf_mock_dbus_async_reply (g_variant_new ("(s)", "tag"), NULL);
1749 /* Make sure they all quit by joining them */
1750 for (i = 0; i < G_N_ELEMENTS (waiter_threads); i++)
1751 g_thread_join (waiter_threads[i]);
1752 g_atomic_int_set (&it_is_good_to_be_done, FALSE);
1754 /* Do the same again, but with a failure as a result */
1755 success = dconf_engine_change_fast (engine, change, NULL, &error);
1756 g_assert_no_error (error);
1757 g_assert (success);
1758 for (i = 0; i < G_N_ELEMENTS (waiter_threads); i++)
1759 waiter_threads[i] = g_thread_new ("test waiter", waiter_thread, engine);
1760 g_usleep(100 * G_TIME_SPAN_MILLISECOND);
1761 error = g_error_new_literal (G_FILE_ERROR, G_FILE_ERROR_NOENT, "some error");
1762 g_test_expect_message ("dconf", G_LOG_LEVEL_WARNING, "failed to commit changes to dconf: some error");
1763 g_atomic_int_set (&it_is_good_to_be_done, TRUE);
1764 dconf_mock_dbus_async_reply (NULL, error);
1765 g_clear_error (&error);
1766 /* Make sure they all quit by joining them */
1767 for (i = 0; i < G_N_ELEMENTS (waiter_threads); i++)
1768 g_thread_join (waiter_threads[i]);
1769 g_atomic_int_set (&it_is_good_to_be_done, FALSE);
1771 /* Now put two changes in the queue and make sure we have to reply to
1772 * both of them before the waiters finish.
1774 success = dconf_engine_change_fast (engine, change, NULL, &error);
1775 g_assert_no_error (error);
1776 g_assert (success);
1777 success = dconf_engine_change_fast (engine, change, NULL, &error);
1778 g_assert_no_error (error);
1779 g_assert (success);
1780 for (i = 0; i < G_N_ELEMENTS (waiter_threads); i++)
1781 waiter_threads[i] = g_thread_new ("test waiter", waiter_thread, engine);
1782 g_usleep(100 * G_TIME_SPAN_MILLISECOND);
1783 dconf_mock_dbus_async_reply (g_variant_new ("(s)", "tag1"), NULL);
1784 /* Still should not have quit yet... wait a bit to let the waiters try
1785 * to shoot themselves in their collective feet...
1787 g_usleep(100 * G_TIME_SPAN_MILLISECOND);
1788 /* Will be OK after the second reply */
1789 g_atomic_int_set (&it_is_good_to_be_done, TRUE);
1790 dconf_mock_dbus_async_reply (g_variant_new ("(s)", "tag2"), NULL);
1791 /* Make sure they all quit by joining them */
1792 for (i = 0; i < G_N_ELEMENTS (waiter_threads); i++)
1793 g_thread_join (waiter_threads[i]);
1794 g_atomic_int_set (&it_is_good_to_be_done, FALSE);
1796 dconf_changeset_unref (change);
1797 dconf_engine_unref (engine);
1798 dconf_mock_shm_reset ();
1803 main (int argc, char **argv)
1805 g_setenv ("XDG_RUNTIME_DIR", "/RUNTIME/", TRUE);
1806 g_setenv ("XDG_CONFIG_HOME", "/HOME/.config", TRUE);
1807 g_unsetenv ("DCONF_PROFILE");
1809 main_thread = g_thread_self ();
1811 g_test_init (&argc, &argv, NULL);
1813 g_test_add_func ("/engine/profile-parser", test_profile_parser);
1814 g_test_add_func ("/engine/signal-threadsafety", test_signal_threadsafety);
1815 g_test_add_func ("/engine/sources/user", test_user_source);
1816 g_test_add_func ("/engine/sources/system", test_system_source);
1817 g_test_add_func ("/engine/sources/file", test_file_source);
1818 g_test_add_func ("/engine/sources/service", test_service_source);
1819 g_test_add_func ("/engine/read", test_read);
1820 g_test_add_func ("/engine/watch/fast", test_watch_fast);
1821 g_test_add_func ("/engine/watch/sync", test_watch_sync);
1822 g_test_add_func ("/engine/watch/watching", test_watching);
1823 g_test_add_func ("/engine/change/fast", test_change_fast);
1824 g_test_add_func ("/engine/change/sync", test_change_sync);
1825 g_test_add_func ("/engine/signals", test_signals);
1826 g_test_add_func ("/engine/sync", test_sync);
1828 return g_test_run ();